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) {
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
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;
::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");
/* 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;
}
}
}
+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/),
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);
return d_nsec3WildcardHits;
}
+ void prune();
+
private:
struct ZoneEntry
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;
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:
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
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();
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);
}
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
{
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
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();
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);
}