From 23adffab99d2352405e2663312346a1a4ded7dab Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Fri, 4 Jan 2019 17:22:26 +0100 Subject: [PATCH] dnsdist: Add addDynBlockSMT() support to dynBlockRulesGroup --- pdns/dnsdist-dynblocks.hh | 106 ++++++++++++++++++- pdns/dnsdist-lua-inspection.cc | 15 +++ pdns/dnsdistdist/Makefile.am | 1 + pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc | 1 + pdns/statnode.hh | 4 + 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/pdns/dnsdist-dynblocks.hh b/pdns/dnsdist-dynblocks.hh index 73d4af36d..a66899c1b 100644 --- a/pdns/dnsdist-dynblocks.hh +++ b/pdns/dnsdist-dynblocks.hh @@ -21,8 +21,11 @@ */ #pragma once +#include + #include "dolog.hh" #include "dnsdist-rings.hh" +#include "statnode.hh" class DynBlockRulesGroup { @@ -119,7 +122,7 @@ private: bool d_enabled{false}; }; - typedef std::unordered_map counts_t; + typedef std::unordered_map counts_t; public: DynBlockRulesGroup() @@ -149,6 +152,14 @@ public: entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); } + typedef std::function smtVisitor_t; + + void setSuffixMatchRule(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor) + { + d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action); + d_smtVisitor = visitor; + } + void apply() { struct timespec now; @@ -160,6 +171,7 @@ public: void apply(const struct timespec& now) { counts_t counts; + StatNode statNodeRoot; size_t entriesCount = 0; if (hasQueryRules()) { @@ -171,9 +183,9 @@ public: counts.reserve(entriesCount); processQueryRules(counts, now); - processResponseRules(counts, now); + processResponseRules(counts, statNodeRoot, now); - if (counts.empty()) { + if (counts.empty() && statNodeRoot.empty()) { return; } @@ -239,6 +251,29 @@ public: if (updated && blocks) { g_dynblockNMG.setState(*blocks); } + + if (!statNodeRoot.empty()) { + StatNode::Stat node; + std::unordered_set namesToBlock; + statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { + bool block = d_smtVisitor(*node_, self, children); + if (block) { + namesToBlock.insert(DNSName(node_->fullname)); + } + }, + node); + + if (!namesToBlock.empty()) { + updated = false; + SuffixMatchTree smtBlocks = g_dynblockSMT.getCopy(); + for (const auto& name : namesToBlock) { + addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated); + } + if (updated) { + g_dynblockSMT.setState(smtBlocks); + } + } + } } void excludeRange(const Netmask& range) @@ -251,12 +286,18 @@ public: d_excludedSubnets.addMask(range, false); } + void excludeDomain(const DNSName& domain) + { + d_excludedDomains.add(domain); + } + std::string toString() const { std::stringstream result; result << "Query rate rule: " << d_queryRateRule.toString() << std::endl; result << "Response rate rule: " << d_respRateRule.toString() << std::endl; + result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl; result << "RCode rules: " << std::endl; for (const auto& rule : d_rcodeRules) { result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl; @@ -266,6 +307,7 @@ public: result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl; } result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl; + result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl; return result.str(); } @@ -348,6 +390,44 @@ private: updated = true; } + void addOrRefreshBlockSMT(SuffixMatchTree& 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; + } + + struct timespec until = now; + until.tv_sec += rule.d_blockDuration; + unsigned int count = 0; + const auto& got = blocks.lookup(name); + bool expired = false; + + if (got) { + if (until < got->until) { + // had a longer policy + return; + } + + if (now < got->until) { + // only inherit count on fresh query we are extending + count = got->blocks; + } + else { + expired = true; + } + } + + DynBlock db{rule.d_blockReason, until, name, rule.d_action}; + db.blocks = count; + + if (!d_beQuiet && (!got || expired)) { + warnlog("Inserting dynamic block for %s for %d seconds: %s", name, rule.d_blockDuration, rule.d_blockReason); + } + blocks.add(name, db); + updated = true; + } + void addBlock(boost::optional >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated) { addOrRefreshBlock(blocks, now, requestor, rule, updated, false); @@ -368,6 +448,11 @@ private: return d_respRateRule.isEnabled() || !d_rcodeRules.empty(); } + bool hasSuffixMatchRules() const + { + return d_suffixMatchRule.isEnabled(); + } + bool hasRules() const { return hasQueryRules() || hasResponseRules(); @@ -410,15 +495,18 @@ private: } } - void processResponseRules(counts_t& counts, const struct timespec& now) + void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now) { - if (!hasResponseRules()) { + if (!hasResponseRules() && !hasSuffixMatchRules()) { return; } d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now; d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds; + d_suffixMatchRule.d_cutOff = d_suffixMatchRule.d_minTime = now; + d_suffixMatchRule.d_cutOff.tv_sec -= d_suffixMatchRule.d_seconds; + for (auto& rule : d_rcodeRules) { rule.second.d_cutOff = rule.second.d_minTime = now; rule.second.d_cutOff.tv_sec -= rule.second.d_seconds; @@ -432,6 +520,7 @@ private: } bool respRateMatches = d_respRateRule.matches(c.when); + bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when); bool rcodeRuleMatches = checkIfResponseCodeMatches(c); if (respRateMatches || rcodeRuleMatches) { @@ -443,6 +532,10 @@ private: entry.d_rcodeCounts[c.dh.rcode]++; } } + + if (suffixMatchRuleMatches) { + root.submit(c.name, c.dh.rcode, boost::none); + } } } } @@ -451,6 +544,9 @@ private: std::map d_qtypeRules; DynBlockRule d_queryRateRule; DynBlockRule d_respRateRule; + DynBlockRule d_suffixMatchRule; NetmaskGroup d_excludedSubnets; + SuffixMatchNode d_excludedDomains; + smtVisitor_t d_smtVisitor; bool d_beQuiet{false}; }; diff --git a/pdns/dnsdist-lua-inspection.cc b/pdns/dnsdist-lua-inspection.cc index c64bf563f..dda84759c 100644 --- a/pdns/dnsdist-lua-inspection.cc +++ b/pdns/dnsdist-lua-inspection.cc @@ -660,6 +660,11 @@ void setupLuaInspection() group->setResponseByteRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None); } }); + g_lua.registerFunction::*)(unsigned int, const std::string&, unsigned int, boost::optional, DynBlockRulesGroup::smtVisitor_t)>("setSuffixMatchRule", [](std::shared_ptr& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional action, DynBlockRulesGroup::smtVisitor_t visitor) { + if (group) { + group->setSuffixMatchRule(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, visitor); + } + }); g_lua.registerFunction::*)(uint8_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional, boost::optional)>("setRCodeRate", [](std::shared_ptr& group, uint8_t rcode, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional action, boost::optional warningRate) { if (group) { group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None); @@ -690,6 +695,16 @@ void setupLuaInspection() group->includeRange(Netmask(*boost::get(&ranges))); } }); + g_lua.registerFunction::*)(boost::variant>>)>("excludeDomains", [](std::shared_ptr& group, boost::variant>> domains) { + if (domains.type() == typeid(std::vector>)) { + for (const auto& range : *boost::get>>(&domains)) { + group->excludeDomain(DNSName(range.second)); + } + } + else { + group->excludeDomain(DNSName(*boost::get(&domains))); + } + }); g_lua.registerFunction::*)()>("apply", [](std::shared_ptr& group) { group->apply(); }); diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 92c8632ce..6625d5b93 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -251,6 +251,7 @@ testrunner_SOURCES = \ sholder.hh \ sodcrypto.cc \ sstuff.hh \ + statnode.cc statnode.hh \ threadname.hh threadname.cc \ testrunner.cc \ xpf.cc xpf.hh diff --git a/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc b/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc index 560c222b2..b191a268a 100644 --- a/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc +++ b/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc @@ -10,6 +10,7 @@ Rings g_rings; GlobalStateHolder> g_dynblockNMG; +GlobalStateHolder> g_dynblockSMT; BOOST_AUTO_TEST_SUITE(dnsdistdynblocks_hh) diff --git a/pdns/statnode.hh b/pdns/statnode.hh index 39c20c0fc..ac8d78cb3 100644 --- a/pdns/statnode.hh +++ b/pdns/statnode.hh @@ -60,6 +60,10 @@ public: Stat print(unsigned int depth=0, Stat newstat=Stat(), bool silent=false) const; typedef boost::function visitor_t; void visit(visitor_t visitor, Stat& newstat, unsigned int depth=0) const; + bool empty() const + { + return children.empty() && s.remotes.empty(); + } typedef std::map children_t; children_t children; -- 2.40.0