summaryrefslogtreecommitdiff
path: root/edns-subnet/subnetmod.c
diff options
context:
space:
mode:
authorDag-Erling Smørgrav <des@FreeBSD.org>2026-05-22 14:08:07 +0000
committerDag-Erling Smørgrav <des@FreeBSD.org>2026-05-22 14:08:07 +0000
commit4dd0a17edce60370304a45f2c40251e09e193bd6 (patch)
treef0cb0f27a9e83b56f8383265413d035ab01926c6 /edns-subnet/subnetmod.c
parentec5b94f552d7cb2a9d456c67e9941bcf5e3698bf (diff)
Diffstat (limited to 'edns-subnet/subnetmod.c')
-rw-r--r--edns-subnet/subnetmod.c108
1 files changed, 101 insertions, 7 deletions
diff --git a/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c
index 88310a785d7a..587839faa6df 100644
--- a/edns-subnet/subnetmod.c
+++ b/edns-subnet/subnetmod.c
@@ -70,6 +70,7 @@ subnet_data_delete(void *d, void *ATTR_UNUSED(arg))
r = (struct subnet_msg_cache_data*)d;
addrtree_delete(r->tree4);
addrtree_delete(r->tree6);
+ free(r->reason_fail);
free(r);
}
@@ -84,6 +85,8 @@ msg_cache_sizefunc(void *k, void *d)
+ q->key.qname_len + lock_get_mem(&q->entry.lock);
s += addrtree_size(r->tree4);
s += addrtree_size(r->tree6);
+ if(r->reason_fail)
+ s += strlen(r->reason_fail)+1;
return s;
}
@@ -162,8 +165,15 @@ int ecs_whitelist_check(struct query_info* qinfo,
if(!ecs_is_whitelisted(sn_env->whitelist,
addr, addrlen, qinfo->qname, qinfo->qname_len,
qinfo->qclass)) {
- verbose(VERB_ALGO, "subnet store subquery global, name and addr have no subnet treatment.");
- qstate->no_cache_store = 0;
+ /* The stub or forward can have no_cache set.*/
+ if(iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL, NULL, 0)) {
+ verbose(VERB_ALGO, "subnet subquery is not stored globally, stuborfwd is no_cache");
+ } else {
+ verbose(VERB_ALGO, "subnet store subquery global, name and addr have no subnet treatment.%s",
+ (sq->started_no_cache_store?
+ " But the subnet module was started with no_cache_store for the super query, and that is still applied to this query":""));
+ qstate->no_cache_store = sq->started_no_cache_store;
+ }
}
}
return 1;
@@ -193,12 +203,18 @@ int ecs_whitelist_check(struct query_info* qinfo,
if(sq->ecs_server_out.subnet_source_mask == 0) {
sq->subnet_sent_no_subnet = 1;
sq->subnet_sent = 0;
+ /* The result should end up in subnet cache,
+ * not in global cache. */
+ qstate->no_cache_store = 1;
return 1;
}
subnet_ecs_opt_list_append(&sq->ecs_server_out,
&qstate->edns_opts_back_out, qstate, region);
}
sq->subnet_sent = 1;
+ /* Do not store servfails in global cache, since the subnet
+ * option is sent out. */
+ qstate->no_cache_store = 1;
}
else {
/* Outgoing ECS option is set, but we don't want to sent it to
@@ -420,6 +436,35 @@ update_cache(struct module_qstate *qstate, int id)
}
/* lru_entry->lock is locked regardless of how we got here,
* either from the slabhash_lookup, or above in the new allocated */
+ if(!qstate->return_msg && qstate->error_response_cache) {
+ struct subnet_msg_cache_data *data =
+ (struct subnet_msg_cache_data*)lru_entry->data;
+ data->ttl_servfail = *qstate->env->now + NORR_TTL;
+ data->ede_fail = errinf_to_reason_bogus(qstate);
+ diff_size = (data->reason_fail?strlen(data->reason_fail)+1:0);
+ if(qstate->errinf) {
+ char* str = errinf_to_str_misc(qstate);
+ free(data->reason_fail);
+ data->reason_fail = NULL;
+ if(str)
+ data->reason_fail = strdup(str);
+ }
+ diff_size = (data->reason_fail?strlen(data->reason_fail)+1:0)
+ - diff_size;
+ lock_rw_unlock(&lru_entry->lock);
+ if (need_to_insert) {
+ slabhash_insert(subnet_msg_cache, h, lru_entry,
+ lru_entry->data, NULL);
+ } else {
+ slabhash_update_space_used(subnet_msg_cache, h, NULL,
+ diff_size);
+ }
+ return;
+ }
+ if(!qstate->return_msg) {
+ lock_rw_unlock(&lru_entry->lock);
+ return;
+ }
/* Step 2, find the correct tree */
if (!(tree = get_tree(lru_entry->data, edns, sne, qstate->env->cfg))) {
lock_rw_unlock(&lru_entry->lock);
@@ -463,6 +508,21 @@ update_cache(struct module_qstate *qstate, int id)
}
}
+/** See if there is a stored servfail, returns true if so, and sets reply. */
+static int
+lookup_check_servfail(struct module_qstate *qstate,
+ struct subnet_msg_cache_data *data)
+{
+ struct module_env *env = qstate->env;
+ if(!data)
+ return 0;
+ if(!data->ttl_servfail || TTL_IS_EXPIRED(data->ttl_servfail, *env->now))
+ return 0;
+ qstate->return_rcode = LDNS_RCODE_SERVFAIL;
+ errinf_ede(qstate, data->reason_fail, data->ede_fail);
+ return 1;
+}
+
/** Lookup in cache and reply true iff reply is sent. */
static int
lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq, int prefetch)
@@ -476,6 +536,8 @@ lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq,
struct addrtree *tree;
struct addrnode *node;
uint8_t scope;
+ int must_validate = (!(qstate->query_flags&BIT_CD)
+ || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate;
memset(&sq->ecs_client_out, 0, sizeof(sq->ecs_client_out));
@@ -489,12 +551,20 @@ lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq,
tree = (ecs->subnet_addr_fam == EDNSSUBNET_ADDRFAM_IP4)?
data->tree4 : data->tree6;
if (!tree) { /* qinfo in cache but not for this family */
+ if(lookup_check_servfail(qstate, data)) {
+ lock_rw_unlock(&e->lock);
+ return 1;
+ }
lock_rw_unlock(&e->lock);
return 0;
}
node = addrtree_find(tree, (addrkey_t*)ecs->subnet_addr,
ecs->subnet_source_mask, *env->now);
if (!node) { /* plain old cache miss */
+ if(lookup_check_servfail(qstate, data)) {
+ lock_rw_unlock(&e->lock);
+ return 1;
+ }
lock_rw_unlock(&e->lock);
return 0;
}
@@ -503,12 +573,24 @@ lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq,
(struct reply_info *)node->elem, qstate->region, *env->now, 0,
env->scratch);
scope = (uint8_t)node->scope;
- lock_rw_unlock(&e->lock);
if (!qstate->return_msg) { /* Failed allocation or expired TTL */
+ if(lookup_check_servfail(qstate, data)) {
+ lock_rw_unlock(&e->lock);
+ return 1;
+ }
+ lock_rw_unlock(&e->lock);
return 0;
}
-
+ lock_rw_unlock(&e->lock);
+ if(qstate->return_msg->rep->security == sec_status_unchecked
+ && must_validate) {
+ /* The message has to be validated first. */
+ verbose(VERB_ALGO, "subnet: unchecked cache entry needs "
+ "validation");
+ return 0;
+ }
+
if (sq->subnet_downstream) { /* relay to interested client */
sq->ecs_client_out.subnet_scope_mask = scope;
sq->ecs_client_out.subnet_addr_fam = ecs->subnet_addr_fam;
@@ -563,12 +645,15 @@ generate_sub_request(struct module_qstate *qstate, int id, struct subnet_qstate*
qflags |= BIT_RD;
if((qstate->query_flags & BIT_CD)!=0) {
qflags |= BIT_CD;
- valrec = 1;
+ /* The valrec is left off. Leave out: valrec = 1;
+ * So that the cache is protected with DNSSEC validation.
+ * Just like the global cache. DNSSEC validation is performed
+ * regardless of the setting of the querier's CD flag. */
}
fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub));
- if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime, valrec,
- &subq)) {
+ if(!(*qstate->env->attach_sub)(qstate, &qinf, qstate->client_info,
+ qflags, prime, valrec, &subq)) {
return 0;
}
if(subq) {
@@ -580,6 +665,7 @@ generate_sub_request(struct module_qstate *qstate, int id, struct subnet_qstate*
}
subsq = (struct subnet_qstate*)subq->minfo[id];
subsq->is_subquery_nonsubnet = 1;
+ subsq->started_no_cache_store = sq->started_no_cache_store;
/* When the client asks 0.0.0.0/0 and the name is not treated
* as subnet, it is to be stored in the global cache.
@@ -632,6 +718,12 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
/* already an answer and its not a message, but retain
* the actual rcode, instead of module_error, so send
* module_finished */
+ if(qstate->error_response_cache) {
+ verbose(VERB_ALGO, "subnet: store error response");
+ lock_rw_wrlock(&sne->biglock);
+ update_cache(qstate, id);
+ lock_rw_unlock(&sne->biglock);
+ }
return module_finished;
}
@@ -881,9 +973,11 @@ ecs_edns_back_parsed(struct module_qstate* qstate, int id,
sq->max_scope = sq->ecs_server_in.subnet_scope_mask;
} else if(sq->subnet_sent_no_subnet) {
/* The answer can be stored as scope 0, not in global cache. */
+ /* This was already set in ecs_whitelist_check */
qstate->no_cache_store = 1;
} else if(sq->subnet_sent) {
/* Need another query to be able to store in global cache. */
+ /* This was already set in ecs_whitelist_check */
qstate->no_cache_store = 1;
}