the recursor send no edns subnet option to the upstream.
+9 October 2023: Wouter
+ - Fix edns subnet so that queries with a source prefix of zero cause
+ the recursor send no edns subnet option to the upstream.
+
4 October 2023: Wouter
- Fix #946: Forwarder returns servfail on upstream response noerror no
data.
qstate->no_cache_store = 0;
}
+ sq->subnet_sent_no_subnet = 0;
if(sq->ecs_server_out.subnet_validdata && ((sq->subnet_downstream &&
qstate->env->cfg->client_subnet_always_forward) ||
ecs_is_whitelisted(sn_env->whitelist,
* set. */
if(!edns_opt_list_find(qstate->edns_opts_back_out,
qstate->env->cfg->client_subnet_opcode)) {
+ /* if the client is not wanting an EDNS subnet option,
+ * omit it and store that we omitted it but actually
+ * are doing EDNS subnet to the server. */
+ if(sq->ecs_server_out.subnet_source_mask == 0) {
+ sq->subnet_sent_no_subnet = 1;
+ sq->subnet_sent = 0;
+ return 1;
+ }
subnet_ecs_opt_list_append(&sq->ecs_server_out,
&qstate->edns_opts_back_out, qstate, region);
}
}
/* We have not asked for subnet data */
- if (!sq->subnet_sent) {
+ if (!sq->subnet_sent && !sq->subnet_sent_no_subnet) {
if (s_in->subnet_validdata)
verbose(VERB_QUERY, "subnetcache: received spurious data");
if (sq->subnet_downstream) /* Copy back to client */
}
/* subnet sent but nothing came back */
- if (!s_in->subnet_validdata) {
+ if (!s_in->subnet_validdata && !sq->subnet_sent_no_subnet) {
/* The authority indicated no support for edns subnet. As a
* consequence the answer ended up in the regular cache. It
* is still useful to put it in the edns subnet cache for
return module_finished;
}
+ /* Purposefully there was no sent subnet, and there is consequently
+ * no subnet in the answer. If there was, use the subnet in the answer
+ * anyway. But if there is not, treat it as a prefix 0 answer. */
+ if(sq->subnet_sent_no_subnet && !s_in->subnet_validdata) {
+ /* Fill in 0.0.0.0/0 scope 0, or ::0/0 scope 0, for caching. */
+ s_in->subnet_addr_fam = s_out->subnet_addr_fam;
+ s_in->subnet_source_mask = 0;
+ s_in->subnet_scope_mask = 0;
+ memset(s_in->subnet_addr, 0, INET6_SIZE);
+ s_in->subnet_validdata = 1;
+ }
+
/* Being here means we have asked for and got a subnet specific
* answer. Also, the answer from the authority is not yet cached
* anywhere. */
(void)edns_opt_list_remove(&qstate->edns_opts_back_out,
qstate->env->cfg->client_subnet_opcode);
sq->subnet_sent = 0;
+ sq->subnet_sent_no_subnet = 0;
return module_restart_next;
}
edns_opt_list_remove(&qstate->edns_opts_back_out,
qstate->env->cfg->client_subnet_opcode);
sq->subnet_sent = 0;
+ sq->subnet_sent_no_subnet = 0;
memset(&sq->ecs_server_out, 0, sizeof(sq->ecs_server_out));
} else if (!sq->track_max_scope &&
FLAGS_GET_RCODE(response->rep->flags) == LDNS_RCODE_NOERROR &&
sq->ecs_server_in.subnet_scope_mask >
sq->max_scope))
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. */
+ qstate->no_cache_store = 1;
}
return 1;
struct ecs_data ecs_server_out;
int subnet_downstream;
int subnet_sent;
+ /**
+ * If there was no subnet sent because the client used source prefix
+ * length 0 for omitting the information. Then the answer is cached
+ * like subnet was a /0 scope. Like the subnet_sent flag, but when
+ * the EDNS subnet option is omitted because the client asked.
+ */
+ int subnet_sent_no_subnet;
/** keep track of longest received scope, set after receiving CNAME for
* incoming QNAME. */
int track_max_scope;
--- /dev/null
+; subnet unit test
+server:
+ trust-anchor-signaling: no
+ send-client-subnet: 1.2.3.4
+ send-client-subnet: 1.2.3.5
+ target-fetch-policy: "0 0 0 0 0"
+ module-config: "subnetcache validator iterator"
+ qname-minimisation: no
+ minimal-responses: no
+
+stub-zone:
+ name: "example.com"
+ stub-addr: 1.2.3.4
+CONFIG_END
+
+SCENARIO_BEGIN Test subnetcache source prefix zero from client.
+; In RFC7871 section-7.1.2 (para. 2).
+; It says that the recursor must send no EDNS subnet or its own address
+; in the EDNS subnet to the upstream server. And use that answer for the
+; source prefix length zero query. That type of query is for privacy.
+; The authority server is then going to use the resolver's IP, if any, to
+; tailor the answer to the query source address.
+
+; ns.example.com
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.4
+
+; reply with 0.0.0.0/0 in reply
+; For the test the answers for 0.0.0.0/0 queries are SERVFAIL, the normal
+; answers are NOERROR.
+ENTRY_BEGIN
+MATCH opcode qtype qname ednsdata
+ADJUST copy_id
+REPLY QR AA DO SERVFAIL
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN CNAME star.c10r.example.com.
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+ 00 08 00 04 ; OPCODE=subnet, optlen
+ 00 01 00 00 ; ip4, scope 0, source 0
+ ; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+
+; reply without subnet
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA DO NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN CNAME star.c10r.example.com.
+ENTRY_END
+
+; delegation answer for c10r.example.com, with subnet /0
+ENTRY_BEGIN
+MATCH opcode subdomain ednsdata
+ADJUST copy_id copy_query
+REPLY QR DO SERVFAIL
+SECTION QUESTION
+c10r.example.com. IN NS
+SECTION AUTHORITY
+c10r.example.com. IN NS ns.c10r.example.com.
+SECTION ADDITIONAL
+ns.c10r.example.com. IN A 1.2.3.5
+HEX_EDNSDATA_BEGIN
+ 00 08 00 04 ; OPCODE=subnet, optlen
+ 00 01 00 00 ; ip4, scope 0, source 0
+ ; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+
+; delegation answer for c10r.example.com, without subnet
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR DO NOERROR
+SECTION QUESTION
+c10r.example.com. IN NS
+SECTION AUTHORITY
+c10r.example.com. IN NS ns.c10r.example.com.
+SECTION ADDITIONAL
+ns.c10r.example.com. IN A 1.2.3.5
+ENTRY_END
+RANGE_END
+
+; ns.c10r.example.com
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.5
+
+; reply with 0.0.0.0/0 in reply
+ENTRY_BEGIN
+MATCH opcode qtype qname ednsdata
+ADJUST copy_id
+REPLY QR AA DO SERVFAIL
+SECTION QUESTION
+star.c10r.example.com. IN A
+SECTION ANSWER
+star.c10r.example.com. IN A 1.2.3.6
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+ 00 08 00 04 ; OPCODE=subnet, optlen
+ 00 01 00 00 ; ip4, scope 0, source 0
+ ; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+
+; reply without subnet
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA DO NOERROR
+SECTION QUESTION
+star.c10r.example.com. IN A
+SECTION ANSWER
+star.c10r.example.com. IN A 1.2.3.6
+ENTRY_END
+RANGE_END
+
+; ask for www.example.com
+; server answers with CNAME to a delegation, that then
+; returns a /24 answer.
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+www.example.com. IN A
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+ 00 08 00 04 ; OPCODE=subnet, optlen
+ 00 01 00 00 ; ip4, scope 0, source 0
+ ; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ednsdata
+REPLY QR RD RA DO NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN CNAME star.c10r.example.com.
+star.c10r.example.com. IN A 1.2.3.6
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+ 00 08 00 04 ; OPCODE=subnet, optlen
+ 00 01 00 00 ; ip4, scope 0, source 0
+ ; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+SCENARIO_END