From: Remi Gacogne Date: Thu, 7 Jan 2021 15:21:34 +0000 (+0100) Subject: rec: Cache cleaning, make the aggressive nsec cache size configurable X-Git-Tag: dnsdist-1.6.0-alpha2~12^2~19 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=edcf680eb16657d7fed87d226ea17cd7ddd4c2ad;p=thirdparty%2Fpdns.git rec: Cache cleaning, make the aggressive nsec cache size configurable --- diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index 125cfebc7a..a0b238e620 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -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(); + g_aggressiveNSECCache = make_unique(::arg().asNum("aggressive-nsec-cache-size")); } else { g_log<>& 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 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>& node) { + if (!node.d_value || erased > toErase || lookedAt > toLook) { + return; + } + + { + std::lock_guard lock(node.d_value->d_lock); + auto& sidx = boost::multi_index::get(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>& node) { + if (!node.d_value) { + return; + } + + { + std::lock_guard lock(node.d_value->d_lock); + + auto& sidx = boost::multi_index::get(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& nsec) { /* this test only covers Cloudflare's ones (https://blog.cloudflare.com/black-lies/), diff --git a/pdns/recursordist/aggressive_nsec.hh b/pdns/recursordist/aggressive_nsec.hh index 132a53a4b3..69b63eb2cf 100644 --- a/pdns/recursordist/aggressive_nsec.hh +++ b/pdns/recursordist/aggressive_nsec.hh @@ -38,6 +38,10 @@ class AggressiveNSECCache { public: + AggressiveNSECCache(uint64_t entries): d_maxEntries(entries) + { + } + void insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector>& signatures, bool nsec3); bool getDenial(time_t, const DNSName& name, const QType& type, std::vector& ret, int& res, const ComboAddress& who, const boost::optional& routingTag, bool doDNSSEC); @@ -68,6 +72,8 @@ public: return d_nsec3WildcardHits; } + void prune(); + private: struct ZoneEntry @@ -134,7 +140,7 @@ private: std::atomic d_nsecWildcardHits{0}; std::atomic d_nsec3WildcardHits{0}; std::atomic d_entriesCount{0}; + uint64_t d_maxEntries{0}; }; - extern std::unique_ptr g_aggressiveNSECCache; diff --git a/pdns/recursordist/docs/settings.rst b/pdns/recursordist/docs/settings.rst index 39dfc9abe2..63c65f6892 100644 --- a/pdns/recursordist/docs/settings.rst +++ b/pdns/recursordist/docs/settings.rst @@ -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: diff --git a/pdns/recursordist/test-aggressive_nsec_cc.cc b/pdns/recursordist/test-aggressive_nsec_cc.cc index a355b7e7a4..c3f1360ce1 100644 --- a/pdns/recursordist/test-aggressive_nsec_cc.cc +++ b/pdns/recursordist/test-aggressive_nsec_cc.cc @@ -10,7 +10,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain) { std::unique_ptr sr; initSR(sr, true); - g_aggressiveNSECCache = make_unique(); + g_aggressiveNSECCache = make_unique(10000); setDNSSECValidation(sr, DNSSECMode::ValidateAll); @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata) { std::unique_ptr sr; initSR(sr, true); - g_aggressiveNSECCache = make_unique(); + g_aggressiveNSECCache = make_unique(10000); setDNSSECValidation(sr, DNSSECMode::ValidateAll); @@ -194,7 +194,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata_wildcard) { std::unique_ptr sr; initSR(sr, true); - g_aggressiveNSECCache = make_unique(); + g_aggressiveNSECCache = make_unique(10000); setDNSSECValidation(sr, DNSSECMode::ValidateAll); @@ -285,7 +285,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis) { std::unique_ptr sr; initSR(sr, true); - g_aggressiveNSECCache = make_unique(); + g_aggressiveNSECCache = make_unique(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 sr; initSR(sr, true); - g_aggressiveNSECCache = make_unique(); + g_aggressiveNSECCache = make_unique(10000); setDNSSECValidation(sr, DNSSECMode::ValidateAll); @@ -471,7 +471,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata) { std::unique_ptr sr; initSR(sr, true); - g_aggressiveNSECCache = make_unique(); + g_aggressiveNSECCache = make_unique(10000); setDNSSECValidation(sr, DNSSECMode::ValidateAll); @@ -557,7 +557,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata_wildcard) { std::unique_ptr sr; initSR(sr, true); - g_aggressiveNSECCache = make_unique(); + g_aggressiveNSECCache = make_unique(10000); setDNSSECValidation(sr, DNSSECMode::ValidateAll); @@ -660,7 +660,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis) { std::unique_ptr sr; initSR(sr, true); - g_aggressiveNSECCache = make_unique(); + g_aggressiveNSECCache = make_unique(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); }