]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Fix and add unit tests for Aggressive NSEC cache cleaning, dump, wiping
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 8 Jan 2021 15:58:00 +0000 (16:58 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 22 Feb 2021 17:44:06 +0000 (18:44 +0100)
pdns/pdns_recursor.cc
pdns/rec_channel_rec.cc
pdns/recursordist/aggressive_nsec.cc
pdns/recursordist/aggressive_nsec.hh
pdns/recursordist/test-aggressive_nsec_cc.cc
pdns/recursordist/test-syncres_cc.cc

index a0b238e620189c514353af1a495e143c69aaabde..c0f713f4914d2a9e8e8798339824a5155830e86a 100644 (file)
@@ -3514,7 +3514,7 @@ static void houseKeeping(void *)
         g_recCache->doPrune(g_maxCacheEntries);
         g_negCache->prune(g_maxCacheEntries / 10);
         if (g_aggressiveNSECCache) {
-          g_aggressiveNSECCache->prune();
+          g_aggressiveNSECCache->prune(now.tv_sec);
         }
         last_RC_prune = now.tv_sec;
       }
index ca92008537870bc9095899c7a320c2cd2110a996..5a78673f6ceb51c616c9ebeab312eef55b1503fa 100644 (file)
@@ -301,6 +301,27 @@ static uint64_t dumpNegCache(int fd)
   return g_negCache->dumpToFile(fp.get(), now);
 }
 
+static uint64_t dumpAggressiveNSECCache(int fd)
+{
+  if (!g_aggressiveNSECCache) {
+    return 0;
+  }
+
+  int newfd = dup(fd);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!fp) {
+    return 0;
+  }
+  fprintf(fp.get(), "; aggressive NSEC cache dump follows\n;\n");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, nullptr);
+  return g_aggressiveNSECCache->dumpToFile(fp, now);
+}
+
 static uint64_t* pleaseDump(int fd)
 {
   return new uint64_t(t_packetCache->doDump(fd));
@@ -368,7 +389,7 @@ static RecursorControlChannel::Answer doDumpCache(int s)
   uint64_t total = 0;
   try {
     int fd = fdw;
-    total = g_recCache->doDump(fd) + dumpNegCache(fd) + broadcastAccFunction<uint64_t>([fd]{ return pleaseDump(fd); });
+    total = g_recCache->doDump(fd) + dumpNegCache(fd) + broadcastAccFunction<uint64_t>([fd]{ return pleaseDump(fd); }) + dumpAggressiveNSECCache(fd);
   }
   catch(...){}
 
index dee2d5d0ea8aa9a74a0782125208b7fe523c3571..b35038cd479f7e06a6df0b046a2e390d07ef7cf6 100644 (file)
@@ -19,6 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include <cinttypes>
 
 #include "aggressive_nsec.hh"
 #include "cachecleaner.hh"
@@ -86,16 +87,17 @@ void AggressiveNSECCache::updateEntriesCount()
 void AggressiveNSECCache::removeZoneInfo(const DNSName& zone, bool subzones)
 {
   WriteLock rl(d_lock);
-  auto got = d_zones.lookup(zone);
-  if (!got || !*got || (*got)->d_zone != zone) {
-    return;
-  }
 
   if (subzones) {
     d_zones.remove(zone, true);
     updateEntriesCount();
   }
   else {
+    auto got = d_zones.lookup(zone);
+    if (!got || !*got || (*got)->d_zone != zone) {
+      return;
+    }
+
     std::lock_guard<std::mutex> lock((*got)->d_lock);
     auto removed = (*got)->d_entries.size();
     d_zones.remove(zone, false);
@@ -103,15 +105,14 @@ void AggressiveNSECCache::removeZoneInfo(const DNSName& zone, bool subzones)
   }
 }
 
-void AggressiveNSECCache::prune()
+void AggressiveNSECCache::prune(time_t now)
 {
   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;
+  uint64_t toLook = std::max(d_entriesCount / 5U, static_cast<uint64_t>(1U));
 
   if (d_entriesCount > maxNumberOfEntries) {
     uint64_t toErase = d_entriesCount - maxNumberOfEntries;
@@ -128,6 +129,10 @@ void AggressiveNSECCache::prune()
         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 (erased >= toErase || lookedAt >= toLook) {
+            break;
+          }
+
           if (it->d_ttd < now) {
             it = sidx.erase(it);
             ++erased;
@@ -135,9 +140,6 @@ void AggressiveNSECCache::prune()
           else {
             ++it;
           }
-          if (erased > toErase || lookedAt > toLook) {
-            break;
-          }
         }
       }
 
@@ -160,6 +162,9 @@ void AggressiveNSECCache::prune()
 
         auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(node.d_value->d_entries);
         for (auto it = sidx.begin(); it != sidx.end(); ++lookedAt) {
+          if (lookedAt >= toLook) {
+            break;
+          }
           if (it->d_ttd < now || lookedAt > toLook) {
             it = sidx.erase(it);
             ++erased;
@@ -167,9 +172,6 @@ void AggressiveNSECCache::prune()
           else {
             ++it;
           }
-          if (lookedAt > toLook) {
-            break;
-          }
         }
       }
 
@@ -179,6 +181,7 @@ void AggressiveNSECCache::prune()
     });
   }
 
