]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - pdns/dnsdist-dynblocks.hh
auth: switch circleci mssql image
[thirdparty/pdns.git] / pdns / dnsdist-dynblocks.hh
index ca1aa0998751e014244492bb6e1bde5f98feb43b..5d9923fb07efafbd5b188d6fd767b53ce6b4a8ce 100644 (file)
  */
 #pragma once
 
+#include <unordered_set>
+
 #include "dolog.hh"
 #include "dnsdist-rings.hh"
+#include "statnode.hh"
+
+#include "dnsdist-lua-inspection-ffi.hh"
+
+// dnsdist_ffi_stat_node_t is a lightuserdata
+template<>
+struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*> {
+    static const int minSize = 1;
+    static const int maxSize = 1;
+
+    static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept {
+        lua_pushlightuserdata(state, ptr);
+        return PushedObject{state, 1};
+    }
+};
+
+typedef std::function<bool(dnsdist_ffi_stat_node_t*)> dnsdist_ffi_stat_node_visitor_t;
+
+struct dnsdist_ffi_stat_node_t
+{
+  dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_): node(node_), self(self_), children(children_)
+  {
+  }
+
+  const StatNode& node;
+  const StatNode::Stat& self;
+  const StatNode::Stat& children;
+};
 
 class DynBlockRulesGroup
 {
@@ -42,7 +72,7 @@ private:
     {
     }
 
-    DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int seconds, DNSAction::Action action): d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_seconds(seconds), d_action(action), d_enabled(true)
+    DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action): d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true)
     {
     }
 
@@ -74,6 +104,17 @@ private:
       return (count > limit);
     }
 
+    bool warningRateExceeded(unsigned int count, const struct timespec& now) const
+    {
+      if (d_warningRate == 0) {
+        return false;
+      }
+
+      double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
+      double limit = delta * d_warningRate;
+      return (count > limit);
+    }
+
     bool isEnabled() const
     {
       return d_enabled;
@@ -102,43 +143,68 @@ private:
     struct timespec d_minTime;
     unsigned int d_blockDuration{0};
     unsigned int d_rate{0};
+    unsigned int d_warningRate{0};
     unsigned int d_seconds{0};
     DNSAction::Action d_action{DNSAction::Action::None};
     bool d_enabled{false};
   };
 
-    typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
+  typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
 
 public:
   DynBlockRulesGroup()
   {
   }
 
