]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Tie force refresh mode into syncres and tasks
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Thu, 26 Mar 2026 14:07:14 +0000 (15:07 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 27 Jul 2026 10:19:07 +0000 (12:19 +0200)
Signed-off-by: Otto Moerbeek <otto.moerbeek@open-xchange.com>
pdns/recursordist/rec-taskqueue.cc
pdns/recursordist/rec-taskqueue.hh
pdns/recursordist/recursor_cache.cc
pdns/recursordist/syncres.cc
pdns/recursordist/syncres.hh
pdns/recursordist/taskqueue.hh

index eac312eed20532e9f34d0a4696040b32afe2e021..f0783763242ffaed655b943fbba3395ad40192e3 100644 (file)
@@ -120,7 +120,8 @@ static void resolveInternal(const struct timeval& now, bool logErrors, const pdn
   auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "netmask", Logging::Loggable(task.d_netmask.empty() ? "" : task.d_netmask.toString()));
   const string msg = "Exception while running a background ResolveTask";
   SyncRes resolver(now);
-  resolver.setRefreshAlmostExpired(task.d_refreshMode);
+  resolver.setRefreshAlmostExpired(task.d_refreshMode != pdns::ResolveTask::RefreshMode::None);
+  resolver.setForcedRefresh(task.d_refreshMode == pdns::ResolveTask::RefreshMode::Forced);
   resolver.setQuerySource(task.d_netmask);
   if (forceNoQM) {
     resolver.setQNameMinimization(false);
@@ -248,14 +249,14 @@ bool runTaskOnce(bool logErrors)
   return true;
 }
 
-void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask)
+void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask, bool force)
 {
   if (SyncRes::isUnsupported(qtype)) {
     auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()), "netmask", Logging::Loggable(netmask.empty() ? "" : netmask.toString()));
     log->error(Logr::Error, "Cannot push task", "qtype unsupported");
     return;
   }
-  pdns::ResolveTask task{qname, qtype, deadline, true, resolve, {}, {}, netmask};
+  pdns::ResolveTask task{qname, qtype, deadline, force ? pdns::ResolveTask::ResolveTask::Forced : pdns::ResolveTask::RefreshMode::Refresh, resolve, {}, {}, netmask};
   if (s_taskQueue.lock()->queue.push(std::move(task))) {
     ++s_almost_expired_tasks.pushed;
   }
@@ -269,7 +270,7 @@ void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t de
     return;
   }
   auto func = forceQMOff ? resolveForceNoQM : resolve;
-  pdns::ResolveTask task{qname, qtype, deadline, false, func, {}, {}, {}};
+  pdns::ResolveTask task{qname, qtype, deadline, pdns::ResolveTask::RefreshMode::None, func, {}, {}, {}};
   auto lock = s_taskQueue.lock();
   bool inserted = lock->rateLimitSet.insert(now, task);
   if (inserted) {
@@ -287,7 +288,7 @@ bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip
     return false;
   }
 
-  pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ipAddress, nsname, {}};
+  pdns::ResolveTask task{qname, qtype, deadline, pdns::ResolveTask::RefreshMode::None, tryDoT, ipAddress, nsname, {}};
   bool pushed = s_taskQueue.lock()->queue.push(std::move(task));
   if (pushed) {
     ++s_almost_expired_tasks.pushed;
index e7bc855bd761a97eb0edb6aef8f142dc113d4fd1..e1a30661765c8dd3b7f14eff2d2b5d8fd7b0c4ca 100644 (file)
@@ -35,7 +35,7 @@ struct ResolveTask;
 }
 void runTasks(size_t max, bool logErrors);
 bool runTaskOnce(bool logErrors);
-void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask);
+void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask, bool force = false);
 void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline, bool forceQMOff);
 bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ipAddress, time_t deadline, const DNSName& nsname);
 void taskQueueClear();
