]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Cache cleaning, make the aggressive nsec cache size configurable
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 7 Jan 2021 15:21:34 +0000 (16:21 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 22 Feb 2021 17:43:07 +0000 (18:43 +0100)
pdns/pdns_recursor.cc
pdns/recursordist/aggressive_nsec.cc
pdns/recursordist/aggressive_nsec.hh
pdns/recursordist/docs/settings.rst
pdns/recursordist/test-aggressive_nsec_cc.cc

index 125cfebc7a82c9e9a61d5d9fa330b95beb00fa31..a0b238e620189c514353af1a495e143c69aaabde 100644 (file)
@@ -3467,11 +3467,12 @@ static void doStats(void)
 
 static void houseKeeping(void *)
 {
-  static thread_local time_t last_rootupdate, last_secpoll, last_trustAnchorUpdate{0}, last_RC_prune;
+  static thread_local time_t last_rootupdate, last_secpoll, last_trustAnchorUpdate{0};
   static thread_local struct timeval last_prune;
 
   static thread_local int cleanCounter=0;
   static thread_local bool s_running;  // houseKeeping can get suspended in secpoll, and be restarted, which makes us do duplicate work
+  static time_t last_RC_prune = 0;
   auto luaconfsLocal = g_luaconfs.getLocal();
 
   if (last_trustAnchorUpdate == 0 && !luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0) {
@@ -3512,6 +3513,9 @@ static void houseKeeping(void *)
       if (now.tv_sec - last_RC_prune > 5) {
         g_recCache->doPrune(g_maxCacheEntries);
         g_negCache->prune(g_maxCacheEntries / 10);
+        if (g_aggressiveNSECCache) {
+          g_aggressiveNSECCache->prune();
+        }
         last_RC_prune = now.tv_sec;
       }
       // XXX !!! global
@@ -4741,9 +4745,9 @@ static int serviceMain(int argc, char*argv[])
 
   s_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
 
-  if (::arg().mustDo("aggressive-nsec")) {
+  if (::arg().asNum("aggressive-nsec-cache-size") > 0) {
     if (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog) {
-      g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+      g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(::arg().asNum("aggressive-nsec-cache-size"));
     }
     else {
       g_log<<Logger::Warning<<"Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate' or 'log-fail', ignoring"<<endl;
@@ -5516,7 +5520,7 @@ int main(int argc, char **argv)
 
     ::arg().setSwitch("extended-resolution-errors", "If set, send an EDNS Extended Error extension on resolution failures, like DNSSEC validation errors")="no";
 
-    ::arg().setSwitch("aggressive-nsec", "If set, and DNSSEC validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198")="no";
+    ::arg().setSwitch("aggressive-nsec-cache-size", "The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198")="0";
 
     ::arg().setCmd("help","Provide a helpful message");
     ::arg().setCmd("version","Print version string");
index fdac6802ebb4996de99c68ef1e646f218bea8a7b..dee2d5d0ea8aa9a74a0782125208b7fe523c3571 100644 (file)
@@ -76,7 +76,9 @@ void AggressiveNSECCache::updateEntriesCount()
   /* need to be called while holding a write lock */
   uint64_t counter = 0;
   d_zones.visit([&counter](const SuffixMatchTree<std::shared_ptr<ZoneEntry>>& node) {
-    counter += node.d_value->d_entries.size();
+    if (node.d_value) {
+      counter += node.d_value->d_entries.size();
+    }
   });
   d_entriesCount = counter;
 }
@@ -101,6 +103,84 @@ void AggressiveNSECCache::removeZoneInfo(const DNSName& zone, bool subzones)
   }
 }
 
+void AggressiveNSECCache::prune()
+{
+  uint64_t maxNumberOfEntries = d_maxEntries;
+  time_t now = time(nullptr);
+  std::vector<DNSName> emptyEntries;
+
+  uint64_t erased = 0;
+  uint64_t lookedAt = 0;
+  uint64_t toLook = d_entriesCount / 10;
+
+  if (d_entriesCount > maxNumberOfEntries) {
+    uint64_t toErase = d_entriesCount - maxNumberOfEntries;
+    toLook = toErase * 5;
+    // we are full, scan at max 5 * toErase entries and stop once we have nuked enough
+
+    WriteLock rl(d_lock);
+    d_zones.visit([now, &erased, toErase, toLook, &lookedAt, &emptyEntries](const SuffixMatchTree<std::shared_ptr<ZoneEntry>>& node) {
+      if (!node.d_value || erased > toErase || lookedAt > toLook) {
+        return;
+      }
+
+      {
+        std::lock_guard<std::mutex> lock(node.d_value->d_lock);
+        auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(node.d_value->d_entries);
+        for (auto it = sidx.begin(); it != sidx.end(); ++lookedAt) {
+          if (it->d_ttd < now) {
+            it = sidx.erase(it);
+            ++erased;
+          }
+          else {
+            ++it;
+          }
+          if (erased > toErase || lookedAt > toLook) {
+            break;
+          }
+        }
+      }
+
+      if (node.d_value->d_entries.size() == 0) {
+        emptyEntries.push_back(node.d_value->d_zone);
+      }
+    });
+  }
+  else {
+    // we are not full, just look through 10% of the cache and nuke everything that is expired
+    WriteLock rl(d_lock);
+
+    d_zones.visit([now, &erased, toLook, &lookedAt, &emptyEntries](const SuffixMatchTree<std::shared_ptr<ZoneEntry>>& node) {
+      if (!node.d_value) {
+        return;
+      }
+
+      {
+        std::lock_guard<std::mutex> lock(node.d_value->d_lock);
+
+        auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(node.d_value->d_entries);
+        for (auto it = sidx.begin(); it != sidx.end(); ++lookedAt) {
+          if (it->d_ttd < now || lookedAt > toLook) {
+            it = sidx.erase(it);
+            ++erased;
+          }
+          else {
+            ++it;
+          }
+          if (lookedAt > toLook) {
+            break;
+          }
+        }
+      }
+
+      if (node.d_value->d_entries.size() == 0) {
+        emptyEntries.push_back(node.d_value->d_zone);
+      }
+    });
+  }
+
+}
+
 static bool isMinimallyCoveringNSEC(const DNSName& owner, const std::shared_ptr<NSECRecordContent>& nsec)
 {
   /* this test only covers Cloudflare's ones (https://blog.cloudflare.com/black-lies/),
index 132a53a4b342ac7eeb9c9483800f53aafbe64dfb..69b63eb2cf3c7b9c54bd7483b2201956d4c8f9e0 100644 (file)
 class AggressiveNSECCache
 {
 public:
+  AggressiveNSECCache(uint64_t entries): d_maxEntries(entries)
+  {
+  }
+
   void insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<RRSIGRecordContent>>& signatures, bool nsec3);
   bool getDenial(time_t, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC);
 
@@ -68,6 +72,8 @@ public:
     return d_nsec3WildcardHits;
   }
 
+  void prune();
+
 private:
 
   struct ZoneEntry
@@ -134,7 +140,7 @@ private:
   std::atomic<uint64_t> d_nsecWildcardHits{0};
   std::atomic<uint64_t> d_nsec3WildcardHits{0};
   std::atomic<uint64_t> d_entriesCount{0};
+  uint64_t d_maxEntries{0};
 };
 
-
 extern std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache;
index 39dfc9abe29880a5482bcc7a35c69e075c5ad8d8..63c65f68928163d28c30b6e84448987a4dec3899 100644 (file)
@@ -21,16 +21,16 @@ variable to act as base setting. This is mostly useful for
   forward-zones += bar.example.com=[1234::abcde]:5353;
 
 
-.. _setting-aggressive-nsec:
+.. _setting-aggressive-nsec-cache-size:
 
-``aggressive-nsec``
--------------------
+``aggressive-nsec-cache-size``
+------------------------------
 .. versionadded:: 4.5.0
 
--  Boolean
--  Default: no
+-  Integer
+-  Default: 0
 
-If set, and DNSSEC validation is enabled, the recursor cache NSEC and NSEC3 records to generate negative answers, and use cached wildcards to synthesize positive answsers, as defined in :rfc:`8198`.
+The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in :rfc:`8198`.
 This setting requires DNSSEC validation to be enabled via the `dnssec_` setting.
 
 .. _setting-allow-from:
index a355b7e7a4b60b031513b60ebc5c20c6f8cac872..c3f1360ce1e2c3186b8d834ce0d2f9ca2e380a00 100644 (file)
@@ -10,7 +10,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
-  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
@@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
-  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
@@ -194,7 +194,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata_wildcard)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
-  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
@@ -285,7 +285,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
-  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
@@ -361,7 +361,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis)
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(ret.at(0).d_name, target);
-  BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);  
+  BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 
   ret.clear();
@@ -370,7 +370,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis)
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(ret.at(0).d_name, DNSName("b.powerdns.com."));
-  BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);  
+  BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
 
@@ -378,7 +378,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
-  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
@@ -471,7 +471,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
-  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
@@ -557,7 +557,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata_wildcard)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
-  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
@@ -660,7 +660,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
-  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+  g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
@@ -744,7 +744,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis)
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(ret.at(0).d_name, target);
-  BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);  
+  BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 
   ret.clear();
@@ -753,7 +753,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis)
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(ret.at(0).d_name, DNSName("b.powerdns.com."));
-  BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);  
+  BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 }