-  void setQueryRate(unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+  void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
   {
-    d_queryRateRule = DynBlockRule(reason, blockDuration, rate, seconds, action);
+    d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
   }
 
-  void setResponseByteRate(unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+  /* rate is in bytes per second */
+  void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
   {
-    d_respRateRule = DynBlockRule(reason, blockDuration, rate, seconds, action);
+    d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
   }
 
-  void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+  void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
   {
     auto& entry = d_rcodeRules[rcode];
-    entry = DynBlockRule(reason, blockDuration, rate, seconds, action);
+    entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
   }
 
-  void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+  void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
   {
     auto& entry = d_qtypeRules[qtype];
-    entry = DynBlockRule(reason, blockDuration, rate, seconds, action);
+    entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+  }
+
+  typedef std::function<bool(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> 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 setSuffixMatchRuleFFI(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor)
+  {
+    d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
+    d_smtVisitorFFI = visitor;
   }
 
   void apply()
+  {
+    struct timespec now;
+    gettime(&now);
+
+    apply(now);
+  }
+
+  void apply(const struct timespec& now)
   {
     counts_t counts;
+    StatNode statNodeRoot;
 
     size_t entriesCount = 0;
     if (hasQueryRules()) {
@@ -149,42 +215,68 @@ public:
     }
     counts.reserve(entriesCount);
 
-    processQueryRules(counts);
-    processResponseRules(counts);
+    processQueryRules(counts, now);
+    processResponseRules(counts, statNodeRoot, now);
 
-    if (counts.empty()) {
+    if (counts.empty() && statNodeRoot.empty()) {
       return;
     }
 
     boost::optional<NetmaskTree<DynBlock> > blocks;
     bool updated = false;
-    struct timespec now;
-    gettime(&now);
 
     for (const auto& entry : counts) {
-      if (d_queryRateRule.rateExceeded(entry.second.queries, now)) {
-        addBlock(blocks, now, entry.first, d_queryRateRule, updated);
+      const auto& requestor = entry.first;
+      const auto& counters = entry.second;
+
+      if (d_queryRateRule.warningRateExceeded(counters.queries, now)) {
+        handleWarning(blocks, now, requestor, d_queryRateRule, updated);
+      }
+
+      if (d_queryRateRule.rateExceeded(counters.queries, now)) {
+        addBlock(blocks, now, requestor, d_queryRateRule, updated);
         continue;
       }
 
-      if (d_respRateRule.rateExceeded(entry.second.respBytes, now)) {
-        addBlock(blocks, now, entry.first, d_respRateRule, updated);
+      if (d_respRateRule.warningRateExceeded(counters.respBytes, now)) {
+        handleWarning(blocks, now, requestor, d_respRateRule, updated);
+      }
+
+      if (d_respRateRule.rateExceeded(counters.respBytes, now)) {
+        addBlock(blocks, now, requestor, d_respRateRule, updated);
         continue;
       }
 
-      for (const auto& rule : d_qtypeRules) {
-        const auto& typeIt = entry.second.d_qtypeCounts.find(rule.first);
-        if (typeIt != entry.second.d_qtypeCounts.cend() && rule.second.rateExceeded(typeIt->second, now)) {
-          addBlock(blocks, now, entry.first, rule.second, updated);
-          break;
+      for (const auto& pair : d_qtypeRules) {
+        const auto qtype = pair.first;
+
+        const auto& typeIt = counters.d_qtypeCounts.find(qtype);
+        if (typeIt != counters.d_qtypeCounts.cend()) {
+
+          if (pair.second.warningRateExceeded(typeIt->second, now)) {
+            handleWarning(blocks, now, requestor, pair.second, updated);
+          }
+
+          if (pair.second.rateExceeded(typeIt->second, now)) {
+            addBlock(blocks, now, requestor, pair.second, updated);
+            break;
+          }
         }
       }
 
-      for (const auto& rule : d_rcodeRules) {
-        const auto& rcodeIt = entry.second.d_rcodeCounts.find(rule.first);
-        if (rcodeIt != entry.second.d_rcodeCounts.cend() && rule.second.rateExceeded(rcodeIt->second, now)) {
-          addBlock(blocks, now, entry.first, rule.second, updated);
-          break;
+      for (const auto& pair : d_rcodeRules) {
+        const auto rcode = pair.first;
+
+        const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
+        if (rcodeIt != counters.d_rcodeCounts.cend()) {
+          if (pair.second.warningRateExceeded(rcodeIt->second, now)) {
+            handleWarning(blocks, now, requestor, pair.second, updated);
+          }
+
+          if (pair.second.rateExceeded(rcodeIt->second, now)) {
+            addBlock(blocks, now, requestor, pair.second, updated);
+            break;
+          }
         }
       }
     }
@@ -192,6 +284,38 @@ public:
     if (updated && blocks) {
       g_dynblockNMG.setState(*blocks);
     }
+
+    if (!statNodeRoot.empty()) {
+      StatNode::Stat node;
+      std::unordered_set<DNSName> namesToBlock;
+      statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
+                           bool block = false;
+
+                           if (d_smtVisitorFFI) {
+                             dnsdist_ffi_stat_node_t tmp(*node_, self, children);
+                             block = d_smtVisitorFFI(&tmp);
+                           }
+                           else {
+                             block = d_smtVisitor(*node_, self, children);
+                           }
+
+                           if (block) {
+                             namesToBlock.insert(DNSName(node_->fullname));
+                           }
+                         },
+        node);
+
+      if (!namesToBlock.empty()) {
+        updated = false;
+        SuffixMatchTree<DynBlock> 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)
@@ -204,12 +328,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;
@@ -219,10 +349,16 @@ 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();
   }
 
+  void setQuiet(bool quiet)
+  {
+    d_beQuiet = quiet;
+  }
+
 private:
   bool checkIfQueryTypeMatches(const Rings::Query& query)
   {
@@ -244,7 +380,7 @@ private:
     return rule->second.matches(response.when);
   }
 
-  void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
+  void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning)
   {
     if (d_excludedSubnets.match(requestor)) {
       /* do not add a block for excluded subnets */
@@ -259,11 +395,23 @@ private:
     unsigned int count = 0;
     const auto& got = blocks->lookup(Netmask(requestor));
     bool expired = false;
+    bool wasWarning = false;
+
     if (got) {
-      if (until < got->second.until) {
-        // had a longer policy
+      if (warning && !got->second.warning) {
+        /* we have an existing entry which is not a warning,
+           don't override it */
         return;
       }
+      else if (!warning && got->second.warning) {
+        wasWarning = true;
+      }
+      else {
+        if (until < got->second.until) {
+          // had a longer policy
+          return;
+        }
+      }
 
       if (now < got->second.until) {
         // only inherit count on fresh query we are extending
@@ -274,15 +422,64 @@ private:
       }
     }
 
-    DynBlock db{rule.d_blockReason, until, DNSName(), rule.d_action};
+    DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
     db.blocks = count;
-    if (!got || expired) {
-      warnlog("Inserting dynamic block for %s for %d seconds: %s", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+    db.warning = warning;
+    if (!d_beQuiet && (!got || expired || wasWarning)) {
+      warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
     }
     blocks->insert(Netmask(requestor)).second = db;
     updated = true;
   }
 
+  void 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;
+    }
+
+    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<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
+  {
+    addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
+  }
+
+  void handleWarning(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
+  {
+    addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
+  }
+
   bool hasQueryRules() const
   {
     return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
@@ -293,19 +490,22 @@ private:
     return d_respRateRule.isEnabled() || !d_rcodeRules.empty();
   }
 
+  bool hasSuffixMatchRules() const
+  {
+    return d_suffixMatchRule.isEnabled();
+  }
+
   bool hasRules() const
   {
     return hasQueryRules() || hasResponseRules();
   }
 
-  void processQueryRules(counts_t& counts)
+  void processQueryRules(counts_t& counts, const struct timespec& now)
   {
     if (!hasQueryRules()) {
       return;
     }
 
-    struct timespec now;
-    gettime(&now);
     d_queryRateRule.d_cutOff = d_queryRateRule.d_minTime = now;
     d_queryRateRule.d_cutOff.tv_sec -= d_queryRateRule.d_seconds;
 
@@ -337,17 +537,18 @@ private:
     }
   }
 
-  void processResponseRules(counts_t& counts)
+  void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now)
   {
-    if (!hasResponseRules()) {
+    if (!hasResponseRules() && !hasSuffixMatchRules()) {
       return;
     }
 
-    struct timespec now;
-    gettime(&now);
     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;
@@ -361,6 +562,7 @@ private:
         }
 
         bool respRateMatches = d_respRateRule.matches(c.when);
+        bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when);
         bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
 
         if (respRateMatches || rcodeRuleMatches) {
@@ -372,6 +574,10 @@ private:
             entry.d_rcodeCounts[c.dh.rcode]++;
           }
         }
+
+        if (suffixMatchRuleMatches) {
+          root.submit(c.name, c.dh.rcode, boost::none);
+        }
       }
     }
   }
@@ -380,5 +586,10 @@ private:
   std::map<uint16_t, DynBlockRule> d_qtypeRules;
   DynBlockRule d_queryRateRule;
   DynBlockRule d_respRateRule;
+  DynBlockRule d_suffixMatchRule;
   NetmaskGroup d_excludedSubnets;
+  SuffixMatchNode d_excludedDomains;
+  smtVisitor_t d_smtVisitor;
+  dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
+  bool d_beQuiet{false};
 };