]> granicus.if.org Git - pdns/commitdiff
dnsdist: Remove 'expired' states from MaxQPSIPRule
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 16 May 2018 13:47:35 +0000 (15:47 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 25 May 2018 14:11:38 +0000 (16:11 +0200)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-rules.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/rules-actions.rst

index be71a5f869b999316064409af69137193642c149..f8134fff48df0db5256ba917a7cb5ddcce2d8e5f 100644 (file)
@@ -345,7 +345,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
   { "LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
   { "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" },
-  { "MaxQPSIPRule", true, "qps, v4Mask=32, v6Mask=64, burst=qps", "matches traffic exceeding the qps limit per subnet" },
+  { "MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60]]]]]", "matches traffic exceeding the qps limit per subnet" },
   { "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" },
   { "mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
   { "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
index e79a05f7c183a74ce9af4c734e6862b2e78e32d0..437b47b4ec717c1bd3331e02b8dc4938c15fe3bd 100644 (file)
 class MaxQPSIPRule : public DNSRule
 {
 public:
-  MaxQPSIPRule(unsigned int qps, unsigned int burst, unsigned int ipv4trunc=32, unsigned int ipv6trunc=64:
-    d_qps(qps), d_burst(burst), d_ipv4trunc(ipv4trunc), d_ipv6trunc(ipv6trunc)
+  MaxQPSIPRule(unsigned int qps, unsigned int burst, unsigned int ipv4trunc=32, unsigned int ipv6trunc=64, unsigned int expiration=300, unsigned int cleanupDelay=60):
+    d_qps(qps), d_burst(burst), d_ipv4trunc(ipv4trunc), d_ipv6trunc(ipv6trunc), d_cleanupDelay(cleanupDelay), d_expiration(expiration)
   {
     pthread_rwlock_init(&d_lock, 0);
+    gettime(&d_lastCleanup, true);
+  }
+
+  void cleanupIfNeeded(const struct timespec& now) const
+  {
+    if (d_cleanupDelay > 0) {
+      struct timespec cutOff = d_lastCleanup;
+      cutOff.tv_sec += d_cleanupDelay;
+
+      if (cutOff < now) {
+        WriteLock w(&d_lock);
+
+        /* the QPS Limiter doesn't use realtime, be careful! */
+        gettime(&cutOff, false);
+        cutOff.tv_sec -= d_expiration;
+
+        for (auto entry = d_limits.begin(); entry != d_limits.end(); ) {
+          if (!entry->second.seenSince(cutOff)) {
+            entry = d_limits.erase(entry);
+          }
+          else {
+            ++entry;
+          }
+        }
+
+        d_lastCleanup = now;
+      }
+    }
   }
 
   bool matches(const DNSQuestion* dq) const override
   {
+    cleanupIfNeeded(*dq->queryTime);
+
     ComboAddress zeroport(*dq->remote);
     zeroport.sin4.sin_port=0;
     zeroport.truncate(zeroport.sin4.sin_family == AF_INET ? d_ipv4trunc : d_ipv6trunc);
@@ -43,16 +73,17 @@ public:
       ReadLock r(&d_lock);
       const auto iter = d_limits.find(zeroport);
       if (iter != d_limits.end()) {
-        return !iter->second.check();
+        return !iter->second.check(d_qps, d_burst);
       }
     }
     {
       WriteLock w(&d_lock);
+
       auto iter = d_limits.find(zeroport);
       if(iter == d_limits.end()) {
         iter=d_limits.insert({zeroport,QPSLimiter(d_qps, d_burst)}).first;
       }
-      return !iter->second.check();
+      return !iter->second.check(d_qps, d_burst);
     }
   }
 
@@ -64,9 +95,9 @@ public:
 
 private:
   mutable pthread_rwlock_t d_lock;
-  mutable std::map<ComboAddress, QPSLimiter> d_limits;
-  unsigned int d_qps, d_burst, d_ipv4trunc, d_ipv6trunc;
-
+  mutable std::map<ComboAddress, BasicQPSLimiter> d_limits;
+  mutable struct timespec d_lastCleanup;
+  unsigned int d_qps, d_burst, d_ipv4trunc, d_ipv6trunc, d_cleanupDelay, d_expiration;
 };
 
 class MaxQPSRule : public DNSRule
@@ -1097,8 +1128,8 @@ void setupLuaRules()
         });
     });
 
-  g_lua.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<int> ipv4trunc, boost::optional<int> ipv6trunc, boost::optional<int> burst) {
-      return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, burst.get_value_or(qps), ipv4trunc.get_value_or(32), ipv6trunc.get_value_or(64)));
+  g_lua.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<int> ipv4trunc, boost::optional<int> ipv6trunc, boost::optional<int> burst, boost::optional<unsigned int> expiration, boost::optional<unsigned int> cleanupDelay) {
+      return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, burst.get_value_or(qps), ipv4trunc.get_value_or(32), ipv6trunc.get_value_or(64), expiration.get_value_or(300), cleanupDelay.get_value_or(60)));
     });
 
   g_lua.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional<int> burst) {
index 96a050b8dea1db310a1f031d9bf58fceda5f0c8d..d020d0b447510ff679b4de1d21ca5036c81bbd1a 100644 (file)
@@ -272,28 +272,69 @@ struct StopWatch
 
 };
 
