]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
Store responses received on listen interface in cache
authorWillem Toorop <willem@nlnetlabs.nl>
Sat, 15 Mar 2025 15:22:29 +0000 (16:22 +0100)
committerWillem Toorop <willem@nlnetlabs.nl>
Sat, 15 Mar 2025 15:22:29 +0000 (16:22 +0100)
daemon/worker.c

index ba5118a74fd692f6b1fa6982d40ed2bf0d7a7805..4f8d33caf2a5591a6dcdbe69d397da1ef29aabbd 100644 (file)
@@ -272,6 +272,11 @@ worker_handle_service_reply(struct comm_point* c, void* arg, int error,
        return 0;
 }
 
+#define REQUEST_OK        0
+#define DROP_REQUEST     -1
+#define RESPONSE_MESSAGE -2
+
+
 /** ratelimit error replies
  * @param worker: the worker struct with ratelimit counter
  * @param err: error code that would be wanted.
@@ -283,7 +288,7 @@ worker_err_ratelimit(struct worker* worker, int err)
        if(worker->err_limit_time == *worker->env.now) {
                /* see if limit is exceeded for this second */
                if(worker->err_limit_count++ > ERROR_RATELIMIT)
-                       return -1;
+                       return DROP_REQUEST;
        } else {
                /* new second, new limits */
                worker->err_limit_time = *worker->env.now;
@@ -296,6 +301,9 @@ worker_err_ratelimit(struct worker* worker, int err)
  * Structure holding the result of the worker_check_request function.
  * Based on configuration it could be called up to four times; ideally should
  * be called once.
+ * When value is a positive number, it countains the error to return.
+ * Otherwise DROP_REQUEST (-1) is returned, or RESPONSE_MESSAGE (-2) in
+ * case the qr bit was set. Value is set to REQUEST_OK (0) if all is good.
  */
 struct check_request_result {
        int checked;
@@ -314,18 +322,18 @@ worker_check_request(sldns_buffer* pkt, struct worker* worker,
        out->checked = 1;
        if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) {
                verbose(VERB_QUERY, "request too short, discarded");
-               out->value = -1;
+               out->value = DROP_REQUEST;
                return;
        }
        if(sldns_buffer_limit(pkt) > NORMAL_UDP_SIZE &&
                worker->daemon->cfg->harden_large_queries) {
                verbose(VERB_QUERY, "request too large, discarded");
-               out->value = -1;
+               out->value = DROP_REQUEST;
                return;
        }
        if(LDNS_QR_WIRE(sldns_buffer_begin(pkt))) {
-               verbose(VERB_QUERY, "request has QR bit on, discarded");
-               out->value = -1;
+               /* verbose(VERB_QUERY, "request has QR bit on, discarded"); */
+               out->value = RESPONSE_MESSAGE;
                return;
        }
        if(LDNS_TC_WIRE(sldns_buffer_begin(pkt))) {
@@ -367,10 +375,39 @@ worker_check_request(sldns_buffer* pkt, struct worker* worker,
                out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
                return;
        }
-       out->value = 0;
+       out->value = REQUEST_OK;
        return;
 }
 
+/** check response sanity.
+ * @param pkt: the wire packet to examine for sanity.
+ * @param worker: parameters for checking.
+ * @param out: 1 on success, otherwise 0.
+*/
+static int
+worker_check_response(sldns_buffer* pkt, struct worker* worker)
+{
+       if(LDNS_TC_WIRE(sldns_buffer_begin(pkt))) {
+               LDNS_TC_CLR(sldns_buffer_begin(pkt));
+               verbose(VERB_QUERY, "response bad, has TC bit on");
+               return 0;
+       }
+       if(LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY) {
+               verbose(VERB_QUERY, "not a query response");
+               return 0;
+       }
+       if(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1) {
+               verbose(VERB_QUERY, "request wrong nr qd=%d",
+                       LDNS_QDCOUNT(sldns_buffer_begin(pkt)));
+               return 0;
+       }
+       if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) == 0) {
+               verbose(VERB_QUERY, "response must be an answer message");
+               return 0;
+       }
+       return 1;
+}
+
 void
 worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg,
        size_t len, int error, void* arg)
@@ -1138,8 +1175,8 @@ deny_refuse(struct comm_point* c, enum acl_access acl,
                if(worker->stats.extended)
                        worker->stats.unwanted_queries++;
                worker_check_request(c->buffer, worker, check_result);
-               if(check_result->value != 0) {
-                       if(check_result->value != -1) {
+               if(check_result->value != REQUEST_OK) {
+                       if(check_result->value > 0) {
                                LDNS_QR_SET(sldns_buffer_begin(c->buffer));
                                LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
                                        check_result->value);
@@ -1416,7 +1453,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                char buf[LDNS_MAX_DOMAINLEN];
                /* Check if this is unencrypted and asking for certs */
                worker_check_request(c->buffer, worker, &check_result);
-               if(check_result.value != 0) {
+               if(check_result.value != REQUEST_OK) {
                        verbose(VERB_ALGO,
                                "dnscrypt: worker check request: bad query.");
                        log_addr(VERB_CLIENT,"from",&repinfo->client_addr,
@@ -1479,10 +1516,31 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
        }
 
        worker_check_request(c->buffer, worker, &check_result);
-       if(check_result.value != 0) {
+       if (check_result.value == RESPONSE_MESSAGE) {
+               struct reply_info *rep = NULL;
+               int r;
+
+               if (!worker_check_response(c->buffer, worker)) {
+                       log_err("Not a suitable response to cache");
+                       comm_point_drop_reply(repinfo);
+                       return 0;
+               }
+               if((r = reply_info_parse(c->buffer, worker->env.alloc, &qinfo,
+                               &rep, worker->scratchpad, &edns))) {
+                       log_err("bad response. Will not cache it (%d)", r);
+                       comm_point_drop_reply(repinfo);
+                       return 0;
+               }
+               dns_cache_store(&worker->env, &qinfo, rep, 0 /* is_referral */,
+                               0 /* leeway */, 0 /* pside */,
+                               NULL /* region */, 0 /* flags */,
+                               *worker->env.now, 0 /* is_valrec */);
+               comm_point_drop_reply(repinfo);
+               return 0;
+       } else if(check_result.value != REQUEST_OK) {
                verbose(VERB_ALGO, "worker check request: bad query.");
                log_addr(VERB_CLIENT,"from",&repinfo->client_addr, repinfo->client_addrlen);
-               if(check_result.value != -1) {
+               if(check_result.value > REQUEST_OK) {
                        LDNS_QR_SET(sldns_buffer_begin(c->buffer));
                        LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
                                check_result.value);