From: Remi Gacogne Date: Fri, 8 Jan 2021 15:58:00 +0000 (+0100) Subject: rec: Fix and add unit tests for Aggressive NSEC cache cleaning, dump, wiping X-Git-Tag: dnsdist-1.6.0-alpha2~12^2~18 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5586c462fdceef117338f8f114759efe0111adfd;p=thirdparty%2Fpdns.git rec: Fix and add unit tests for Aggressive NSEC cache cleaning, dump, wiping --- diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index a0b238e620..c0f713f491 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -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; } diff --git a/pdns/rec_channel_rec.cc b/pdns/rec_channel_rec.cc index ca92008537..5a78673f6c 100644 --- a/pdns/rec_channel_rec.cc +++ b/pdns/rec_channel_rec.cc @@ -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(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([fd]{ return pleaseDump(fd); }); + total = g_recCache->doDump(fd) + dumpNegCache(fd) + broadcastAccFunction([fd]{ return pleaseDump(fd); }) + dumpAggressiveNSECCache(fd); } catch(...){} diff --git a/pdns/recursordist/aggressive_nsec.cc b/pdns/recursordist/aggressive_nsec.cc index dee2d5d0ea..b35038cd47 100644 --- a/pdns/recursordist/aggressive_nsec.cc +++ b/pdns/recursordist/aggressive_nsec.cc @@ -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 #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 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 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(1U)); if (d_entriesCount > maxNumberOfEntries) { uint64_t toErase = d_entriesCount - maxNumberOfEntries; @@ -128,6 +129,10 @@ void AggressiveNSECCache::prune() 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 (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(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& 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& fp, const struct timeval& now) +{ + size_t ret = 0; + + ReadLock rl(d_lock); + d_zones.visit([&ret, now, &fp](const SuffixMatchTree>& node) { + if (!node.d_value) { + return; + } + + std::lock_guard 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; +} diff --git a/pdns/recursordist/aggressive_nsec.hh b/pdns/recursordist/aggressive_nsec.hh index 69b63eb2cf..14d9c3893c 100644 --- a/pdns/recursordist/aggressive_nsec.hh +++ b/pdns/recursordist/aggressive_nsec.hh @@ -72,7 +72,8 @@ public: return d_nsec3WildcardHits; } - void prune(); + void prune(time_t now); + size_t dumpToFile(std::unique_ptr& fp, const struct timeval& now); private: diff --git a/pdns/recursordist/test-aggressive_nsec_cc.cc b/pdns/recursordist/test-aggressive_nsec_cc.cc index c3f1360ce1..31531a41db 100644 --- a/pdns/recursordist/test-aggressive_nsec_cc.cc +++ b/pdns/recursordist/test-aggressive_nsec_cc.cc @@ -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(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("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("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(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("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("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(10000); + + std::vector 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("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("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(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() diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index 1c0c259895..21b569401f 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -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;