-class QPSLimiter
+class BasicQPSLimiter
 {
 public:
-  QPSLimiter()
+  BasicQPSLimiter()
   {
   }
 
-  QPSLimiter(unsigned int rate, unsigned int burst) : d_rate(rate), d_burst(burst), d_tokens(burst)
+  BasicQPSLimiter(unsigned int rate, unsigned int burst): d_tokens(burst)
+  {
+    d_prev.start();
+  }
+
+  bool check(unsigned int rate, unsigned int burst) const // this is not quite fair
+  {
+    auto delta = d_prev.udiffAndSet();
+
+    d_tokens += 1.0 * rate * (delta/1000000.0);
+
+    if(d_tokens > burst) {
+      d_tokens = burst;
+    }
+
+    bool ret=false;
+    if(d_tokens >= 1.0) { // we need this because burst=1 is weird otherwise
+      ret=true;
+      --d_tokens;
+    }
+
+    return ret;
+  }
+
+  bool seenSince(const struct timespec& cutOff) const
+  {
+    return cutOff < d_prev.d_start;
+  }
+
+protected:
+  mutable StopWatch d_prev;
+  mutable double d_tokens;
+};
+
+class QPSLimiter : public BasicQPSLimiter
+{
+public:
+  QPSLimiter(): BasicQPSLimiter()
+  {
+  }
+
+  QPSLimiter(unsigned int rate, unsigned int burst): BasicQPSLimiter(rate, burst), d_rate(rate), d_burst(burst), d_passthrough(false)
   {
-    d_passthrough=false;
     d_prev.start();
   }
 
   unsigned int getRate() const
   {
-    return d_passthrough? 0 : d_rate;
+    return d_passthrough ? 0 : d_rate;
   }
 
   int getPassed() const
   {
     return d_passed;
   }
+
   int getBlocked() const
   {
     return d_blocked;
@@ -301,34 +342,26 @@ public:
 
   bool check() const // this is not quite fair
   {
-    if(d_passthrough)
+    if (d_passthrough) {
       return true;
-    auto delta = d_prev.udiffAndSet();
-
-    d_tokens += 1.0*d_rate * (delta/1000000.0);
-
-    if(d_tokens > d_burst)
-      d_tokens = d_burst;
+    }
 
-    bool ret=false;
-    if(d_tokens >= 1.0) { // we need this because burst=1 is weird otherwise
-      ret=true;
-      --d_tokens;
+    bool ret = BasicQPSLimiter::check(d_rate, d_burst);
+    if (ret) {
       d_passed++;
     }
-    else
+    else {
       d_blocked++;
+    }
 
     return ret;
   }
 private:
-  bool d_passthrough{true};
-  unsigned int d_rate;
-  unsigned int d_burst;
-  mutable double d_tokens;
-  mutable StopWatch d_prev;
   mutable unsigned int d_passed{0};
   mutable unsigned int d_blocked{0};
+  unsigned int d_rate;
+  unsigned int d_burst;
+  bool d_passthrough{true};
 };
 
 struct ClientState;
index 28850879699eccf01afdf6d24eb92b9b15c5b6bf..58f71ecea4b3f046e593362c2e897052a556a05a 100644 (file)
@@ -537,14 +537,20 @@ These ``DNSRule``\ s be one of the following items:
 
   Matches queries with the DO flag set
 
-.. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst]]])
+.. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay]]]]])
+  .. versionchanged:: 1.3.1
+    Added the optional parameters ``expiration`` and ``cleanupDelay``.
 
-  Matches traffic for a subnet specified by ``v4Mask`` or ``v6Mask`` exceeding ``qps`` queries per second up to ``burst`` allowed
+  Matches traffic for a subnet specified by ``v4Mask`` or ``v6Mask`` exceeding ``qps`` queries per second up to ``burst`` allowed.
+  This rule keeps track of QPS by netmask or source IP. This state is cleaned up regularly if  ``cleanupDelay`` is greater than zero,
+  removing existing netmasks or IP addresses that have not been seen in the last ``expiration`` seconds.
 
   :param int qps: The number of queries per second allowed, above this number traffic is matched
   :param int v4Mask: The IPv4 netmask to match on. Default is 32 (the whole address)
   :param int v6Mask: The IPv6 netmask to match on. Default is 64
   :param int burst: The number of burstable queries per second allowed. Default is same as qps
+  :param int expiration: How long to keep netmask or IP addresses after they have last been seen, in seconds. Default is 300
+  :param int cleanupDelay: The number of seconds between two cleanups. Default is 60
 
 .. function:: MaxQPSRule(qps)