]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix edns subnet so that queries with a source prefix of zero cause
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Mon, 9 Oct 2023 10:21:22 +0000 (12:21 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Mon, 9 Oct 2023 10:21:22 +0000 (12:21 +0200)
  the recursor send no edns subnet option to the upstream.

doc/Changelog
edns-subnet/subnetmod.c
edns-subnet/subnetmod.h
testdata/subnet_prezero.crpl [new file with mode: 0644]

index f18f97374a9c436b7b80335459793ecc95210b35..62ce262d2fce07e64f7c66efe99c44616c8f2446 100644 (file)
@@ -1,3 +1,7 @@
+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.
index 13fd669b5d52250e0efec4a590a22c7081a11e1f..cefde84e5f4c96962399fe2abc8fedf12ca520d6 100644 (file)
@@ -156,6 +156,7 @@ int ecs_whitelist_check(struct query_info* qinfo,
                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, 
@@ -166,6 +167,14 @@ 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)) {
+                       /* 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);
                }
@@ -515,7 +524,7 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
        }
 
        /* 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 */
@@ -524,7 +533,7 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
        }
 
        /* 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
@@ -540,6 +549,18 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
                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. */
@@ -556,6 +577,7 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
                (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;
        }
 
@@ -676,6 +698,7 @@ ecs_query_response(struct module_qstate* qstate, struct dns_msg* response,
                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 &&
@@ -737,6 +760,9 @@ ecs_edns_back_parsed(struct module_qstate* qstate, int id,
                                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;
index f0bcaad33e155d3f7750cc87c7a101c83366bc40..1ff8a23ecdbaee80d1bbd4252f60c73b0c8fd4ec 100644 (file)
@@ -85,6 +85,13 @@ struct subnet_qstate {
        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;
diff --git a/testdata/subnet_prezero.crpl b/testdata/subnet_prezero.crpl
new file mode 100644 (file)
index 0000000..22cdfff
--- /dev/null
@@ -0,0 +1,155 @@
+; 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