From: Remi Gacogne Date: Fri, 27 Nov 2020 09:51:13 +0000 (+0100) Subject: dnsdist: Implement DynBlock oversampling, fix refresh issues with SMT entries X-Git-Tag: rec-4.5.0-alpha1~92^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3814053e12332163e9776220fb44856ed1abd058;p=thirdparty%2Fpdns.git dnsdist: Implement DynBlock oversampling, fix refresh issues with SMT entries --- diff --git a/pdns/dnsdist-dynblocks.hh b/pdns/dnsdist-dynblocks.hh index eaf85f09e0..87027a47ce 100644 --- a/pdns/dnsdist-dynblocks.hh +++ b/pdns/dnsdist-dynblocks.hh @@ -63,6 +63,7 @@ private: std::map d_rcodeCounts; std::map d_qtypeCounts; uint64_t queries{0}; + uint64_t responses{0}; uint64_t respBytes{0}; }; @@ -377,8 +378,8 @@ public: static std::map>> getHitsForTopSuffixes(); /* get the the top offenders based on the current value of the counters */ - static std::map>> getTopNetmasks(); - static std::map>> getTopSuffixes(); + static std::map>> getTopNetmasks(size_t topN); + static std::map>> getTopSuffixes(size_t topN); static void purgeExpired(const struct timespec& now); static time_t s_expiredDynBlocksPurgeInterval; diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 5d8eae4257..6de9aed771 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -1148,8 +1148,9 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) boost::format fmt("%-24s %8d %8d %-10s %-20s %s\n"); g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "Reason").str(); for(const auto& e: slow) { - if(now < e.second.until) - g_outputBuffer+= (fmt % e.first.toString() % (e.second.until.tv_sec - now.tv_sec) % e.second.blocks % (e.second.warning ? "true" : "false") % DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) % e.second.reason).str(); + if (now < e.second.until) { + g_outputBuffer+= (fmt % e.first.toString() % (e.second.until.tv_sec - now.tv_sec) % e.second.blocks % (e.second.warning ? "true" : "false") % DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) % e.second.reason).str(); + } } auto slow2 = g_dynblockSMT.getCopy(); slow2.visit([&now, &fmt](const SuffixMatchTree& node) { diff --git a/pdns/dnsdistdist/dnsdist-dynblocks.cc b/pdns/dnsdistdist/dnsdist-dynblocks.cc index 95a5914559..900a25cfaf 100644 --- a/pdns/dnsdistdist/dnsdist-dynblocks.cc +++ b/pdns/dnsdistdist/dnsdist-dynblocks.cc @@ -86,11 +86,11 @@ void DynBlockRulesGroup::apply(const struct timespec& now) const auto& rcodeIt = counters.d_rcodeCounts.find(rcode); if (rcodeIt != counters.d_rcodeCounts.cend()) { - if (pair.second.warningRatioExceeded(counters.queries, rcodeIt->second)) { + if (pair.second.warningRatioExceeded(counters.responses, rcodeIt->second)) { handleWarning(blocks, now, requestor, pair.second, updated); } - if (pair.second.ratioExceeded(counters.queries, rcodeIt->second)) { + if (pair.second.ratioExceeded(counters.responses, rcodeIt->second)) { addBlock(blocks, now, requestor, pair.second, updated); break; } @@ -222,7 +222,13 @@ void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree& blocks, struct timespec until = now; until.tv_sec += rule.d_blockDuration; unsigned int count = 0; - const auto& got = blocks.lookup(name); + /* be careful, if you try to insert a longer suffix + lookup() might return a shorter one if it is + already in the tree as a final node */ + const DynBlock* got = blocks.lookup(name); + if (got && got->domain != name) { + got = nullptr; + } bool expired = false; if (got) { @@ -293,20 +299,34 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root, return; } + struct timespec responseCutOff = now; + d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now; d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds; + if (d_respRateRule.d_cutOff < responseCutOff) { + responseCutOff = d_respRateRule.d_cutOff; + } d_suffixMatchRule.d_cutOff = d_suffixMatchRule.d_minTime = now; d_suffixMatchRule.d_cutOff.tv_sec -= d_suffixMatchRule.d_seconds; + if (d_suffixMatchRule.d_cutOff < responseCutOff) { + responseCutOff = d_suffixMatchRule.d_cutOff; + } for (auto& rule : d_rcodeRules) { rule.second.d_cutOff = rule.second.d_minTime = now; rule.second.d_cutOff.tv_sec -= rule.second.d_seconds; + if (rule.second.d_cutOff < responseCutOff) { + responseCutOff = rule.second.d_cutOff; + } } for (auto& rule : d_rcodeRatioRules) { rule.second.d_cutOff = rule.second.d_minTime = now; rule.second.d_cutOff.tv_sec -= rule.second.d_seconds; + if (rule.second.d_cutOff < responseCutOff) { + responseCutOff = rule.second.d_cutOff; + } } for (const auto& shard : g_rings.d_shards) { @@ -316,8 +336,13 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root, continue; } + if (c.when < responseCutOff) { + continue; + } + auto& entry = counts[c.requestor]; - ++entry.queries; + ++entry.responses; + bool respRateMatches = d_respRateRule.matches(c.when); bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when); bool rcodeRuleMatches = checkIfResponseCodeMatches(c); @@ -375,20 +400,20 @@ void DynBlockMaintenance::purgeExpired(const struct timespec& now) } } -std::map>> DynBlockMaintenance::getTopNetmasks() +std::map>> DynBlockMaintenance::getTopNetmasks(size_t topN) { std::map>> results; - if (s_topN == 0) { + if (topN == 0) { return results; } auto blocks = g_dynblockNMG.getLocal(); for (const auto& entry : *blocks) { auto& topsForReason = results[entry.second.reason]; - if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.blocks) { + if (topsForReason.size() < topN || topsForReason.front().second < entry.second.blocks) { auto newEntry = std::make_pair(entry.first, entry.second.blocks.load()); - if (topsForReason.size() >= s_topN) { + if (topsForReason.size() >= topN) { topsForReason.pop_front(); } @@ -402,20 +427,20 @@ std::map>> DynBlockMaint return results; } -std::map>> DynBlockMaintenance::getTopSuffixes() +std::map>> DynBlockMaintenance::getTopSuffixes(size_t topN) { std::map>> results; - if (s_topN == 0) { + if (topN == 0) { return results; } auto blocks = g_dynblockSMT.getLocal(); - blocks->visit([&results](const SuffixMatchTree& node) { + blocks->visit([&results, topN](const SuffixMatchTree& node) { auto& topsForReason = results[node.d_value.reason]; - if (topsForReason.size() < DynBlockMaintenance::s_topN || topsForReason.front().second < node.d_value.blocks) { + if (topsForReason.size() < topN || topsForReason.front().second < node.d_value.blocks) { auto newEntry = std::make_pair(node.d_value.domain, node.d_value.blocks.load()); - if (topsForReason.size() >= DynBlockMaintenance::s_topN) { + if (topsForReason.size() >= topN) { topsForReason.pop_front(); } @@ -445,8 +470,10 @@ time_t DynBlockMaintenance::s_expiredDynBlocksPurgeInterval{300}; void DynBlockMaintenance::collectMetrics() { MetricsSnapshot snapshot; - snapshot.smtData = getTopSuffixes(); - snapshot.nmgData = getTopNetmasks(); + /* over sampling to get entries that are not in the top N + every time a chance to be at the end */ + snapshot.smtData = getTopSuffixes(s_topN * 5); + snapshot.nmgData = getTopNetmasks(s_topN * 5); { std::lock_guard lock(s_topsMutex); diff --git a/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc b/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc index cb074855d8..caee5adcff 100644 --- a/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc +++ b/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc @@ -714,7 +714,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) { /* now we ask for the top 20 offenders for each reason */ StopWatch sw; sw.start(); - auto top = DynBlockMaintenance::getTopNetmasks(); + auto top = DynBlockMaintenance::getTopNetmasks(20); BOOST_REQUIRE_EQUAL(top.size(), 1U); auto offenders = top.at(reason); BOOST_REQUIRE_EQUAL(offenders.size(), 20U); @@ -770,7 +770,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) { /* now we ask for the top 20 offenders for each reason */ StopWatch sw; sw.start(); - auto top = DynBlockMaintenance::getTopSuffixes(); + auto top = DynBlockMaintenance::getTopSuffixes(20); BOOST_REQUIRE_EQUAL(top.size(), 1U); auto suffixes = top.at(reason); BOOST_REQUIRE_EQUAL(suffixes.size(), 20U); @@ -825,7 +825,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) { cerr<<"added 1000000 entries in "<size(), 1000000U); sw.start(); - auto top = DynBlockMaintenance::getTopNetmasks(); + auto top = DynBlockMaintenance::getTopNetmasks(20); cerr<<"scanned "<size()<<" entries in "<