]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
General rate limit on taskqueue tasks
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Wed, 16 Feb 2022 09:31:31 +0000 (10:31 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Thu, 17 Feb 2022 14:47:55 +0000 (15:47 +0100)
pdns/recursordist/rec-taskqueue.cc
pdns/recursordist/rec-taskqueue.hh
pdns/recursordist/taskqueue.cc
pdns/recursordist/taskqueue.hh
pdns/recursordist/test-syncres_cc2.cc
pdns/recursordist/test-syncres_cc3.cc
pdns/recursordist/test-syncres_cc7.cc
pdns/syncres.cc

index 4e2f5f1f233111eb71cf48e204b469f6b036898c..f59a3505150c596ee8af021e2b238aab9dc3b319 100644 (file)
 #include "stat_t.hh"
 #include "syncres.hh"
 
-struct Queues
+// For rate lmiting, we maintain a set of tasks recently submitted.
+class TimedSet
+{
+public:
+  TimedSet(time_t t) :
+    d_expiry_seconds(t)
+  {
+  }
+  bool insert(time_t now, const pdns::ResolveTask& task)
+  {
+    time_t ttd = now + d_expiry_seconds;
+    bool inserted = d_set.emplace(task, ttd).second;
+    if (!inserted) {
+      // Instead of a periodic clean, we always do it on a hit
+      // the operation should be cheap as we just walk the ordered time_t index
+      // There is a slim chance if we never hit a rate limiting case we'll never clean... oh well
+      auto& ind = d_set.template get<time_t>();
+      auto it = ind.begin();
+      bool erased = false;
+      while (it != ind.end()) {
+        if (it->d_ttd < now) {
+          erased = true;
+          it = ind.erase(it);
+        }
+        else {
+          break;
+        }
+      }
+      // Try again if the loop deleted at least one entry
+      if (erased) {
+        inserted = d_set.emplace(task, ttd).second;
+      }
+    }
+    return inserted;
+  }
+
+private:
+  struct Entry
+  {
+    Entry(const pdns::ResolveTask& task, time_t ttd) :
+      d_task(task), d_ttd(ttd) {}
+    pdns::ResolveTask d_task;
+    time_t d_ttd;
+  };
+
+  typedef multi_index_container<Entry,
+                                indexed_by<
+                                  ordered_unique<tag<pdns::ResolveTask>, member<Entry, pdns::ResolveTask, &Entry::d_task>>,
+                                  ordered_non_unique<tag<time_t>, member<Entry, time_t, &Entry::d_ttd>>>>
+    timed_set_t;
+  timed_set_t d_set;
+  time_t d_expiry_seconds;
+};
+
+struct Queue
 {
   pdns::TaskQueue queue;
-  std::set<pdns::ResolveTask> running;
+  TimedSet rateLimitSet{60};
 };
-static LockGuarded<Queues> s_taskQueue;
+static LockGuarded<Queue> s_taskQueue;
 
 struct taskstats
 {
@@ -55,7 +109,7 @@ static void resolve(const struct timeval& now, bool logErrors, const pdns::Resol
     log->info(Logr::Debug, "resolving");
     int res = sr.beginResolve(task.d_qname, QType(task.d_qtype), QClass::IN, ret);
     ex = false;
-    log->info(Logr::Debug, "done", "rcode", Logging::Loggable(res), "records",  Logging::Loggable(ret.size()));
+    log->info(Logr::Debug, "done", "rcode", Logging::Loggable(res), "records", Logging::Loggable(ret.size()));
   }
   catch (const std::exception& e) {
     log->error(Logr::Error, msg, e.what());
@@ -103,10 +157,8 @@ void runTaskOnce(bool logErrors)
       return;
     }
     task = lock->queue.pop();
-    lock->running.insert(task);
   }
   bool expired = task.run(logErrors);
-  s_taskQueue.lock()->running.erase(task);
   if (expired) {
     s_taskQueue.lock()->queue.incExpired();
   }
@@ -116,7 +168,7 @@ void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline
 {
   pdns::ResolveTask task{qname, qtype, deadline, true, resolve};
   auto lock = s_taskQueue.lock();
-  bool running = lock->running.count(task) > 0;
+  bool running = !lock->rateLimitSet.insert(time(nullptr), task);
   if (!running) {
     ++s_almost_expired_tasks.pushed;
     lock->queue.push(std::move(task));
@@ -127,7 +179,7 @@ void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t deadline)
 {
   pdns::ResolveTask task{qname, qtype, deadline, false, resolve};
   auto lock = s_taskQueue.lock();
-  bool running = lock->running.count(task) > 0;
+  bool running = !lock->rateLimitSet.insert(time(nullptr), task);
   if (!running) {
     ++s_resolve_tasks.pushed;
     lock->queue.push(std::move(task));
index fe1fea6bfc5679fe342eb40dec8820b732b50965..b33e2abc51ec1cb5c61fdad72c2e254dfe4e6061 100644 (file)
  */
 #pragma once
 
-#include "dnsname.hh"
+#include <cstdint>
+#include <time.h>
+
+class DNSName;
 
 void runTaskOnce(bool logErrors);
 void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline);
index 7e810015b22952f79301d9cccea6bca511a2b48c..600bec3b9c82bccf101d16ce91bd8d8417b4a0db 100644 (file)
@@ -46,7 +46,7 @@ ResolveTask TaskQueue::pop()
 
 bool ResolveTask::run(bool logErrors)
 {
-  if (!d_func) {
+  if (d_func == nullptr) {
     auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(d_qname), "qtype", Logging::Loggable(QType(d_qtype).toString()));
     log->error(Logr::Debug, "null task");
     return false;
index a457823138254d1c521bbeaba59656b5bd0b4d24..abdd768439c403123a969e507ba0ac1e0aea1103 100644 (file)
@@ -45,11 +45,12 @@ struct ResolveTask
   uint16_t d_qtype;
   time_t d_deadline;
   bool d_refreshMode; // Whether to run this task in regular mode (false) or in the mode that refreshes almost expired tasks
-  std::function<void(const struct timeval& now, bool logErrors, const ResolveTask& task)> d_func;
+  // Use a function ponter as comparing std::functions is a nuisance
+  void (*d_func)(const struct timeval& now, bool logErrors, const ResolveTask& task);
 
   bool operator<(const ResolveTask& a) const
   {
-    return std::tie(d_qname, d_qtype, d_refreshMode) < std::tie(d_qname, d_qtype, d_refreshMode);
+    return std::tie(d_qname, d_qtype, d_refreshMode, d_func) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func);
   }
   bool run(bool logErrors);
 };
index 15d53bf7fe910725bbf19fa77e7871119122a54a..8e46deb05fd6c6d4c1b36953f0886710fbe8c6f2 100644 (file)
@@ -311,10 +311,6 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_with_non_resolving)
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
   BOOST_CHECK_EQUAL(SyncRes::getNonResolvingNSSize(), 2U);
 
-  // Originally empty NoData results where not cached, now they are
-  BOOST_CHECK_EQUAL(g_negCache->size(), 4U);
-  g_negCache->clear();
-
   // Again, should not change anything
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::ServFail);
@@ -324,10 +320,6 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_with_non_resolving)
 
   BOOST_CHECK_EQUAL(SyncRes::getNonResolvingNSSize(), 2U);
 