index 8beaf2f9d1f0792313d8bd3dba464148c3b5e6aa..ef3481d3458508fe595504eeb54baa6d912812e6 100644 (file)
@@ -421,7 +421,7 @@ time_t MemRecursorCache::fakeTTD(MemRecursorCache::OrderedTagIterator_t& entry,
     const uint32_t deadline = forcedRefresh(flags) ? origTTL / 2 : origTTL * SyncRes::s_refresh_ttlperc / 100;
     // coverity[store_truncates_time_t]
     const bool almostExpired = static_cast<uint32_t>(ttl) <= deadline;
-    if (almostExpired && qname != g_rootdnsname) {
+    if (almostExpired /* && qname != g_rootdnsname */) {
       if (refresh(flags)) {
         return -1;
       }
index 96c8166ccbcb202dcc2f4c4cb3b2403a2859de2d..3c692271d2728b4606f1a7e1657b9244162fac02 100644 (file)
@@ -1861,134 +1861,134 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
     if (d_serveStale) {
       LOG(prefix << qname << ": Restart, with serve-stale enabled" << endl);
     }
-    // This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
-    if (!d_updatingRootNS || qtype.getCode() != QType::NS || !qname.isRoot()) {
-      DNSName authname(qname);
-      const auto iter = getBestAuthZone(&authname);
-
-      if (d_cacheonly) {
-        if (iter != t_sstorage.domainmap->end()) {
-          if (iter->second.isAuth()) {
-            LOG(prefix << qname << ": Cache only lookup for '" << qname << "|" << qtype << "', in auth zone" << endl);
-            ret.clear();
-            d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, prefix, res);
-            if (fromCache != nullptr) {
-              *fromCache = d_wasOutOfBand;
-            }
-            return res;
+
+    // Originally this was all skipped for root refresh cases, but we now have a generic solution
+    // for that via forcedRefresh
+    DNSName authname(qname);
+    const auto iter = getBestAuthZone(&authname);
+
+    if (d_cacheonly) {
+      if (iter != t_sstorage.domainmap->end()) {
+        if (iter->second.isAuth()) {
+          LOG(prefix << qname << ": Cache only lookup for '" << qname << "|" << qtype << "', in auth zone" << endl);
+          ret.clear();
+          d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, prefix, res);
+          if (fromCache != nullptr) {
+            *fromCache = d_wasOutOfBand;
           }
+          return res;
         }
       }
+    }
 
-      bool wasForwardedOrAuthZone = false;
-      bool wasAuthZone = false;
-      bool wasForwardRecurse = false;
+    bool wasForwardedOrAuthZone = false;
+    bool wasAuthZone = false;
+    bool wasForwardRecurse = false;
 
-      if (iter != t_sstorage.domainmap->end()) {
-        wasForwardedOrAuthZone = true;
+    if (iter != t_sstorage.domainmap->end()) {
+      wasForwardedOrAuthZone = true;
 
-        if (iter->second.isAuth()) {
-          wasAuthZone = true;
-        }
-        else if (iter->second.shouldRecurse()) {
-          wasForwardRecurse = true;
-        }
+      if (iter->second.isAuth()) {
+        wasAuthZone = true;
+      }
+      else if (iter->second.shouldRecurse()) {
+        wasForwardRecurse = true;
       }
+    }
 
-      /* When we are looking for a DS, we want to the non-CNAME cache check first
-         because we can actually have a DS (from the parent zone) AND a CNAME (from
-         the child zone), and what we really want is the DS */
-      if (qtype != QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse, loop == 1)) { // will reroute us if needed
-        d_wasOutOfBand = wasAuthZone;
-        // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
-        // are in QM Step0) we might have a CNAME but not the corresponding target.
-        // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
-        // we will get the records from the cache, resulting in a small overhead.
-        // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
-        // RPZ rules will not be evaluated anymore (we already matched).
-        bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
-        if (stoppedByPolicyHit && d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom && d_appliedPolicy.d_custom) {
-          // if the custom RPZ record was a CNAME we still need a full chase
-          // tested by unit test test_following_cname_chain_with_rpz
-          if (!d_appliedPolicy.d_custom->empty() && d_appliedPolicy.d_custom->at(0)->getType() == QType::CNAME) {
-            stoppedByPolicyHit = false;
-          }
-        }
-        if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
-          *fromCache = true;
+    /* When we are looking for a DS, we want to the non-CNAME cache check first
+       because we can actually have a DS (from the parent zone) AND a CNAME (from
+       the child zone), and what we really want is the DS */
+    if (qtype != QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse, loop == 1)) { // will reroute us if needed
+      d_wasOutOfBand = wasAuthZone;
+      // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
+      // are in QM Step0) we might have a CNAME but not the corresponding target.
+      // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
+      // we will get the records from the cache, resulting in a small overhead.
+      // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
+      // RPZ rules will not be evaluated anymore (we already matched).
+      bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+      if (stoppedByPolicyHit && d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom && d_appliedPolicy.d_custom) {
+        // if the custom RPZ record was a CNAME we still need a full chase
+        // tested by unit test test_following_cname_chain_with_rpz
+        if (!d_appliedPolicy.d_custom->empty() && d_appliedPolicy.d_custom->at(0)->getType() == QType::CNAME) {
+          stoppedByPolicyHit = false;
         }
-        /* Apply Post filtering policies */
-
-        if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
-          auto luaLocal = g_luaconfs.getLocal();
-          if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
-            mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-            bool done = false;
-            handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-            if (done && fromCache != nullptr) {
-              *fromCache = true;
-            }
+      }
+      if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
+        *fromCache = true;
+      }
+      /* Apply Post filtering policies */
+
+      if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+        auto luaLocal = g_luaconfs.getLocal();
+        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          bool done = false;
+          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+          if (done && fromCache != nullptr) {
+            *fromCache = true;
           }
         }
-        // This handles the case mentioned above: if the full CNAME chain leading to the answer was
-        // constructed from the cache, indicate that.
-        if (fromCache != nullptr && !*fromCache && haveFinalAnswer(qname, qtype, res, ret)) {
-          *fromCache = true;
-        }
-        return res;
       }
+      // This handles the case mentioned above: if the full CNAME chain leading to the answer was
+      // constructed from the cache, indicate that.
+      if (fromCache != nullptr && !*fromCache && haveFinalAnswer(qname, qtype, res, ret)) {
+        *fromCache = true;
+      }
+      return res;
+    }
 
-      if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, prefix, res, context)) {
-        // we done
-        d_wasOutOfBand = wasAuthZone;
-        if (fromCache != nullptr) {
-          *fromCache = true;
-        }
+    if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, prefix, res, context)) {
+      // we done
+      d_wasOutOfBand = wasAuthZone;
+      if (fromCache != nullptr) {
+        *fromCache = true;
+      }
 
-        if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
-          auto luaLocal = g_luaconfs.getLocal();
-          if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
-            mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-            bool done = false;
-            handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-          }
+      if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+        auto luaLocal = g_luaconfs.getLocal();
+        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          bool done = false;
+          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
         }
