]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib/cache: trim TTL of failing stashed packets
authorVladimír Čunát <vladimir.cunat@nic.cz>
Mon, 16 Mar 2026 10:00:22 +0000 (11:00 +0100)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Wed, 13 May 2026 10:38:54 +0000 (12:38 +0200)
In particular, in STUB mode
(i.e. forwarding with dnssec:false + authoritative:false)
if a SERVFAIL packet from upstream contained also records,
this packet could be cached with long TTL.

This issue was reported by Qifan Zhang from Palo Alto Networks.

Additionally, let's apply our TTL limits for caching also
to the `cache-control: max-age=` HTTP header sent in DoH replies.

(cherry picked from commit e4812f51e385334e051851c839b4a262d6224411)

NEWS
lib/cache/entry_pkt.c
modules/http/http_doh.test.lua
tests/config/doh2.test.lua

diff --git a/NEWS b/NEWS
index 3195ec947b9cff1af0fbf18419d513d29277dd6c..07a2a9093981fa7bedc719824b0db012bb28327f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -8,12 +8,14 @@ Improvements
 - packaging: rpm: added systemd's sysusers support (!1830)  
   This should also resolve the issue with user and group  
   configuration during installation (GH#130).
+- make DoH cache-control header respect our cache's TTL limits (!1832)
 
 
 Bugfixes
 --------
 - respect disablement of QNAME case randomization even after TCP issues
 - cache: fix wrong TTL in some cases, typically 32768
+- reduce excessive caching of some uncommon failed answers (!1832)
 
 
 Knot Resolver 5.7.6 (2025-07-17)
index aaac3fcc46e641a8d17a3e0e25658db92bfcb7ec..71af06a524998784536fbb1031c60c8f5db5934b 100644 (file)
 #include "lib/layer/iterate.h" /* kr_response_classify */
 #include "lib/cache/impl.h"
 
-
 /** Compute TTL for a packet.  It's minimum TTL or zero.  (You can apply limits.) */
-KR_EXPORT
-uint32_t packet_ttl(const knot_pkt_t *pkt)
+static uint32_t packet_ttl_simple(const knot_pkt_t *pkt)
 {
        bool has_ttl = false;
        uint32_t ttl = TTL_MAX_MAX;
@@ -34,6 +32,23 @@ uint32_t packet_ttl(const knot_pkt_t *pkt)
        }
        return has_ttl ? ttl : 0;
 }
+/** Compute TTL for a packet.  Cache limits do apply here.
+ *
+ * Also, failure packets (SERVFAIL in particular) have the minimal TTL.
+ * TODO: overall design around caching SERVFAIL-inducing situations. */
+KR_EXPORT
+uint32_t packet_ttl(const knot_pkt_t *pkt)
+{
+       struct kr_cache *cache = &the_resolver->cache;
+       switch (knot_wire_get_rcode(pkt->wire)) {
+       case KNOT_RCODE_NOERROR:
+       case KNOT_RCODE_NXDOMAIN:
+       case KNOT_RCODE_YXDOMAIN:
+               return MAX(MIN(packet_ttl_simple(pkt), cache->ttl_max), cache->ttl_min);
+       default:
+               return cache->ttl_min;
+       }
+}
 
 
 void stash_pkt(const knot_pkt_t *pkt, const struct kr_query *qry,
index f0685cb152acdcb50139ecc9dbe33086a33a1442..b81ff2dfcb3b70f7c33da1e5c76fc0b27c55eedd 100644 (file)
@@ -122,8 +122,8 @@ else
                if not (headers and pkt) then
                        return
                end
-               -- uncacheable
-               same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0')
+               -- failure but we limit the TTL
+               same(headers:get('cache-control'), 'max-age=5', desc .. ': TTL 5')
                same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches')
        end
 
@@ -215,8 +215,8 @@ else
                if not (headers and pkt) then
                        return
                end
-               -- uncacheable
-               same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0')
+               -- failure but we limit the TTL
+               same(headers:get('cache-control'), 'max-age=5', desc .. ': TTL 5')
                same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches')
        end
 
@@ -373,8 +373,8 @@ else
                if not (headers and pkt) then
                        return
                end
-               -- uncacheable
-               same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0')
+               -- failure but we limit the TTL
+               same(headers:get('cache-control'), 'max-age=5', desc .. ': TTL 5')
                same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches')
        end
 
index d178ecaaa58574aa9cd378cfd2fe4a777c7aedb7..503943e3f8e476eb950c45720d406347955a29aa 100644 (file)
@@ -115,8 +115,8 @@ else
                if not (headers and pkt) then
                        return
                end
-               -- uncacheable
-               same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0')
+               -- failure but we limit the TTL
+               same(headers:get('cache-control'), 'max-age=5', desc .. ': TTL 5')
                same(headers:get('access-control-allow-origin'), '*', desc .. ': CORS headers')
                same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches')
        end
@@ -252,8 +252,8 @@ else
                if not (headers and pkt) then
                        return
                end
-               -- uncacheable
-               same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0')
+               -- failure but we limit the TTL
+               same(headers:get('cache-control'), 'max-age=5', desc .. ': TTL 5')
                same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches')
        end