- For #664: easier code flow for subnetcache prefetching.

- For #664: add testcase.
This commit is contained in:
George Thessalonikefs 2023-07-06 21:57:27 +02:00
parent 91c298c901
commit 40e47bf767
7 changed files with 120 additions and 194 deletions

View file

@ -786,7 +786,7 @@ reply_and_prefetch(struct worker* worker, struct query_info* qinfo,
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);
PREFETCH_EXPIRY_ADD, rpz_passthru, &repinfo->addr, opt_list);
return;
}
#endif

View file

@ -331,6 +331,8 @@ update_cache(struct module_qstate *qstate, int id)
struct ecs_data *edns = &sq->ecs_client_in;
size_t i;
hashvalue_type h;
struct lruhash_entry* lru_entry;
int need_to_insert;
/* qinfo_hash is not set if it is prefetch request */
if (qstate->minfo[id] && ((struct subnet_qstate*)qstate->minfo[id])->qinfo_hash) {
@ -340,9 +342,9 @@ update_cache(struct module_qstate *qstate, int id)
}
/* Step 1, general qinfo lookup */
struct lruhash_entry *lru_entry = slabhash_lookup(subnet_msg_cache, h,
lru_entry = slabhash_lookup(subnet_msg_cache, h,
&qstate->qinfo, 1);
int need_to_insert = (lru_entry == NULL);
need_to_insert = (lru_entry == NULL);
if (!lru_entry) {
void* data = calloc(1,
sizeof(struct subnet_msg_cache_data));
@ -456,7 +458,7 @@ lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq,
sq->ecs_client_out.subnet_validdata = 1;
}
if (prefetch && *qstate->env->now > ((struct reply_info *)node->elem)->prefetch_ttl) {
if (prefetch && *qstate->env->now >= ((struct reply_info *)node->elem)->prefetch_ttl) {
qstate->need_refetch = 1;
}
return 1;

View file

@ -688,107 +688,6 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo,
return 1;
}
#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 mesh_state* mstate,
struct sockaddr_storage *client_addr)
{
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(mstate->s.edns_opts_front_in, 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(client_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_state_delete(&mstate->s);
mesh_run(mesh, s, module_event_new, NULL);
}
#endif /* CLIENT_SUBNET */
/* Internal backend routine of mesh_new_prefetch(). It takes one additional
* parameter, 'run', which controls whether to run the prefetch state
* immediately. When this function is called internally 'run' could be
@ -874,7 +773,7 @@ static void mesh_schedule_prefetch(struct mesh_area* mesh,
* 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)
int rpz_passthru, struct sockaddr_storage* addr, struct edns_option* edns_list)
{
struct mesh_state* s = NULL;
struct edns_option* opt = NULL;
@ -907,7 +806,7 @@ static void mesh_schedule_prefetch_subnet(struct mesh_area* mesh,
/* 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);
subnet_option_from_ss(addr, &ecs, mesh->env->cfg);
if(ecs.subnet_validdata == 0) {
log_err("prefetch_subnet subnet_option_from_ss: invalid data");
return;
@ -963,14 +862,14 @@ static void mesh_schedule_prefetch_subnet(struct mesh_area* mesh,
void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
uint16_t qflags, time_t leeway, int rpz_passthru,
struct comm_reply* rep, struct edns_option* opt_list)
struct sockaddr_storage* addr, struct edns_option* opt_list)
{
(void)addr;
(void)opt_list;
(void)rep;
#ifdef CLIENT_SUBNET
if(rep)
if(addr)
mesh_schedule_prefetch_subnet(mesh, qinfo, qflags, leeway, 1,
rpz_passthru, rep, opt_list);
rpz_passthru, addr, opt_list);
else
#endif
mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1,
@ -1939,13 +1838,19 @@ mesh_continue(struct mesh_area* mesh, struct mesh_state* mstate,
if(s == module_finished) {
if(mstate->s.curmod == 0) {
struct query_info* qinfo = NULL;
struct edns_option* opt_list = NULL, *ecs;
struct sockaddr_storage addr;
uint16_t qflags;
int rpz_p = 0;
struct sockaddr_storage client_addr;
if (mstate->reply_list) {
client_addr = mstate->reply_list->query_reply.addr;
}
#ifdef CLIENT_SUBNET
if(mstate->s.need_refetch && mstate->reply_list &&
modstack_find(&mesh->mods, "subnetcache") != -1 &&
mstate->s.env->unique_mesh) {
addr = mstate->reply_list->query_reply.addr;
} else
#endif
memset(&addr, 0, sizeof(addr));
mesh_query_done(mstate);
mesh_walk_supers(mesh, mstate);
@ -1956,25 +1861,26 @@ mesh_continue(struct mesh_area* mesh, struct mesh_state* mstate,
* we need to make a copy of the query info here. */
if(mstate->s.need_refetch) {
mesh_copy_qinfo(mstate, &qinfo, &qflags);
#ifdef CLIENT_SUBNET
/* Make also a copy of the ecs option if any */
if((ecs = edns_opt_list_find(
mstate->s.edns_opts_front_in,
mstate->s.env->cfg->client_subnet_opcode)) != NULL) {
(void)edns_opt_list_append(&opt_list,
ecs->opt_code, ecs->opt_len,
ecs->opt_data,
mstate->s.env->scratch);
}
#endif
rpz_p = mstate->s.rpz_passthru;
}
if(qinfo) {
#ifdef CLIENT_SUBNET
if(modstack_find(&mesh->mods, "subnetcache") != -1 ) {
mesh_schedule_prefetch_subnet(mesh, qinfo, qflags,
0, 1, rpz_p, mstate, &client_addr);
}
else {
mesh_state_delete(&mstate->s);
mesh_schedule_prefetch(mesh, qinfo, qflags,
0, 1, rpz_p);
}
#else
mesh_state_delete(&mstate->s);
mesh_schedule_prefetch(mesh, qinfo, qflags,
0, 1, rpz_p);
#endif
mesh_new_prefetch(mesh, qinfo, qflags, 0,
rpz_p,
addr.ss_family!=AF_UNSPEC?&addr:NULL,
opt_list);
} else {
mesh_state_delete(&mstate->s);
}

View file

@ -335,13 +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 addr: sockaddr_storage for the client; to be used with subnet.
* @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,
struct comm_reply* rep, struct edns_option* opt_list);
struct sockaddr_storage* addr, struct edns_option* opt_list);
/**
* Handle new event from the wire. A serviced query has returned.

View file

@ -1,18 +1,17 @@
; 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.
; Check if the prefetch option works properly for messages stored in ECS cache
; for non-ECS clients.
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
client-subnet-always-forward: yes
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:
@ -20,7 +19,7 @@ stub-zone:
stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
CONFIG_END
SCENARIO_BEGIN Test prefetch option for global cache
SCENARIO_BEGIN Test prefetch option for ECS cache
; K.ROOT-SERVERS.NET.
RANGE_BEGIN 0 100
@ -34,9 +33,6 @@ RANGE_BEGIN 0 100
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
@ -65,9 +61,6 @@ RANGE_BEGIN 0 100
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
@ -85,7 +78,7 @@ RANGE_BEGIN 0 100
RANGE_END
; ns.example.com.
RANGE_BEGIN 0 10
RANGE_BEGIN 0 100
ADDRESS 1.2.3.4
ENTRY_BEGIN
MATCH opcode qtype qname
@ -96,43 +89,6 @@ RANGE_BEGIN 0 10
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
@ -144,7 +100,7 @@ RANGE_BEGIN 11 100
SECTION QUESTION
www.example.com. IN A
SECTION ANSWER
www.example.com. IN A 10.20.30.40
www.example.com. 10 IN A 10.20.30.40
SECTION AUTHORITY
example.com. IN NS ns.example.com.
SECTION ADDITIONAL
@ -167,7 +123,7 @@ SECTION QUESTION
www.example.com. IN A
ENTRY_END
; This answer should be in the global cache
; This answer will end up in the subnet cache
STEP 2 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
@ -183,33 +139,53 @@ ns.example.com. IN A 1.2.3.4
ENTRY_END
; Try to trigger a prefetch
STEP 3 TIME_PASSES ELAPSE 11
STEP 3 TIME_PASSES ELAPSE 9
STEP 11 QUERY
STEP 4 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
; This record came from the cache and a prefetch is triggered
STEP 5 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
www.example.com. 1 IN A 10.20.30.40
SECTION AUTHORITY
example.com. 3589 IN NS ns.example.com.
example.com. 3591 IN NS ns.example.com.
SECTION ADDITIONAL
ns.example.com. 3589 IN A 1.2.3.4
ns.example.com. 3591 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
; Allow for some time to pass to differentiate from a cached vs resolved answer
STEP 6 TIME_PASSES ELAPSE 1
STEP 7 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
www.example.com. IN A
ENTRY_END
; This prefetched record came from the ECS cache
STEP 8 CHECK_ANSWER
ENTRY_BEGIN
MATCH all ttl
REPLY QR RD RA NOERROR
SECTION QUESTION
www.example.com. IN A
SECTION ANSWER
www.example.com. 9 IN A 10.20.30.40
SECTION AUTHORITY
example.com. 3599 IN NS ns.example.com.
SECTION ADDITIONAL
ns.example.com. 3599 IN A 1.2.3.4
ENTRY_END
SCENARIO_END

View file

@ -1195,7 +1195,7 @@ int inplace_cb_query_response_call(struct module_env* env,
}
struct edns_option* edns_opt_copy_region(struct edns_option* list,
struct regional* region)
struct regional* region)
{
struct edns_option* result = NULL, *cur = NULL, *s;
while(list) {
@ -1224,6 +1224,42 @@ struct edns_option* edns_opt_copy_region(struct edns_option* list,
return result;
}
struct edns_option* edns_opt_copy_filter_region(struct edns_option* list,
uint16_t* filter_list, size_t filter_list_len, struct regional* region)
{
struct edns_option* result = NULL, *cur = NULL, *s;
size_t i;
while(list) {
for(i=0; i<filter_list_len; i++)
if(filter_list[i] == list->opt_code) goto found;
if(i == filter_list_len) goto next;
found:
/* copy edns option structure */
s = regional_alloc_init(region, list, sizeof(*list));
if(!s) return NULL;
s->next = NULL;
/* copy option data */
if(s->opt_data) {
s->opt_data = regional_alloc_init(region, s->opt_data,
s->opt_len);
if(!s->opt_data)
return NULL;
}
/* link into list */
if(cur)
cur->next = s;
else result = s;
cur = s;
next:
/* examine next element */
list = list->next;
}
return result;
}
int edns_opt_compare(struct edns_option* p, struct edns_option* q)
{
if(!p && !q) return 0;

View file

@ -718,6 +718,12 @@ int inplace_cb_query_response_call(struct module_env* env,
struct edns_option* edns_opt_copy_region(struct edns_option* list,
struct regional* region);
/**
* Copy a filtered edns option list allocated to the new region
*/
struct edns_option* edns_opt_copy_filter_region(struct edns_option* list,
uint16_t* filter_list, size_t filter_list_len, struct regional* region);
/**
* Copy edns option list allocated with malloc
*/