-
-        return res;
       }
 
-      /* if we have not found a cached DS (or denial of), now is the time to look for a CNAME */
-      if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse, loop == 1)) { // will reroute us if needed
-        d_wasOutOfBand = wasAuthZone;
-        // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
-        // are in QM Step0) we might have a CNAME but not the corresponding target.
-        // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
-        // we will get the records from the cache, resulting in a small overhead.
-        // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
-        // RPZ rules will not be evaluated anymore (we already matched).
-        const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+      return res;
+    }
 
-        if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
-          *fromCache = true;
-        }
-        /* Apply Post filtering policies */
-
-        if (d_wantsRPZ && !stoppedByPolicyHit) {
-          auto luaLocal = g_luaconfs.getLocal();
-          if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
-            mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-            bool done = false;
-            handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-            if (done && fromCache != nullptr) {
-              *fromCache = true;
-            }
+    /* if we have not found a cached DS (or denial of), now is the time to look for a CNAME */
+    if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse, loop == 1)) { // will reroute us if needed
+      d_wasOutOfBand = wasAuthZone;
+      // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
+      // are in QM Step0) we might have a CNAME but not the corresponding target.
+      // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
+      // we will get the records from the cache, resulting in a small overhead.
+      // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
+      // RPZ rules will not be evaluated anymore (we already matched).
+      const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+
+      if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
+        *fromCache = true;
+      }
+      /* Apply Post filtering policies */
+
+      if (d_wantsRPZ && !stoppedByPolicyHit) {
+        auto luaLocal = g_luaconfs.getLocal();
+        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          bool done = false;
+          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+          if (done && fromCache != nullptr) {
+            *fromCache = true;
           }
         }
-        if (fromCache != nullptr && !*fromCache && haveFinalAnswer(qname, qtype, res, ret)) {
-          *fromCache = true;
-        }
-        return res;
       }
