diff options
Diffstat (limited to 'dnsdist-dynblocks.cc')
-rw-r--r-- | dnsdist-dynblocks.cc | 511 |
1 files changed, 363 insertions, 148 deletions
diff --git a/dnsdist-dynblocks.cc b/dnsdist-dynblocks.cc index 2f37dfd..b7baca2 100644 --- a/dnsdist-dynblocks.cc +++ b/dnsdist-dynblocks.cc @@ -1,13 +1,13 @@ #include "dnsdist.hh" #include "dnsdist-dynblocks.hh" +#include "dnsdist-metrics.hh" GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG; GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT; DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop; #ifndef DISABLE_DYNBLOCKS - void DynBlockRulesGroup::apply(const struct timespec& now) { counts_t counts; @@ -29,7 +29,7 @@ void DynBlockRulesGroup::apply(const struct timespec& now) return; } - boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> > blocks; + boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>> blocks; bool updated = false; for (const auto& entry : counts) { @@ -54,6 +54,16 @@ void DynBlockRulesGroup::apply(const struct timespec& now) continue; } + if (d_respCacheMissRatioRule.warningRatioExceeded(counters.responses, counters.cacheMisses)) { + handleWarning(blocks, now, requestor, d_respCacheMissRatioRule, updated); + continue; + } + + if (d_respCacheMissRatioRule.ratioExceeded(counters.responses, counters.cacheMisses)) { + addBlock(blocks, now, requestor, d_respCacheMissRatioRule, updated); + continue; + } + for (const auto& pair : d_qtypeRules) { const auto qtype = pair.first; @@ -108,50 +118,64 @@ void DynBlockRulesGroup::apply(const struct timespec& now) g_dynblockNMG.setState(std::move(*blocks)); } - if (!statNodeRoot.empty()) { - StatNode::Stat node; - std::unordered_map<DNSName, std::optional<std::string>> namesToBlock; - statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { - bool block = false; - std::optional<std::string> reason; - - if (d_smtVisitorFFI) { - dnsdist_ffi_stat_node_t tmp(*node_, self, children, reason); - block = d_smtVisitorFFI(&tmp); - } - else { - auto ret = d_smtVisitor(*node_, self, children); - block = std::get<0>(ret); - if (block) { - if (boost::optional<std::string> tmp = std::get<1>(ret)) { - reason = std::move(*tmp); - } - } - } - - if (block) { - namesToBlock.insert({DNSName(node_->fullname), std::move(reason)}); - } - }, - node); - - if (!namesToBlock.empty()) { - updated = false; - SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy(); - for (auto& [name, reason] : namesToBlock) { - if (reason) { - DynBlockRule rule(d_suffixMatchRule); - rule.d_blockReason = std::move(*reason); - addOrRefreshBlockSMT(smtBlocks, now, std::move(name), std::move(rule), updated); + applySMT(now, statNodeRoot); +} + +void DynBlockRulesGroup::applySMT(const struct timespec& now, StatNode& statNodeRoot) +{ + if (statNodeRoot.empty()) { + return; + } + + bool updated = false; + StatNode::Stat node; + std::unordered_map<DNSName, SMTBlockParameters> namesToBlock; + statNodeRoot.visit([this, &namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { + bool block = false; + SMTBlockParameters blockParameters; + if (d_smtVisitorFFI) { + dnsdist_ffi_stat_node_t tmp(*node_, self, children, blockParameters); + block = d_smtVisitorFFI(&tmp); + } + else { + auto ret = d_smtVisitor(*node_, self, children); + block = std::get<0>(ret); + if (block) { + if (boost::optional<std::string> tmp = std::get<1>(ret)) { + blockParameters.d_reason = std::move(*tmp); } - else { - addOrRefreshBlockSMT(smtBlocks, now, std::move(name), d_suffixMatchRule, updated); + if (boost::optional<int> tmp = std::get<2>(ret)) { + blockParameters.d_action = static_cast<DNSAction::Action>(*tmp); + } + } + } + if (block) { + namesToBlock.insert({DNSName(node_->fullname), std::move(blockParameters)}); + } + }, + node); + + if (!namesToBlock.empty()) { + updated = false; + SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy(); + for (auto& [name, parameters] : namesToBlock) { + if (parameters.d_reason || parameters.d_action) { + DynBlockRule rule(d_suffixMatchRule); + if (parameters.d_reason) { + rule.d_blockReason = std::move(*parameters.d_reason); } + if (parameters.d_action) { + rule.d_action = *parameters.d_action; + } + addOrRefreshBlockSMT(smtBlocks, now, name, rule, updated); } - if (updated) { - g_dynblockSMT.setState(std::move(smtBlocks)); + else { + addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated); } } + if (updated) { + g_dynblockSMT.setState(std::move(smtBlocks)); + } } } @@ -173,11 +197,7 @@ bool DynBlockRulesGroup::checkIfResponseCodeMatches(const Rings::Response& respo } auto ratio = d_rcodeRatioRules.find(response.dh.rcode); - if (ratio != d_rcodeRatioRules.end() && ratio->second.matches(response.when)) { - return true; - } - - return false; + return ratio != d_rcodeRatioRules.end() && ratio->second.matches(response.when); } /* return the actual action that will be taken by that block: @@ -192,40 +212,36 @@ static DNSAction::Action getActualAction(const DynBlock& block) return g_dynBlockAction; } -void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning) +namespace dnsdist::DynamicBlocks +{ +bool addOrRefreshBlock(NetmaskTree<DynBlock, AddressAndPortRange>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet) { - /* network exclusions are address-based only (no port) */ - if (d_excludedSubnets.match(requestor.getNetwork())) { - /* do not add a block for excluded subnets */ - return; - } - - if (!blocks) { - blocks = g_dynblockNMG.getCopy(); - } - struct timespec until = now; - until.tv_sec += rule.d_blockDuration; unsigned int count = 0; - const auto& got = blocks->lookup(requestor); bool expired = false; bool wasWarning = false; bool bpf = false; + struct timespec until = now; + until.tv_sec += duration; - if (got) { + DynBlock dblock{reason, until, DNSName(), warning ? DNSAction::Action::NoOp : action}; + dblock.warning = warning; + + const auto& got = blocks.lookup(requestor); + if (got != nullptr) { bpf = got->second.bpf; if (warning && !got->second.warning) { /* we have an existing entry which is not a warning, don't override it */ - return; + return false; } - else if (!warning && got->second.warning) { + if (!warning && got->second.warning) { wasWarning = true; } else { if (until < got->second.until) { // had a longer policy - return; + return false; } } @@ -238,14 +254,11 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, } } - DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action}; - db.blocks = count; - db.warning = warning; - if (!got || expired || wasWarning) { - const auto actualAction = getActualAction(db); - if (g_defaultBPFFilter && - ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128)) && - (actualAction == DNSAction::Action::Drop || actualAction == DNSAction::Action::Truncate)) { + dblock.blocks = count; + + if (got == nullptr || expired || wasWarning) { + const auto actualAction = getActualAction(dblock); + if (g_defaultBPFFilter && ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128)) && (actualAction == DNSAction::Action::Drop || actualAction == DNSAction::Action::Truncate)) { try { BPFFilter::MatchAction bpfAction = actualAction == DNSAction::Action::Drop ? BPFFilter::MatchAction::Drop : BPFFilter::MatchAction::Truncate; if (g_defaultBPFFilter->supportsMatchAction(bpfAction)) { @@ -259,41 +272,37 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, } } - if (!d_beQuiet) { - warnlog("Inserting %s%sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", bpf ? "eBPF " : "", requestor.toString(), rule.d_blockDuration, rule.d_blockReason); + if (!beQuiet) { + warnlog("Inserting %s%sdynamic block for %s for %d seconds: %s", warning ? "(warning) " : "", bpf ? "eBPF " : "", requestor.toString(), duration, reason); } } - db.bpf = bpf; + dblock.bpf = bpf; - blocks->insert(requestor).second = std::move(db); + blocks.insert(requestor).second = std::move(dblock); - updated = true; + return true; } -void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated) +bool addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet) { - if (d_excludedDomains.check(name)) { - /* do not add a block for excluded domains */ - return; - } - struct timespec until = now; - until.tv_sec += rule.d_blockDuration; + until.tv_sec += duration; unsigned int count = 0; + DynBlock dblock{reason, until, name.makeLowerCase(), action}; /* 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) { + if (got != nullptr && got->domain != name) { got = nullptr; } bool expired = false; - if (got) { + if (got != nullptr) { if (until < got->until) { // had a longer policy - return; + return false; } if (now < got->until) { @@ -305,14 +314,55 @@ void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, } } - DynBlock db{rule.d_blockReason, until, name.makeLowerCase(), rule.d_action}; - db.blocks = count; + dblock.blocks = count; + + if (!beQuiet && (got == nullptr || expired)) { + warnlog("Inserting dynamic block for %s for %d seconds: %s", name, duration, reason); + } + blocks.add(name, std::move(dblock)); + return true; +} +} + +void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning) +{ + /* network exclusions are address-based only (no port) */ + if (d_excludedSubnets.match(requestor.getNetwork())) { + /* do not add a block for excluded subnets */ + return; + } + + if (!blocks) { + blocks = g_dynblockNMG.getCopy(); + } + + updated = dnsdist::DynamicBlocks::addOrRefreshBlock(*blocks, now, requestor, rule.d_blockReason, rule.d_blockDuration, rule.d_action, warning, d_beQuiet); + if (updated && d_newBlockHook) { + try { + d_newBlockHook(dnsdist_ffi_dynamic_block_type_nmt, requestor.toString().c_str(), rule.d_blockReason.c_str(), static_cast<uint8_t>(rule.d_action), rule.d_blockDuration, warning); + } + catch (const std::exception& exp) { + warnlog("Error calling the Lua hook after a dynamic block insertion: %s", exp.what()); + } + } +} + +void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated) +{ + if (d_excludedDomains.check(name)) { + /* do not add a block for excluded domains */ + return; + } - if (!d_beQuiet && (!got || expired)) { - warnlog("Inserting dynamic block for %s for %d seconds: %s", name, rule.d_blockDuration, rule.d_blockReason); + updated = dnsdist::DynamicBlocks::addOrRefreshBlockSMT(blocks, now, name, rule.d_blockReason, rule.d_blockDuration, rule.d_action, d_beQuiet); + if (updated && d_newBlockHook) { + try { + d_newBlockHook(dnsdist_ffi_dynamic_block_type_smt, name.toString().c_str(), rule.d_blockReason.c_str(), static_cast<uint8_t>(rule.d_action), rule.d_blockDuration, false); + } + catch (const std::exception& exp) { + warnlog("Error calling the Lua hook after a dynamic block insertion: %s", exp.what()); + } } - blocks.add(name, std::move(db)); - updated = true; } void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timespec& now) @@ -330,22 +380,22 @@ void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timesp } for (const auto& shard : g_rings.d_shards) { - auto rl = shard->queryRing.lock(); - for(const auto& c : *rl) { - if (now < c.when) { + auto queryRing = shard->queryRing.lock(); + for (const auto& ringEntry : *queryRing) { + if (now < ringEntry.when) { continue; } - bool qRateMatches = d_queryRateRule.matches(c.when); - bool typeRuleMatches = checkIfQueryTypeMatches(c); + bool qRateMatches = d_queryRateRule.matches(ringEntry.when); + bool typeRuleMatches = checkIfQueryTypeMatches(ringEntry); if (qRateMatches || typeRuleMatches) { - auto& entry = counts[AddressAndPortRange(c.requestor, c.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)]; + auto& entry = counts[AddressAndPortRange(ringEntry.requestor, ringEntry.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)]; if (qRateMatches) { ++entry.queries; } if (typeRuleMatches) { - ++entry.d_qtypeCounts[c.qtype]; + ++entry.d_qtypeCounts[ringEntry.qtype]; } } } @@ -372,6 +422,12 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root, responseCutOff = d_suffixMatchRule.d_cutOff; } + d_respCacheMissRatioRule.d_cutOff = d_respCacheMissRatioRule.d_minTime = now; + d_respCacheMissRatioRule.d_cutOff.tv_sec -= d_respCacheMissRatioRule.d_seconds; + if (d_respCacheMissRatioRule.d_cutOff < responseCutOff) { + responseCutOff = d_respCacheMissRatioRule.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; @@ -389,39 +445,37 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root, } for (const auto& shard : g_rings.d_shards) { - auto rl = shard->respRing.lock(); - for(const auto& c : *rl) { - if (now < c.when) { + auto responseRing = shard->respRing.lock(); + for (const auto& ringEntry : *responseRing) { + if (now < ringEntry.when) { continue; } - if (c.when < responseCutOff) { + if (ringEntry.when < responseCutOff) { continue; } - auto& entry = counts[AddressAndPortRange(c.requestor, c.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)]; + auto& entry = counts[AddressAndPortRange(ringEntry.requestor, ringEntry.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)]; ++entry.responses; - bool respRateMatches = d_respRateRule.matches(c.when); - bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when); - bool rcodeRuleMatches = checkIfResponseCodeMatches(c); + bool respRateMatches = d_respRateRule.matches(ringEntry.when); + bool suffixMatchRuleMatches = d_suffixMatchRule.matches(ringEntry.when); + bool rcodeRuleMatches = checkIfResponseCodeMatches(ringEntry); + bool respCacheMissRatioRuleMatches = d_respCacheMissRatioRule.matches(ringEntry.when); - if (respRateMatches || rcodeRuleMatches) { - if (respRateMatches) { - entry.respBytes += c.size; - } - if (rcodeRuleMatches) { - ++entry.d_rcodeCounts[c.dh.rcode]; - } + if (respRateMatches) { + entry.respBytes += ringEntry.size; + } + if (rcodeRuleMatches) { + ++entry.d_rcodeCounts[ringEntry.dh.rcode]; + } + if (respCacheMissRatioRuleMatches && !ringEntry.isACacheHit()) { + ++entry.cacheMisses; } if (suffixMatchRuleMatches) { - bool hit = c.ds.sin4.sin_family == 0; - if (!hit && c.ds.isIPv4() && c.ds.sin4.sin_addr.s_addr == 0 && c.ds.sin4.sin_port == 0) { - hit = true; - } - - root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, hit, boost::none); + const bool hit = ringEntry.isACacheHit(); + root.submit(ringEntry.name, ((ringEntry.dh.rcode == 0 && ringEntry.usec == std::numeric_limits<unsigned int>::max()) ? -1 : ringEntry.dh.rcode), ringEntry.size, hit, boost::none); } } } @@ -462,7 +516,7 @@ void DynBlockMaintenance::purgeExpired(const struct timespec& now) updated.erase(entry); } g_dynblockNMG.setState(std::move(updated)); - g_stats.dynBlocked += bpfBlocked; + dnsdist::metrics::g_stats.dynBlocked += bpfBlocked; } } @@ -507,10 +561,10 @@ std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> D topsForReason.pop_front(); } - topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& a, const std::pair<AddressAndPortRange, unsigned int>& b) { - return a.second < b.second; - }), - newEntry); + topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& rhs, const std::pair<AddressAndPortRange, unsigned int>& lhs) { + return rhs.second < lhs.second; + }), + newEntry); } } @@ -534,10 +588,10 @@ std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaint topsForReason.pop_front(); } - topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) { - return a.second < b.second; - }), - newEntry); + topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& rhs, const std::pair<DNSName, unsigned int>& lhs) { + return rhs.second < lhs.second; + }), + newEntry); } }); @@ -546,7 +600,7 @@ std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaint struct DynBlockEntryStat { - size_t sum; + size_t sum{0}; unsigned int lastSeenValue{0}; }; @@ -577,9 +631,9 @@ void DynBlockMaintenance::generateMetrics() } /* do NMG */ - std::map<std::string, std::map<AddressAndPortRange, DynBlockEntryStat>> nm; + std::map<std::string, std::map<AddressAndPortRange, DynBlockEntryStat>> netmasks; for (const auto& reason : s_metricsData.front().nmgData) { - auto& reasonStat = nm[reason.first]; + auto& reasonStat = netmasks[reason.first]; /* prepare the counters by scanning the oldest entry (N+1) */ for (const auto& entry : reason.second) { @@ -597,9 +651,9 @@ void DynBlockMaintenance::generateMetrics() continue; } - auto& nmgData = snap.nmgData; + const auto& nmgData = snap.nmgData; for (const auto& reason : nmgData) { - auto& reasonStat = nm[reason.first]; + auto& reasonStat = netmasks[reason.first]; for (const auto& entry : reason.second) { auto& stat = reasonStat[entry.first]; if (entry.second < stat.lastSeenValue) { @@ -617,20 +671,20 @@ void DynBlockMaintenance::generateMetrics() /* now we need to get the top N entries (for each "reason") based on our counters (sum of the last N entries) */ std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGs; { - for (const auto& reason : nm) { + for (const auto& reason : netmasks) { auto& topsForReason = topNMGs[reason.first]; for (const auto& entry : reason.second) { if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.sum) { /* Note that this is a gauge, so we need to divide by the number of elapsed seconds */ - auto newEntry = std::pair<AddressAndPortRange, unsigned int>(entry.first, std::round(entry.second.sum / 60.0)); + auto newEntry = std::pair<AddressAndPortRange, unsigned int>(entry.first, std::round(static_cast<double>(entry.second.sum) / 60.0)); if (topsForReason.size() >= s_topN) { topsForReason.pop_front(); } - topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& a, const std::pair<AddressAndPortRange, unsigned int>& b) { - return a.second < b.second; - }), - newEntry); + topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& rhs, const std::pair<AddressAndPortRange, unsigned int>& lhs) { + return rhs.second < lhs.second; + }), + newEntry); } } } @@ -657,7 +711,7 @@ void DynBlockMaintenance::generateMetrics() continue; } - auto& smtData = snap.smtData; + const auto& smtData = snap.smtData; for (const auto& reason : smtData) { auto& reasonStat = smt[reason.first]; for (const auto& entry : reason.second) { @@ -682,15 +736,15 @@ void DynBlockMaintenance::generateMetrics() for (const auto& entry : reason.second) { if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.sum) { /* Note that this is a gauge, so we need to divide by the number of elapsed seconds */ - auto newEntry = std::pair<DNSName, unsigned int>(entry.first, std::round(entry.second.sum / 60.0)); + auto newEntry = std::pair<DNSName, unsigned int>(entry.first, std::round(static_cast<double>(entry.second.sum) / 60.0)); if (topsForReason.size() >= s_topN) { topsForReason.pop_front(); } - topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) { - return a.second < b.second; - }), - newEntry); + topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& lhs, const std::pair<DNSName, unsigned int>& rhs) { + return lhs.second < rhs.second; + }), + newEntry); } } } @@ -727,7 +781,7 @@ void DynBlockMaintenance::run() sleepDelay = std::min(sleepDelay, (nextMetricsGeneration - now)); // coverity[store_truncates_time_t] - sleep(sleepDelay); + std::this_thread::sleep_for(std::chrono::seconds(sleepDelay)); try { now = time(nullptr); @@ -749,7 +803,9 @@ void DynBlockMaintenance::run() } if (s_expiredDynBlocksPurgeInterval > 0 && now >= nextExpiredPurge) { - struct timespec tspec; + struct timespec tspec + { + }; gettime(&tspec); purgeExpired(tspec); @@ -774,4 +830,163 @@ std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaint { return s_tops.lock()->topSMTsByReason; } + +std::string DynBlockRulesGroup::DynBlockRule::toString() const +{ + if (!isEnabled()) { + return ""; + } + + std::stringstream result; + if (d_action != DNSAction::Action::None) { + result << DNSAction::typeToString(d_action) << " "; + } + else { + result << "Apply the global DynBlock action "; + } + result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'"; + + return result.str(); +} + +bool DynBlockRulesGroup::DynBlockRule::matches(const struct timespec& when) +{ + if (!d_enabled) { + return false; + } + + if (d_seconds > 0 && when < d_cutOff) { + return false; + } + + if (when < d_minTime) { + d_minTime = when; + } + + return true; +} + +bool DynBlockRulesGroup::DynBlockRule::rateExceeded(unsigned int count, const struct timespec& now) const +{ + if (!d_enabled) { + return false; + } + + double delta = d_seconds > 0 ? d_seconds : DiffTime(now, d_minTime); + double limit = delta * d_rate; + return (count > limit); +} + +bool DynBlockRulesGroup::DynBlockRule::warningRateExceeded(unsigned int count, const struct timespec& now) const +{ + if (!d_enabled) { + return false; + } + + if (d_warningRate == 0) { + return false; + } + + double delta = d_seconds > 0 ? d_seconds : DiffTime(now, d_minTime); + double limit = delta * d_warningRate; + return (count > limit); +} + +bool DynBlockRulesGroup::DynBlockRatioRule::ratioExceeded(unsigned int total, unsigned int count) const +{ + if (!d_enabled) { + return false; + } + + if (total < d_minimumNumberOfResponses) { + return false; + } + + double allowed = d_ratio * static_cast<double>(total); + return (count > allowed); +} + +bool DynBlockRulesGroup::DynBlockRatioRule::warningRatioExceeded(unsigned int total, unsigned int count) const +{ + if (!d_enabled) { + return false; + } + + if (d_warningRatio == 0.0) { + return false; + } + + if (total < d_minimumNumberOfResponses) { + return false; + } + + double allowed = d_warningRatio * static_cast<double>(total); + return (count > allowed); +} + +std::string DynBlockRulesGroup::DynBlockRatioRule::toString() const +{ + if (!isEnabled()) { + return ""; + } + + std::stringstream result; + if (d_action != DNSAction::Action::None) { + result << DNSAction::typeToString(d_action) << " "; + } + else { + result << "Apply the global DynBlock action "; + } + result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'"; + + return result.str(); +} + +bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::checkGlobalCacheHitRatio() const +{ + auto globalMisses = dnsdist::metrics::g_stats.cacheMisses.load(); + auto globalHits = dnsdist::metrics::g_stats.cacheHits.load(); + if (globalMisses == 0 || globalHits == 0) { + return false; + } + double globalCacheHitRatio = static_cast<double>(globalHits) / static_cast<double>(globalHits + globalMisses); + return globalCacheHitRatio >= d_minimumGlobalCacheHitRatio; +} + +bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::ratioExceeded(unsigned int total, unsigned int count) const +{ + if (!DynBlockRulesGroup::DynBlockRatioRule::ratioExceeded(total, count)) { + return false; + } + + return checkGlobalCacheHitRatio(); +} + +bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::warningRatioExceeded(unsigned int total, unsigned int count) const +{ + if (!DynBlockRulesGroup::DynBlockRatioRule::warningRatioExceeded(total, count)) { + return false; + } + + return checkGlobalCacheHitRatio(); +} + +std::string DynBlockRulesGroup::DynBlockCacheMissRatioRule::toString() const +{ + if (!isEnabled()) { + return ""; + } + + std::stringstream result; + if (d_action != DNSAction::Action::None) { + result << DNSAction::typeToString(d_action) << " "; + } + else { + result << "Apply the global DynBlock action "; + } + result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, with a global cache-hit ratio of at least " << d_minimumGlobalCacheHitRatio << ", reason: '" << d_blockReason << "'"; + + return result.str(); +} + #endif /* DISABLE_DYNBLOCKS */ |