together.
/** 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
/** 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) {
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);
}
/**
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;
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
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);
+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.
}
/** 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;
* 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;
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);
return 1;
}
-static void
+void
subnet_option_from_ss(struct sockaddr_storage *ss, struct ecs_data* ecs,
struct config_file* cfg)
{
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;
}
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);
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;
/** 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 */
.. 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`
.. attribute:: edns_opts_back_in_iter
- Iterator for `ends_opts_back_in`.
+ Iterator for `edns_opts_back_in`.
.. attribute:: edns_opts_front_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)
* @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.
#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)
/* 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;
}
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,
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;
* @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.
--- /dev/null
+; 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
--- /dev/null
+; 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