-  // Originally empty NoData results where not cached, now they are
-  BOOST_CHECK_EQUAL(g_negCache->size(), 4U);
-  g_negCache->clear();
-
   // Again, but now things should start working because of the queryCounter getting high enough
   // and one entry remains in the non-resolving cache
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
index 29f881d80977d2078b177285e16a71d2391287f7..bcd6ddbb2324b21cdcb2a3ed9ee1b0b6f8ccbca7 100644 (file)
@@ -1210,8 +1210,8 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_nodata_bogus)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusMissingNegativeIndication);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
-  /* we don store empty results */
-  BOOST_CHECK_EQUAL(queriesCount, 3U);
+  /* we don't store empty results */
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
 BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
index 16cb4e32ad23bdefef48869bccf3c4a0f2743ddd..7d4484175ff5631628e58eb96c8ad02957473712 100644 (file)
@@ -1416,8 +1416,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusMissingNegativeIndication);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
-  /* we do store empty results */
-  BOOST_CHECK_EQUAL(queriesCount, 3U);
+  /* we don't store empty results */
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
index bb39a508224b8c12c5cace67f1d8ad0705262df4..11973c56e40e1691d3f973a3e7fb87d6e5971b0f 100644 (file)
@@ -3642,18 +3642,6 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
   uint32_t dnameTTL = 0;
   bool referralOnDS = false;
 
-  if (lwr.d_rcode == 0 && qtype.getCode() != 0 && lwr.d_records.size() == 0) {
-    // NODATA and no SOA, put that into the negcache for a while
-    NegCache::NegCacheEntry ne;
-    ne.d_auth = auth;
-    ne.d_name = qname;
-    ne.d_qtype = qtype;
-    ne.d_ttd = d_now.tv_sec + std::max(s_minimumTTL, 60U);
-    ne.d_validationState = vState::BogusMissingNegativeIndication;
-    LOG(prefix<<qname<<": got an completely empty response for"<<qname<<"/"<<qtype<<", putting into negcache"<<endl);
-    g_negCache->add(ne);
-  }
-
   for (auto& rec : lwr.d_records) {
     if (rec.d_type != QType::OPT && rec.d_class != QClass::IN) {
       continue;