]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix #417: prefetch and ECS causing cache corruption when used 674/head
authorGeorge Thessalonikefs <george@nlnetlabs.nl>
Wed, 17 Feb 2021 12:39:15 +0000 (13:39 +0100)
committerGeorge Thessalonikefs <george@nlnetlabs.nl>
Wed, 11 May 2022 22:56:01 +0000 (00:56 +0200)
  together.

12 files changed:
daemon/worker.c
doc/Changelog
edns-subnet/subnetmod.c
edns-subnet/subnetmod.h
pythonmod/doc/modules/functions.rst
pythonmod/doc/modules/struct.rst
pythonmod/examples/inplace_callbacks.py
services/listen_dnsport.c
services/mesh.c
services/mesh.h
testdata/subnet_prefetch.crpl [new file with mode: 0644]
testdata/subnet_prefetch_with_client_ecs.crpl [new file with mode: 0644]

index 270de8975b0d562ecbfbe0a49e26129e67fd4a3e..bf8c5d6b6763290594b8c3342a65d7c880d4a7c6 100644 (file)
@@ -98,7 +98,7 @@
 /** ratelimit for error responses */
 #define ERROR_RATELIMIT 100 /* qps */
 
-/** 
+/**
  * seconds to add to prefetch leeway.  This is a TTL that expires old rrsets
  * earlier than they should in order to put the new update into the cache.
  * This additional value is to make sure that if not all TTLs are equal in
@@ -763,11 +763,12 @@ bail_out:
 
 /** Reply to client and perform prefetch to keep cache up to date. */
 static void
-reply_and_prefetch(struct worker* worker, struct query_info* qinfo, 
+reply_and_prefetch(struct worker* worker, struct query_info* qinfo,
        uint16_t flags, struct comm_reply* repinfo, time_t leeway, int noreply,
-       int rpz_passthru)
+       int rpz_passthru, struct edns_option* opt_list)
 {
-       /* first send answer to client to keep its latency 
+       (void)opt_list;
+       /* first send answer to client to keep its latency
         * as small as a cachereply */
        if(!noreply) {
                if(repinfo->c->tcp_req_info) {
@@ -778,13 +779,23 @@ reply_and_prefetch(struct worker* worker, struct query_info* qinfo,
                comm_point_send_reply(repinfo);
        }
        server_stats_prefetch(&worker->stats, worker);
-       
+#ifdef CLIENT_SUBNET
+       /* Check if the subnet module is enabled. In that case pass over the
+        * comm_reply information for ECS generation later. The mesh states are
+        * unique when subnet is enabled. */
+       if(modstack_find(&worker->env.mesh->mods, "subnetcache") != -1
+               && worker->env.unique_mesh) {
+               mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway +
+                       PREFETCH_EXPIRY_ADD, rpz_passthru, repinfo, opt_list);
+               return;
+       }
+#endif
        /* create the prefetch in the mesh as a normal lookup without
         * client addrs waiting, which has the cache blacklisted (to bypass
         * the cache and go to the network for the data). */
        /* this (potentially) runs the mesh for the new query */
-       mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway + 
-               PREFETCH_EXPIRY_ADD, rpz_passthru);
+       mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway +
+               PREFETCH_EXPIRY_ADD, rpz_passthru, NULL, NULL);
 }
 
 /**
@@ -1240,6 +1251,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
        struct lruhash_entry* e;
        struct query_info qinfo;
        struct edns_data edns;
+       struct edns_option* original_edns_list = NULL;
        enum acl_access acl;
        struct acl_addr* acladdr;
        int rc = 0;
@@ -1612,6 +1624,11 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                cinfo = &cinfo_tmp;
        }
 
+       /* Keep the original edns list around. The pointer could change if there is
+        * a cached answer (through the inplace callback function there).
+        * No need to actually copy the contents as they shouldn't change.
+        * Used while prefetching and subnet is enabled. */
+       original_edns_list = edns.opt_list_in;
 lookup_cache:
        /* Lookup the cache.  In case we chase an intermediate CNAME chain
         * this is a two-pass operation, and lookup_qinfo is different for
@@ -1649,7 +1666,8 @@ lookup_cache:
                                                sldns_buffer_read_u16_at(c->buffer, 2),
                                                repinfo, leeway,
                                                (partial_rep || need_drop),
-                                               rpz_passthru);
+                                               rpz_passthru,
+                                               original_edns_list);
                                        if(!partial_rep) {
                                                rc = 0;
                                                regional_free_all(worker->scratchpad);
index 15f448fd502bbd39d311d69611e39f6d6bd9a546..f55f4cab0f56e36f052264fa6c533c8b047a2283 100644 (file)
@@ -1,3 +1,7 @@
+12 May 2022: George
+       - Fix #417: prefetch and ECS causing cache corruption when used
+         together.
+
 11 May 2022: Wouter
        - Fix #673: DNS over TLS: error: SSL_handshake syscall: No route to
          host.
index fcea71c31438d568c166b31b5670f7d2ef2265f7..25190b040d45a0fa7e4a0d92544b0bb3f3e1b34d 100644 (file)
@@ -97,8 +97,8 @@ subnet_new_qstate(struct module_qstate *qstate, int id)
 }
 
 /** Add ecs struct to edns list, after parsing it to wire format. */
