]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add indicator for cache hit rules to know if hit a stale entry
authorOliver Chen <oliver.chen@nokia-sbell.com>
Fri, 25 Apr 2025 01:04:25 +0000 (01:04 +0000)
committerOliver Chen <oliver.chen@nokia-sbell.com>
Fri, 25 Apr 2025 01:05:04 +0000 (01:05 +0000)
pdns/dnsdistdist/dnsdist-cache.cc
pdns/dnsdistdist/dnsdist-idstate.hh
pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/docs/reference/dq.rst
pdns/dnsdistdist/test-dnsdist-lua-ffi.cc
regression-tests.dnsdist/test_CacheHitResponses.py

index 6d9e1fc3e1da4cc5649571ac973a5029ce738dd0..81b4748a4df866c82bab766496c8bb8aeca42e67 100644 (file)
@@ -307,6 +307,7 @@ bool DNSDistPacketCache::get(DNSQuestion& dnsQuestion, uint16_t queryId, uint32_
     }
     else {
       age = (value.validity - value.added) - d_settings.d_staleTTL;
+      dnsQuestion.ids.staleCacheHit = true;
     }
   }
 
index a8040291369768a5adbfbf11d3463adf51bca45c..a829d3589b4920840ee77f4bcf4896fd28b4093f 100644 (file)
@@ -176,6 +176,7 @@ struct InternalQueryState
   bool forwardedOverUDP{false};
   bool selfGenerated{false};
   bool cacheHit{false};
+  bool staleCacheHit{false};
 };
 
 struct IDState
index aebe104911205fbfdab6eefaf6890a68a5f10d73..676eaeb7753e58f84cb01b063611cf5c11d2623c 100644 (file)
@@ -661,5 +661,9 @@ void setupLuaBindingsDNSQuestion([[maybe_unused]] LuaContext& luaCtx)
   luaCtx.registerFunction<std::shared_ptr<DownstreamState> (DNSResponse::*)(void) const>("getSelectedBackend", [](const DNSResponse& dnsResponse) {
     return dnsResponse.d_downstream;
   });
+
+  luaCtx.registerFunction<bool (DNSResponse::*)()>("getStaleCacheHit", [](DNSResponse& dnsResponse) {
+    return dnsResponse.ids.staleCacheHit;
+  });
 #endif /* DISABLE_NON_FFI_DQ_BINDINGS */
 }
index 875a3aa73e002d358543c03b5fc8246814936480..42eb0747e0aa72380ce47b21d0ac10dd025ee00f 100644 (file)
@@ -162,6 +162,7 @@ void dnsdist_ffi_dnsresponse_limit_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t m
 void dnsdist_ffi_dnsresponse_set_max_returned_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t max) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsresponse_clear_records_type(dnsdist_ffi_dnsresponse_t* dr, uint16_t qtype) __attribute__ ((visibility ("default")));
 bool dnsdist_ffi_dnsresponse_rebase(dnsdist_ffi_dnsresponse_t* dr, const char* initialName, size_t initialNameSize) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnsresponse_get_stale_cache_hit(const dnsdist_ffi_dnsresponse_t* dr) __attribute__ ((visibility ("default")));
 
 bool dnsdist_ffi_dnsquestion_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) __attribute__ ((visibility ("default")));
 bool dnsdist_ffi_dnsresponse_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) __attribute__ ((visibility ("default")));
index a63c088d12c260456ef081615159d443a6c22a76..d5ed9f8ec09f9916cfb166f05865c16aa4df7564 100644 (file)
@@ -840,6 +840,11 @@ bool dnsdist_ffi_dnsresponse_rebase(dnsdist_ffi_dnsresponse_t* dr, const char* i
   return true;
 }
 
+bool dnsdist_ffi_dnsresponse_get_stale_cache_hit(const dnsdist_ffi_dnsresponse_t* dr)
+{
+  return dr->dr->ids.staleCacheHit;
+}
+
 bool dnsdist_ffi_dnsquestion_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
 {
   try {
index 65337d7012b3670e575ea7bd01db24a10d20f731..8364f590bc86984695898babb3c4fe3c39f5d629 100644 (file)
@@ -438,12 +438,18 @@ DNSResponse object
   If the value is really needed while the response is being processed, it is possible to set a tag while the query is processed, as tags will be passed to the response object.
   It also has additional methods:
 
-  .. method:: DNSResponse.getSelectedBackend() -> Server
+  .. method:: DNSResponse:getSelectedBackend() -> Server
 
     .. versionadded:: 1.9.0
 
     Get the selected backend :class:`Server` or nil
 
+  .. method:: DNSResponse:getStaleCacheHit() -> bool
+
+    .. versionadded:: 2.0.0
+
+    Get the indicator of whether the cache lookup hit a stale entry.
+
   .. method:: DNSResponse:editTTLs(func)
 
     The function ``func`` is invoked for every entry in the answer, authority and additional section.
index bb544f0dc37eebe59cd4a75f7213eeacfd6b3775..7504f0787c3d230510323d41c95099ca5fdf4c8c 100644 (file)
@@ -445,6 +445,10 @@ BOOST_AUTO_TEST_CASE(test_Response)
     dnsdist_ffi_dnsresponse_clear_records_type(nullptr, QType::A);
     dnsdist_ffi_dnsresponse_clear_records_type(&lightDR, QType::A);
   }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsresponse_get_stale_cache_hit(&lightDR), false);
+  }
 }
 
 BOOST_AUTO_TEST_CASE(test_Server)
index f74e8b7723aa396d5f24bb4d1bf79ed5d13ddc8b..fc7425ed7c53e3d6ed51bea69d9b36a0fa370978 100644 (file)
@@ -87,3 +87,55 @@ class TestCacheHitResponses(DNSDistTest):
             TestCacheHitResponses._responsesCounter[key] = 0
 
         self.assertEqual(total, 2)
+
+class TestStaleCacheHitResponses(DNSDistTest):
+
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
+    _config_template = """
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
+    getPool(""):setCache(pc)
+    setStaleCacheEntriesTTL(600)
+    setKey("%s")
+    controlSocket("127.0.0.1:%d")
+    newServer{address="127.0.0.1:%s"}
+    function hitCache(dr) if dr:getStaleCacheHit() then return DNSResponseAction.Drop end return DNSResponseAction.None end
+    addCacheHitResponseAction(SuffixMatchNodeRule("dropstaleentry.cachehitresponses.tests.powerdns.com."), LuaResponseAction(hitCache))
+    """
+
+    def testDroppedWhenStaleCached(self):
+        """
+        CacheHitResponse: Drop when served from the stale cache entry
+        """
+        ttl = 5
+        name = 'dropstaleentry.cachehitresponses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        # first query to fill the cache
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+        # mark server down
+        self.sendConsoleCommand("getServer(0):setDown()")
+
+        # next query should hit the cache within ttl
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, response)
+
+        time.sleep(ttl + 1)
+
+        # further query should hit stale cache thus dropped
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, None)