+  d_entriesCount -= erased;
 }
 
 static bool isMinimallyCoveringNSEC(const DNSName& owner, const std::shared_ptr<NSECRecordContent>& nsec)
@@ -714,3 +717,37 @@ bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType
   ++d_nsecHits;
   return true;
 }
+
+size_t AggressiveNSECCache::dumpToFile(std::unique_ptr<FILE, int(*)(FILE*)>& fp, const struct timeval& now)
+{
+  size_t ret = 0;
+
+  ReadLock rl(d_lock);
+  d_zones.visit([&ret, now, &fp](const SuffixMatchTree<std::shared_ptr<ZoneEntry>>& node) {
+    if (!node.d_value) {
+      return;
+    }
+
+    std::lock_guard<std::mutex> lock(node.d_value->d_lock);
+    fprintf(fp.get(), "; Zone %s\n", node.d_value->d_zone.toString().c_str());
+
+    for (const auto& entry : node.d_value->d_entries) {
+      int64_t ttl = entry.d_ttd - now.tv_sec;
+      try {
+        fprintf(fp.get(), "%s %" PRId64 " IN %s %s\n", entry.d_owner.toString().c_str(), ttl, node.d_value->d_nsec3 ? "NSEC3" : "NSEC", entry.d_record->getZoneRepresentation().c_str());
+        for (const auto& signature : entry.d_signatures) {
+          fprintf(fp.get(), "- RRSIG %s\n", signature->getZoneRepresentation().c_str());
+        }
+        ++ret;
+      }
+      catch (const std::exception& e) {
+        fprintf(fp.get(), "; Error dumping record from zone %s: %s\n", node.d_value->d_zone.toString().c_str(), e.what());
+      }
+      catch (...) {
+        fprintf(fp.get(), "; Error dumping record from zone %s\n", node.d_value->d_zone.toString().c_str());
+      }
+    }
+  });
+
+  return ret;
+}
index 69b63eb2cf3c7b9c54bd7483b2201956d4c8f9e0..14d9c3893cd1f6d81ca557a275a54bb714924630 100644 (file)
@@ -72,7 +72,8 @@ public:
     return d_nsec3WildcardHits;
   }
 
-  void prune();
+  void prune(time_t now);
+  size_t dumpToFile(std::unique_ptr<FILE, int(*)(FILE*)>& fp, const struct timeval& now);
 
 private:
 