-static void
-ecs_opt_list_append(struct ecs_data* ecs, struct edns_option** list,
+void
+subnet_ecs_opt_list_append(struct ecs_data* ecs, struct edns_option** list,
        struct module_qstate *qstate)
 {
        size_t sn_octs, sn_octs_remainder;
@@ -164,7 +164,7 @@ int ecs_whitelist_check(struct query_info* qinfo,
                 * set. */
                if(!edns_opt_list_find(qstate->edns_opts_back_out,
                        qstate->env->cfg->client_subnet_opcode)) {
-                       ecs_opt_list_append(&sq->ecs_server_out,
+                       subnet_ecs_opt_list_append(&sq->ecs_server_out,
                                &qstate->edns_opts_back_out, qstate);
                }
                sq->subnet_sent = 1;
@@ -231,7 +231,7 @@ subnetmod_init(struct module_env *env, int id)
        env->unique_mesh = 1;
        if(!edns_register_option(env->cfg->client_subnet_opcode,
                env->cfg->client_subnet_always_forward /* bypass cache */,
-               0 /* no aggregation */, env)) {
+               1 /* no aggregation */, env)) {
                log_err("subnetcache: could not register opcode");
                ecs_whitelist_delete(sn_env->whitelist);
                slabhash_delete(sn_env->subnet_msg_cache);
@@ -602,7 +602,7 @@ parse_subnet_option(struct edns_option* ecs_option, struct ecs_data* ecs)
        return 1;
 }
 
-static void
+void
 subnet_option_from_ss(struct sockaddr_storage *ss, struct ecs_data* ecs,
        struct config_file* cfg)
 {
@@ -765,7 +765,7 @@ subnetmod_operate(struct module_qstate *qstate, enum module_ev event,
                        verbose(VERB_QUERY, "subnetcache: answered from cache");
                        qstate->ext_state[id] = module_finished;
 
-                       ecs_opt_list_append(&sq->ecs_client_out,
+                       subnet_ecs_opt_list_append(&sq->ecs_client_out,
                                &qstate->edns_opts_front_out, qstate);
                        return;
                }
@@ -787,7 +787,7 @@ subnetmod_operate(struct module_qstate *qstate, enum module_ev event,
                        sq->ecs_server_out.subnet_source_mask =
                                qstate->env->cfg->max_client_subnet_ipv6;
                /* Safe to copy completely, even if the source is limited by the
-                * configuration. ecs_opt_list_append() will limit the address.
+                * configuration. subnet_ecs_opt_list_append() will limit the address.
                 * */
                memcpy(&sq->ecs_server_out.subnet_addr,
                        sq->ecs_client_in.subnet_addr, INET6_SIZE);
@@ -811,7 +811,7 @@ subnetmod_operate(struct module_qstate *qstate, enum module_ev event,
                qstate->ext_state[id] = eval_response(qstate, id, sq);
                if(qstate->ext_state[id] == module_finished &&
                        qstate->return_msg) {
-                       ecs_opt_list_append(&sq->ecs_client_out,
+                       subnet_ecs_opt_list_append(&sq->ecs_client_out,
                                &qstate->edns_opts_front_out, qstate);
                }
                qstate->no_cache_store = sq->started_no_cache_store;
index 27ba2ee7412989b6c10557c24bfa0ec054179711..c877692b46b16aa4ca7b49acfb16f75e0d7fc6dd 100644 (file)
@@ -143,4 +143,11 @@ int ecs_query_response(struct module_qstate* qstate, struct dns_msg* response,
 /** mark subnet msg to be deleted */
 void subnet_markdel(void* key);
 
+/** Add ecs struct to edns list, after parsing it to wire format. */
+void subnet_ecs_opt_list_append(struct ecs_data* ecs, struct edns_option** list,
+       struct module_qstate *qstate);
+
+/** Create ecs_data from the sockaddr_storage information. */
+void subnet_option_from_ss(struct sockaddr_storage *ss, struct ecs_data* ecs,
+       struct config_file* cfg);
 #endif /* SUBNETMOD_H */
index 951a17f948c75542130d177e13417b923434eaf5..76c21d6dbedd5bc4b1b749445bf5c75358518b03 100644 (file)
@@ -60,7 +60,7 @@ EDNS options
 
 .. function:: edns_opt_list_remove(list, code);
 
-    Remove an ENDS option code from the list.
+    Remove an EDNS option code from the list.
     .. note:: All :class:`edns_option` with the code will be removed
 
     :param list: linked list of :class:`edns_option`
index 310cf552456e607b310698940a2d6142e282e8bb..6e6a32e3e8b845b0a33504c6caf5d451519e07ec 100644 (file)
@@ -77,7 +77,7 @@ module_qstate
 
     .. attribute:: edns_opts_back_in_iter
 
-        Iterator for `ends_opts_back_in`.
+        Iterator for `edns_opts_back_in`.
 
     .. attribute:: edns_opts_front_out
 
index de375b4e12fc1cd0f72edbc52a4d6c390b05fbd7..2682fbd02dc0774edbd811e5a1618f881b564b34 100644 (file)
@@ -207,7 +207,7 @@ def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out,
 
     """
     log_info("python: called back while servfail.")
-    # Append the example ENDS option
+    # Append the example EDNS option
     b = bytearray.fromhex("")
     edns_opt_list_append(opt_list_out, 65003, b, region)
 
index afe4cb71d2ad2b0214805b9514cbcfd5427ebdf4..03153bd64778ce145482a149f041a2b0c83cd459 100644 (file)
@@ -1158,7 +1158,7 @@ if_is_ssl(const char* ifname, const char* port, int ssl_port,
  * @param do_auto: use automatic interface detection.
  *     If enabled, then ifname must be the wildcard name.
  * @param do_udp: if udp should be used.
- * @param do_tcp: if udp should be used.
+ * @param do_tcp: if tcp should be used.
  * @param hints: for getaddrinfo. family and flags have to be set by caller.
  * @param port: Port number to use (as string).
  * @param list: list of open ports, appended to, changed to point to list head.
index 7c767e626030ea2462621c163ff1ebab8a3a0d3a..fbaa966bdd05921b3551fc47e8db6a684ca5e527 100644 (file)
 #include "respip/respip.h"
 #include "services/listen_dnsport.h"
 
+#ifdef CLIENT_SUBNET
+#include "edns-subnet/subnetmod.h"
+#include "edns-subnet/edns-subnet.h"
+#endif
+
 /** subtract timers and the values do not overflow or become negative */
 static void
 timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start)
@@ -736,11 +741,11 @@ static void mesh_schedule_prefetch(struct mesh_area* mesh,
                /* move to either the forever or the jostle_list */
                if(mesh->num_forever_states < mesh->max_forever_states) {
                        mesh->num_forever_states ++;
-                       mesh_list_insert(s, &mesh->forever_first, 
+                       mesh_list_insert(s, &mesh->forever_first,
                                &mesh->forever_last);
                        s->list_select = mesh_forever_list;
                } else {
-                       mesh_list_insert(s, &mesh->jostle_first, 
+                       mesh_list_insert(s, &mesh->jostle_first,
                                &mesh->jostle_last);
                        s->list_select = mesh_jostle_list;
                }
@@ -761,10 +766,114 @@ static void mesh_schedule_prefetch(struct mesh_area* mesh,
        mesh_run(mesh, s, module_event_new, NULL);
 }
 
+#ifdef CLIENT_SUBNET
+/* Same logic as mesh_schedule_prefetch but tailored to the subnet module logic
+ * like passing along the comm_reply info. This will be faked into an EDNS
+ * option for processing by the subnet module if the client has not already
+ * attached its own ECS data. */
+static void mesh_schedule_prefetch_subnet(struct mesh_area* mesh,
+       struct query_info* qinfo, uint16_t qflags, time_t leeway, int run,
+       int rpz_passthru, struct comm_reply* rep, struct edns_option* edns_list)
+{
+       struct mesh_state* s = NULL;
+       struct edns_option* opt = NULL;
+#ifdef UNBOUND_DEBUG
+       struct rbnode_type* n;
+#endif
+       if(!mesh_make_new_space(mesh, NULL)) {
+               verbose(VERB_ALGO, "Too many queries. dropped prefetch.");
+               mesh->stats_dropped ++;
+               return;
+       }
+
+       s = mesh_state_create(mesh->env, qinfo, NULL,
+               qflags&(BIT_RD|BIT_CD), 0, 0);
+       if(!s) {
+               log_err("prefetch_subnet mesh_state_create: out of memory");
+               return;
+       }
+       mesh_state_make_unique(s);
+
+       opt = edns_opt_list_find(edns_list, mesh->env->cfg->client_subnet_opcode);
+       if(opt) {
+               /* Use the client's ECS data */
+               if(!edns_opt_list_append(&s->s.edns_opts_front_in, opt->opt_code,
+                       opt->opt_len, opt->opt_data, s->s.region)) {
+                       log_err("prefetch_subnet edns_opt_list_append: out of memory");
+                       return;
+               }
+       } else {
+               /* Fake the ECS data from the client's IP */
+               struct ecs_data ecs;
+               memset(&ecs, 0, sizeof(ecs));
+               subnet_option_from_ss(&rep->addr, &ecs, mesh->env->cfg);
+               if(ecs.subnet_validdata == 0) {
+                       log_err("prefetch_subnet subnet_option_from_ss: invalid data");
+                       return;
+               }
+               subnet_ecs_opt_list_append(&ecs, &s->s.edns_opts_front_in, &s->s);
+               if(!s->s.edns_opts_front_in) {
+                       log_err("prefetch_subnet subnet_ecs_opt_list_append: out of memory");
+                       return;
+               }
+       }
+#ifdef UNBOUND_DEBUG
+       n =
+#else
+       (void)
+#endif
+       rbtree_insert(&mesh->all, &s->node);
+       log_assert(n != NULL);
+       /* set detached (it is now) */
+       mesh->num_detached_states++;
+       /* make it ignore the cache */
+       sock_list_insert(&s->s.blacklist, NULL, 0, s->s.region);
+       s->s.prefetch_leeway = leeway;
+
+       if(s->list_select == mesh_no_list) {
+               /* move to either the forever or the jostle_list */
+               if(mesh->num_forever_states < mesh->max_forever_states) {
+                       mesh->num_forever_states ++;
+                       mesh_list_insert(s, &mesh->forever_first,
+                               &mesh->forever_last);
+                       s->list_select = mesh_forever_list;
+               } else {
+                       mesh_list_insert(s, &mesh->jostle_first,
+                               &mesh->jostle_last);
+                       s->list_select = mesh_jostle_list;
+               }
+       }
+       s->s.rpz_passthru = rpz_passthru;
+
+       if(!run) {
+#ifdef UNBOUND_DEBUG
+               n =
+#else
+               (void)
+#endif
+               rbtree_insert(&mesh->run, &s->run_node);
+               log_assert(n != NULL);
+               return;
+       }
+
+       mesh_run(mesh, s, module_event_new, NULL);
+}
+#endif /* CLIENT_SUBNET */
+
 void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
-        uint16_t qflags, time_t leeway, int rpz_passthru)
+       uint16_t qflags, time_t leeway, int rpz_passthru,
+       struct comm_reply* rep, struct edns_option* opt_list)
 {
-       mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1, rpz_passthru);
+       (void)opt_list;
+       (void)rep;
+#ifdef CLIENT_SUBNET
+       if(rep)
+               mesh_schedule_prefetch_subnet(mesh, qinfo, qflags, leeway, 1,
+                       rpz_passthru, rep, opt_list);
+       else
+#endif
+               mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1,
+                       rpz_passthru);
 }
 
 void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e,
@@ -1561,7 +1670,7 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns,
         struct comm_reply* rep, uint16_t qid, uint16_t qflags,
         const struct query_info* qinfo)
 {
-       struct mesh_reply* r = regional_alloc(s->s.region, 
+       struct mesh_reply* r = regional_alloc(s->s.region,
                sizeof(struct mesh_reply));
        if(!r)
                return 0;
index 526e679fe991c64c1403e43e16213a13c712a79e..3be9b63faeddcabed3c40e1ac04d691219d487c4 100644 (file)
@@ -335,9 +335,13 @@ int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo,
  * @param leeway: TTL leeway what to expire earlier for this update.
  * @param rpz_passthru: if true, the rpz passthru was previously found and
  *     further rpz processing is stopped.
+ * @param rep: comm_reply for the client; to be used when subnet is enabled.
+ * @param opt_list: edns opt_list from the client; to be used when subnet is
+ *     enabled.
  */
 void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
-       uint16_t qflags, time_t leeway, int rpz_passthru);
+       uint16_t qflags, time_t leeway, int rpz_passthru,
+       struct comm_reply* rep, struct edns_option* opt_list);
 
 /**
  * Handle new event from the wire. A serviced query has returned.
diff --git a/testdata/subnet_prefetch.crpl b/testdata/subnet_prefetch.crpl
new file mode 100644 (file)
index 0000000..7083aba
--- /dev/null
@@ -0,0 +1,215 @@
+; Check if the prefetch option works properly for messages stored in the global
+; cache for non-ECS clients. The prefetch query needs to result in an ECS
+; outgoing query based on the client's IP.
+
+server:
+       trust-anchor-signaling: no
+       target-fetch-policy: "0 0 0 0 0"
+       send-client-subnet: 1.2.3.4
+       max-client-subnet-ipv4: 21
+       module-config: "subnetcache iterator"
+       verbosity: 3
+       access-control: 127.0.0.1 allow_snoop
+       qname-minimisation: no
+       minimal-responses: no
+       serve-expired: yes
+       prefetch: yes
+
+stub-zone:
+       name: "."
+       stub-addr: 193.0.14.129         # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test prefetch option for global cache
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+       ADDRESS 193.0.14.129
+       ENTRY_BEGIN
+               MATCH opcode qtype qname ednsdata
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       . IN NS
+               SECTION ANSWER
+                       . IN NS K.ROOT-SERVERS.NET.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                               ;; we expect to receive empty
+                       HEX_EDNSDATA_END
+                       K.ROOT-SERVERS.NET.     IN      A       193.0.14.129
+       ENTRY_END
+
+       ENTRY_BEGIN
+               MATCH opcode qtype qname
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       www.example.com. IN A
+               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 ednsdata
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       com. IN NS
+               SECTION ANSWER
+                       com.    IN NS   a.gtld-servers.net.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                               ;; we expect to receive empty
+                       HEX_EDNSDATA_END
+                       a.gtld-servers.net.     IN      A       192.5.6.30
+       ENTRY_END
+
+       ENTRY_BEGIN
+               MATCH opcode qtype qname
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       www.example.com. IN A
+               SECTION AUTHORITY
+                       example.com.    IN NS   ns.example.com.
+               SECTION ADDITIONAL
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 10
+       ADDRESS 1.2.3.4
+       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.com.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                               ;; we expect to receive empty
+                       HEX_EDNSDATA_END
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+
+       ; response to query of interest
+       ENTRY_BEGIN
+               MATCH opcode qtype qname
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       www.example.com. IN A
+               SECTION ANSWER
+                       www.example.com. 10 IN A        10.20.30.40
+               SECTION AUTHORITY
+                       example.com.    IN NS   ns.example.com.
+               SECTION ADDITIONAL
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 11 100
+       ADDRESS 1.2.3.4
+       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.com.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                               ;; we expect to receive empty
+                       HEX_EDNSDATA_END
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+
+       ; response to query of interest
+       ENTRY_BEGIN
+               MATCH opcode qtype qname ednsdata
+               ADJUST copy_id copy_ednsdata_assume_clientsubnet
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       www.example.com. IN A
+               SECTION ANSWER
+                       www.example.com. IN A   10.20.30.40
+               SECTION AUTHORITY
+                       example.com.    IN NS   ns.example.com.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                                               ; client is 127.0.0.1
+                               00 08           ; OPC
+                               00 07           ; option length
+                               00 01           ; Family
+                               15 00           ; source mask, scopemask
+                               7f 00 00        ; address
+                       HEX_EDNSDATA_END
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; This answer should be in the global cache
+STEP 2 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  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+
+; Try to trigger a prefetch
+STEP 3 TIME_PASSES ELAPSE 11
+
+STEP 11 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; This expired record came from the cache and a prefetch is triggered
+STEP 12 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 30 IN A       10.20.30.40
+SECTION AUTHORITY
+example.com.   3589 IN NS      ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                3589 IN         A       1.2.3.4
+ENTRY_END
+
+; Allow upstream to reply to the prefetch query.
+; It can only be answered if correct ECS was derived from the client's IP.
+; Otherwise the test will fail with "messages pending".
+STEP 13 TRAFFIC
+
+SCENARIO_END
diff --git a/testdata/subnet_prefetch_with_client_ecs.crpl b/testdata/subnet_prefetch_with_client_ecs.crpl
new file mode 100644 (file)
index 0000000..b041025
--- /dev/null
@@ -0,0 +1,221 @@
+; Check if the prefetch option works properly for messages stored in the global
+; cache for ECS clients. The prefetch query needs to result in an ECS
+; outgoing query using the client's ECS data.
+
+server:
+       trust-anchor-signaling: no
+       target-fetch-policy: "0 0 0 0 0"
+       send-client-subnet: 1.2.3.4
+       max-client-subnet-ipv4: 21
+       module-config: "subnetcache iterator"
+       verbosity: 3
+       access-control: 127.0.0.1 allow_snoop
+       qname-minimisation: no
+       minimal-responses: no
+       serve-expired: yes
+       prefetch: yes
+
+stub-zone:
+       name: "."
+       stub-addr: 193.0.14.129         # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test prefetch option for global cache
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+       ADDRESS 193.0.14.129
+       ENTRY_BEGIN
+               MATCH opcode qtype qname ednsdata
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       . IN NS
+               SECTION ANSWER
+                       . IN NS K.ROOT-SERVERS.NET.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                               ;; we expect to receive empty
+                       HEX_EDNSDATA_END
+                       K.ROOT-SERVERS.NET.     IN      A       193.0.14.129
+       ENTRY_END
+
+       ENTRY_BEGIN
+               MATCH opcode qtype qname
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       www.example.com. IN A
+               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 ednsdata
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       com. IN NS
+               SECTION ANSWER
+                       com.    IN NS   a.gtld-servers.net.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                               ;; we expect to receive empty
+                       HEX_EDNSDATA_END
+                       a.gtld-servers.net.     IN      A       192.5.6.30
+       ENTRY_END
+
+       ENTRY_BEGIN
+               MATCH opcode qtype qname
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       www.example.com. IN A
+               SECTION AUTHORITY
+                       example.com.    IN NS   ns.example.com.
+               SECTION ADDITIONAL
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 10
+       ADDRESS 1.2.3.4
+       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.com.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                               ;; we expect to receive empty
+                       HEX_EDNSDATA_END
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+
+       ; response to query of interest
+       ENTRY_BEGIN
+               MATCH opcode qtype qname
+               ADJUST copy_id
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       www.example.com. IN A
+               SECTION ANSWER
+                       www.example.com. 10 IN A        10.20.30.40
+               SECTION AUTHORITY
+                       example.com.    IN NS   ns.example.com.
+               SECTION ADDITIONAL
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 11 100
+       ADDRESS 1.2.3.4
+       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.com.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                               ;; we expect to receive empty
+                       HEX_EDNSDATA_END
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+
+       ; response to query of interest
+       ENTRY_BEGIN
+               MATCH opcode qtype qname ednsdata
+               ADJUST copy_id copy_ednsdata_assume_clientsubnet
+               REPLY QR NOERROR
+               SECTION QUESTION
+                       www.example.com. IN A
+               SECTION ANSWER
+                       www.example.com. IN A   10.20.30.40
+               SECTION AUTHORITY
+                       example.com.    IN NS   ns.example.com.
+               SECTION ADDITIONAL
+                       HEX_EDNSDATA_BEGIN
+                                               ; client is 127.0.0.1
+                               00 08           ; OPC
+                               00 05           ; option length
+                               00 01           ; Family
+                               08 00           ; source mask, scopemask
+                               7f              ; address
+                       HEX_EDNSDATA_END
+                       ns.example.com.         IN      A       1.2.3.4
+       ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; This answer should be in the global cache
+STEP 2 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  10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+
+; Try to trigger a prefetch
+STEP 3 TIME_PASSES ELAPSE 11
+
+STEP 11 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+www.example.com. IN A
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+               00 08 00 05     ; OPC, optlen
+               00 01 08 00     ; ip4, source 8, scope 0
+               7f              ; 127.0.0.0/8
+HEX_EDNSDATA_END
+ENTRY_END
+
+; This expired record came from the cache and a prefetch is triggered
+STEP 12 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA DO NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. 30 IN A       10.20.30.40
+SECTION AUTHORITY
+example.com.   3589 IN NS      ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                3589 IN         A       1.2.3.4
+ENTRY_END
+
+; Allow upstream to reply to the prefetch query.
+; It can only be answered if correct ECS was derived from the client's IP.
+; Otherwise the test will fail with "messages pending".
+STEP 13 TRAFFIC
+
+SCENARIO_END