+      if (fromCache != nullptr && !*fromCache && haveFinalAnswer(qname, qtype, res, ret)) {
+        *fromCache = true;
+      }
+      return res;
     }
 
     if (d_cacheonly) {
@@ -2505,6 +2505,9 @@ bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<
   if (d_refresh) {
     flags |= MemRecursorCache::Refresh;
   }
+  if (d_forcedRefresh) {
+    flags |= MemRecursorCache::ForcedRefresh;
+  }
   if (d_serveStale) {
     flags |= MemRecursorCache::ServeStale;
   }
@@ -2957,6 +2960,9 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
   if (d_refresh) {
     flags |= MemRecursorCache::Refresh;
   }
+  if (d_forcedRefresh) {
+    flags |= MemRecursorCache::ForcedRefresh;
+  }
 
   MemRecursorCache::Extra extra;
   if (g_recCache->get(d_now.tv_sec, sqname, sqt, flags, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth, nullptr, &extra) > 0) {
@@ -6351,6 +6357,7 @@ int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigne
   resolver.setUpdatingRootNS();
   resolver.setAsyncCallback(std::move(asyncCallback));
   resolver.setRefreshAlmostExpired(true);
+  resolver.setForcedRefresh(true);
 
   const string msg = "Failed to update . records";
   vector<DNSRecord> ret;
index c66110fb01591d4b75d4314cd807d1babc3838b3..af49496dd3a4e53e672bd59cb561d2c199ec853a 100644 (file)
@@ -235,7 +235,7 @@ public:
   struct EDNSStatus
   {
     EDNSStatus(const ComboAddress& arg) :
-      address(arg) {}
+      address(arg) { }
     ComboAddress address;
     time_t ttd{0};
     enum EDNSMode : uint8_t
@@ -371,6 +371,13 @@ public:
     return old;
   }
 
+  bool setForcedRefresh(bool doit)
+  {
+    auto old = d_forcedRefresh;
+    d_forcedRefresh = doit;
+    return old;
+  }
+
   bool setQNameMinimization(bool state = true)
   {
     auto old = d_qNameMinimization;
@@ -769,6 +776,7 @@ private:
   bool d_queryReceivedOverTCP{false};
   bool d_followCNAME{true};
   bool d_refresh{false};
+  bool d_forcedRefresh{false};
   bool d_serveStale{false};
 
   LogMode d_lm;
@@ -930,7 +938,7 @@ class ImmediateServFailException
 {
 public:
   ImmediateServFailException(string reason_) :
-    reason(std::move(reason_)) {};
+    reason(std::move(reason_)) { };
 
   string reason; //! Print this to tell the user what went wrong
 };
index 6f174e05fec18b0beae11b9599949142370a2f83..4376160e09f950618ca3489b7e6c1ab1333fff9b 100644 (file)
@@ -52,8 +52,15 @@ struct ResolveTask
   uint16_t d_qtype;
   // Deadline is not part of index and not used by operator<()
   time_t d_deadline;
-  // Whether to run this task in regular mode (false) or in the mode that refreshes almost expired tasks
-  bool d_refreshMode;
+  // Whether to run this task in normal mode (None) or in the mode that refreshes almost expired
+  // rrsets (Regular) or in Forced Mode
+  enum RefreshMode : uint8_t
+  {
+    None,
+    Refresh,
+    Forced
+  };
+  RefreshMode d_refreshMode;
   // Use a function pointer as comparing std::functions is a nuisance
   using TaskFunction = void (*)(const struct timeval& now, bool logErrors, const ResolveTask& task);
   TaskFunction d_func;
@@ -122,7 +129,7 @@ private:
                               composite_key<ResolveTask,
                                             member<ResolveTask, DNSName, &ResolveTask::d_qname>,
                                             member<ResolveTask, uint16_t, &ResolveTask::d_qtype>,
-                                            member<ResolveTask, bool, &ResolveTask::d_refreshMode>,
+                                            member<ResolveTask, ResolveTask::RefreshMode, &ResolveTask::d_refreshMode>,
                                             member<ResolveTask, ResolveTask::TaskFunction, &ResolveTask::d_func>,
                                             member<ResolveTask, ComboAddress, &ResolveTask::d_ip>,
                                             member<ResolveTask, Netmask, &ResolveTask::d_netmask>>>,