index c3f1360ce1e2c3186b8d834ce0d2f9ca2e380a00..31531a41db15b265e6fb616f71bab987c7d742ca 100644 (file)
@@ -757,4 +757,168 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis)
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
 
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wiping)
+{
+  auto cache = make_unique<AggressiveNSECCache>(10000);
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  DNSRecord rec;
+  rec.d_name = DNSName("www.powerdns.com");
+  rec.d_type = QType::NSEC;
+  rec.d_ttl = now.tv_sec + 10;
+  rec.d_content = getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC");
+  auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+  cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
+
+  rec.d_name = DNSName("z.powerdns.com");
+  rec.d_content = getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC");
+  cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
+
+  rec.d_name = DNSName("www.powerdns.org");
+  rec.d_type = QType::NSEC3;
+  rec.d_ttl = now.tv_sec + 10;
+  rec.d_content = getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3");
+  rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+  cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
+
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3);
+
+  /* remove just that zone */
+  cache->removeZoneInfo(DNSName("powerdns.org"), false);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2);
+
+  /* add it back */
+  cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3);
+
+  /* remove everything under .org (which should end up in the same way) */
+  cache->removeZoneInfo(DNSName("org."), true);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2);
+
+  /* add it back */
+  cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3);
+
+  /* remove everything */
+  cache->removeZoneInfo(DNSName("."), true);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_pruning)
+{
+  auto cache = make_unique<AggressiveNSECCache>(2);
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  DNSRecord rec;
+  rec.d_name = DNSName("www.powerdns.com");
+  rec.d_type = QType::NSEC;
+  rec.d_ttl = now.tv_sec + 10;
+  rec.d_content = getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC");
+  auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+  cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
+
+  rec.d_name = DNSName("z.powerdns.com");
+  rec.d_content = getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC");
+  cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
+
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2);
+  /* we are at the limit of the number of entries, so we will scan 1/5th of the entries,
+     and prune the expired ones, which mean we should not remove anything */
+  cache->prune(now.tv_sec);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2);
+
+  rec.d_name = DNSName("www.powerdns.org");
+  rec.d_type = QType::NSEC3;
+  rec.d_ttl = now.tv_sec + 10;
+  rec.d_content = getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3");
+  rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+  cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
+
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3);
+
+  /* we have set a upper bound to 2 entries, so we are above,
+     and all entries are actually expired, so we will prune one entry
+     to get below the limit */
+  cache->prune(now.tv_sec + 600);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2);
+
+  /* now we are at the limit, so we will scan 1/5th of the entries,
+     and prune the expired ones, which mean we will also remove only one */
+  cache->prune(now.tv_sec + 600);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1);
+
+  /* now we are below the limit, so we will scan 1/5th of the entries again,
+     and prune the expired ones, which mean we will remove the last one */
+  cache->prune(now.tv_sec + 600);
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_dump)
+{
+  auto cache = make_unique<AggressiveNSECCache>(10000);
+
+  std::vector<std::string> expected;
+  expected.push_back("; Zone powerdns.com.\n");
+  expected.push_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
+  expected.push_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+  expected.push_back("z.powerdns.com. 10 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
+  expected.push_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+  expected.push_back("; Zone powerdns.org.\n");
+  expected.push_back("www.powerdns.org. 10 IN NSEC3 1 0 500 ab HASG==== A RRSIG NSEC3\n");
+  expected.push_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  DNSRecord rec;
+  rec.d_name = DNSName("www.powerdns.com");
+  rec.d_type = QType::NSEC;
+  rec.d_ttl = now.tv_sec + 10;
+  rec.d_content = getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC");
+  auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+  cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
+
+  rec.d_name = DNSName("z.powerdns.com");
+  rec.d_content = getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC");
+  cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
+
+  rec.d_name = DNSName("www.powerdns.org");
+  rec.d_type = QType::NSEC3;
+  rec.d_ttl = now.tv_sec + 10;
+  rec.d_content = getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3");
+  rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+  cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
+
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3);
+
+  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
+  if (!fp) {
+    BOOST_FAIL("Temporary file could not be opened");
+  }
+
+  BOOST_CHECK_EQUAL(cache->dumpToFile(fp, now), 3);
+
+  rewind(fp.get());
+  char* line = nullptr;
+  size_t len = 0;
+  ssize_t read;
+
+  for (auto str : expected) {
+    read = getline(&line, &len, fp.get());
+    if (read == -1) {
+      BOOST_FAIL("Unable to read a line from the temp file");
+    }
+    BOOST_CHECK_EQUAL(line, str);
+  }
+
+  /* getline() allocates a buffer when called with a nullptr,
+     then reallocates it when needed, but we need to free the
+     last allocation if any. */
+  free(line);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 1c0c25989542a03669dff6058118ef38bda4ab52..21b569401fb21586c0230fb854cf6bade9eceb43 100644 (file)
@@ -292,7 +292,6 @@ void computeRRSIG(const DNSSECPrivateKey& dpk, const DNSName& signer, const DNSN
   rrc.d_siginception = inception ? *inception : (*now - 10);
   rrc.d_sigexpire = *now + sigValidity;
   rrc.d_signer = signer;
-  rrc.d_tag = 0;
   rrc.d_tag = drc.getTag();
   rrc.d_algorithm = algo ? *algo : drc.d_algorithm;