]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Implementation of serve-stale from record cache.
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 14 Jun 2022 13:42:41 +0000 (15:42 +0200)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Fri, 9 Sep 2022 07:43:34 +0000 (09:43 +0200)
If a resolve fails, we try it again with serveStale is true.  If
serveStale is true or a record is already being server stale, the
record cache is willing to produce (and extend the ttd) of stale
records. It wil also keep a count of the extensions, to be able to
limit those and trigger a task te refresh once every while.

If we (potentially) serve stale, we are less aggessive evicting
stale records from the record cache.

Enable by setting server-stale-extensions (default 0). The unit is
30s. So a value of 2880 will keep serving the record for 24 hours, even
if it cannot be refreshed. If the original ttl of a record is less than 30,
the extension unit will be that ttl.

14 files changed:
pdns/cachecleaner.hh
pdns/dnsseckeeper.hh
pdns/recpacketcache.cc
pdns/recpacketcache.hh
pdns/recursor_cache.cc
pdns/recursor_cache.hh
pdns/recursordist/negcache.hh
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-taskqueue.cc
pdns/recursordist/rec-taskqueue.hh
pdns/recursordist/taskqueue.hh
pdns/recursordist/test-rec-taskqueue.cc
pdns/syncres.cc
pdns/syncres.hh

index 578b4690948159924d332f73dc760aab469093cf..f489a9d88f437face61e3528bd4875cf94276919 100644 (file)
@@ -26,7 +26,7 @@
 #include "dnsname.hh"
 #include "lock.hh"
 
-// this function can clean any cache that has a getTTD() method on its entries, a preRemoval() method and a 'sequence' index as its second index
+// this function can clean any cache that has an isStale() method on its entries, a preRemoval() method and a 'sequence' index as its second index
 // the ritual is that the oldest entries are in *front* of the sequence collection, so on a hit, move an item to the end
 // and optionally, on a miss, move it to the beginning
 template <typename S, typename C, typename T>
@@ -49,7 +49,7 @@ void pruneCollection(C& container, T& collection, size_t maxCached, size_t scanF
   size_t tried = 0, erased = 0;
 
   for (auto iter = sidx.begin(); iter != sidx.end() && tried < lookAt; ++tried) {
-    if (iter->getTTD() < now) {
+    if (iter->isStale(now)) {
       iter = sidx.erase(iter);
       erased++;
     }
@@ -157,7 +157,7 @@ uint64_t pruneMutexCollectionsVector(C& container, std::vector<T>& maps, uint64_
     auto& sidx = boost::multi_index::get<S>(mc->d_map);
     uint64_t erased = 0, lookedAt = 0;
     for (auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
-      if (i->getTTD() < now) {
+      if (i->isStale(now)) {
         container.preRemoval(*mc, *i);
         i = sidx.erase(i);
         erased++;
index 4dfe4c1b1db80af73ddeae70f59e76b35fb5ce69..b3082d80d7f7399a6eecbecf1947e52afd0ba7e7 100644 (file)
@@ -251,9 +251,9 @@ private:
   {
     typedef vector<DNSSECKeeper::keymeta_t> keys_t;
   
-    uint32_t getTTD() const
+    uint32_t isStale(time_t now) const
     {
-      return d_ttd;
+      return d_ttd < now;
     }
   
     DNSName d_domain;
@@ -263,9 +263,9 @@ private:
   
   struct METACacheEntry
   {
-    time_t getTTD() const
+    time_t isStale(time_t now) const
     {
-      return d_ttd;
+      return d_ttd < now;
     }
 
     DNSName d_domain;
index 4e15cbed078f92cf3eb75126df82815df1fb3631..6ca11910f733407b0966138467bc350cf05af76c 100644 (file)
@@ -65,7 +65,7 @@ bool RecursorPacketCache::checkResponseMatches(std::pair<packetCache_t::index<Ha
         const bool almostExpired = ttl <= deadline;
         if (almostExpired) {
           iter->d_submitted = true;
-          pushAlmostExpiredTask(qname, qtype, iter->d_ttd);
+          pushAlmostExpiredTask(qname, qtype, iter->d_ttd, Netmask());
         }
       }
       *responsePacket = iter->d_packet;
index 6e15ee4752e9a443661298fe5701e2d6eabbef61..45ba4298ee926ed05bb6097b669911786ab0bea1 100644 (file)
@@ -118,9 +118,9 @@ private:
     bool d_tcp; // whether this entry was created from a TCP query
     inline bool operator<(const struct Entry& rhs) const;
 
-    time_t getTTD() const
+    bool isStale(time_t now) const
     {
-      return d_ttd;
+      return d_ttd < now;
     }
 
     uint32_t getOrigTTL() const
index 5b44c0e20b22c35175d1e99abe4c76b28f830b7b..9b6238a779f2fb00dcd94868d43d77b151a567e6 100644 (file)
@@ -15,6 +15,8 @@
 #include "cachecleaner.hh"
 #include "rec-taskqueue.hh"
 
+uint16_t MemRecursorCache::s_maxServedStaleExtensions;
+
 MemRecursorCache::MemRecursorCache(size_t mapsCount) :
   d_maps(mapsCount)
 {
@@ -152,7 +154,31 @@ time_t MemRecursorCache::handleHit(MapCombo::LockedContent& content, MemRecursor
   return ttd;
 }
 
-MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, const QType qtype, bool requireAuth, const ComboAddress& who)
+static void pushRefreshTask(const DNSName& qname, QType qtype, time_t deadline, const Netmask& netmask)
+{
+  if (qtype == QType::ADDR) {
+    pushAlmostExpiredTask(qname, QType::A, deadline, netmask);
+    pushAlmostExpiredTask(qname, QType::AAAA, deadline, netmask);
+  }
+  else {
+    pushAlmostExpiredTask(qname, qtype, deadline, netmask);
+  }
+}
+
+void MemRecursorCache::updateStaleEntry(time_t now, MemRecursorCache::OrderedTagIterator_t& entry)
+{
+  // We need to take care a infrequently access stale item cannot be extended past
+  // s_maxServedStaleExtension * s_serveStaleExtensionPeriod
+  // We we look how old the entry is, and increase d_servedStale accordingly, taking care not to overflow
+  const time_t howlong = std::max(static_cast<time_t>(1), now - entry->d_ttd);
+  const uint32_t extension = std::max(1U, std::min(entry->d_orig_ttl, s_serveStaleExtensionPeriod));
+  entry->d_servedStale = std::min(entry->d_servedStale + 1 + howlong / extension, static_cast<time_t>(s_maxServedStaleExtensions));
+  entry->d_ttd = now + std::min(entry->d_orig_ttl, s_serveStaleExtensionPeriod);
+
+  pushRefreshTask(entry->d_qname, entry->d_qtype, entry->d_ttd, entry->d_netmask);
+}
+
+MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, const QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale)
 {
   // MUTEX SHOULD BE ACQUIRED (as indicated by the reference to the content which is protected by a lock)
   auto ecsIndexKey = std::tie(qname, qtype);
@@ -176,7 +202,12 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
         }
         continue;
       }
-
+      // If we are serving this record stale (or *should*) and the
+      // ttd has passed increase ttd to the future and remember that
+      // we did. Also push a refresh task.
+      if ((serveStale || entry->d_servedStale > 0) && entry->d_ttd <= now && entry->d_servedStale < s_maxServedStaleExtensions) {
+        updateStaleEntry(now, entry);
+      }
       if (entry->d_ttd > now) {
         if (!requireAuth || entry->d_auth) {
           return entry;
@@ -187,11 +218,14 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
       else {
         /* this netmask-specific entry has expired */
         moveCacheItemToFront<SequencedTag>(map.d_map, entry);
-        ecsIndex->removeNetmask(best);
-        if (ecsIndex->isEmpty()) {
-          map.d_ecsIndex.erase(ecsIndex);
-          break;
+        // but is is also stale wrt serve-stale?
+        if (entry->isStale(now)) {
+          ecsIndex->removeNetmask(best);
+          if (ecsIndex->isEmpty()) {
+            map.d_ecsIndex.erase(ecsIndex);
+          }
         }
+        break;
       }
     }
   }
@@ -200,6 +234,12 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
   auto key = std::make_tuple(qname, qtype, boost::none, Netmask());
   auto entry = map.d_map.find(key);
   if (entry != map.d_map.end()) {
+    // If we are serving this record stale (or *should*) and the
+    // ttd has passed increase ttd to the future and remember that
+    // we did. Also push a refresh task.
+    if ((serveStale || entry->d_servedStale > 0) && entry->d_ttd <= now && entry->d_servedStale < s_maxServedStaleExtensions) {
+      updateStaleEntry(now, entry);
+    }
     if (entry->d_ttd > now) {
       if (!requireAuth || entry->d_auth) {
         return entry;
@@ -243,6 +283,12 @@ bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entr
 time_t MemRecursorCache::fakeTTD(MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, QType qtype, time_t ret, time_t now, uint32_t origTTL, bool refresh)
 {
   time_t ttl = ret - now;
+  // If we are checking an entry being served stale in refresh mode,
+  // we always consider it stale so a real refresh attempt will be
+  // kicked by SyncRes
+  if (refresh && entry->d_servedStale > 0) {
+    return -1;
+  }
   if (ttl > 0 && SyncRes::s_refresh_ttlperc > 0) {
     const uint32_t deadline = origTTL * SyncRes::s_refresh_ttlperc / 100;
     const bool almostExpired = static_cast<uint32_t>(ttl) <= deadline;
@@ -252,13 +298,7 @@ time_t MemRecursorCache::fakeTTD(MemRecursorCache::OrderedTagIterator_t& entry,
       }
       else {
         if (!entry->d_submitted) {
-          if (qtype == QType::ADDR) {
-            pushAlmostExpiredTask(qname, QType::A, entry->d_ttd);
-            pushAlmostExpiredTask(qname, QType::AAAA, entry->d_ttd);
-          }
-          else {
-            pushAlmostExpiredTask(qname, qtype, entry->d_ttd);
-          }
+          pushRefreshTask(qname, qtype, entry->d_ttd, entry->d_netmask);
           entry->d_submitted = true;
         }
       }
@@ -271,6 +311,8 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
 {
   bool requireAuth = flags & RequireAuth;
   bool refresh = flags & Refresh;
+  bool serveStale = flags & ServeStale;
+  
   boost::optional<vState> cachedState{boost::none};
   uint32_t origTTL;
 
@@ -293,11 +335,11 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
     if (qtype == QType::ADDR) {
       time_t ret = -1;
 
-      auto entryA = getEntryUsingECSIndex(*map, now, qname, QType::A, requireAuth, who);
+      auto entryA = getEntryUsingECSIndex(*map, now, qname, QType::A, requireAuth, who, serveStale);
       if (entryA != map->d_map.end()) {
         ret = handleHit(*map, entryA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
       }
-      auto entryAAAA = getEntryUsingECSIndex(*map, now, qname, QType::AAAA, requireAuth, who);
+      auto entryAAAA = getEntryUsingECSIndex(*map, now, qname, QType::AAAA, requireAuth, who, serveStale);
       if (entryAAAA != map->d_map.end()) {
         time_t ttdAAAA = handleHit(*map, entryAAAA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
         if (ret > 0) {
@@ -315,7 +357,7 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
       return ret > 0 ? (ret - now) : ret;
     }
     else {
-      auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who);
+      auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who, serveStale);
       if (entry != map->d_map.end()) {
         time_t ret = handleHit(*map, entry, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
         if (state && cachedState) {
@@ -337,7 +379,8 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
       for (auto i = entries.first; i != entries.second; ++i) {
         firstIndexIterator = map->d_map.project<OrderedTag>(i);
 
-        if (i->d_ttd <= now) {
+        // When serving stale, we consider expired records
+        if (i->d_ttd <= now && !serveStale && i->d_servedStale == 0) {
           moveCacheItemToFront<SequencedTag>(map->d_map, firstIndexIterator);
           continue;
         }
@@ -346,6 +389,12 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
           continue;
         }
         found = true;
+        // If we are serving this record stale (or *should*) and the
+        // ttd has passed increase ttd to the future and remember that
+        // we did. Also push a refresh task.
+        if ((serveStale || i->d_servedStale > 0) && i->d_ttd <= now && i->d_servedStale < s_maxServedStaleExtensions) {
+          updateStaleEntry(now, firstIndexIterator);
+        }
         ttd = handleHit(*map, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
 
         if (qt != QType::ANY && qt != QType::ADDR) { // normally if we have a hit, we are done
@@ -374,7 +423,8 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
     for (auto i = entries.first; i != entries.second; ++i) {
       firstIndexIterator = map->d_map.project<OrderedTag>(i);
 
-      if (i->d_ttd <= now) {
+      // When serving stale, we consider expired records
+      if (i->d_ttd <= now && !serveStale && i->d_servedStale == 0) {
         moveCacheItemToFront<SequencedTag>(map->d_map, firstIndexIterator);
         continue;
       }
@@ -382,8 +432,13 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
       if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
         continue;
       }
-
       found = true;
+      // If we are serving this record stale (or *should*) and the
+      // ttd has passed increase ttd to the future and remember that
+      // we did. Also push a refresh task.
+      if ((serveStale || i->d_servedStale > 0) && i->d_ttd <= now && i->d_servedStale < s_maxServedStaleExtensions) {
+        updateStaleEntry(now, firstIndexIterator);
+      }
       ttd = handleHit(*map, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
 
       if (qt != QType::ANY && qt != QType::ADDR) { // normally if we have a hit, we are done
@@ -498,6 +553,7 @@ void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qt,
     moveCacheItemToBack<SequencedTag>(map->d_map, stored);
   }
   ce.d_submitted = false;
+  ce.d_servedStale = 0;
   map->d_map.replace(stored, ce);
 }
 
@@ -611,7 +667,7 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName& qname,
 
   bool updated = false;
   if (!map->d_ecsIndex.empty() && !routingTag) {
-    auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who);
+    auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who, false); // XXX serveStale?
     if (entry == map->d_map.end()) {
       return false;
     }
@@ -668,7 +724,7 @@ uint64_t MemRecursorCache::doDump(int fd)
       for (const auto& j : i.d_records) {
         count++;
         try {
-          fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s %s %s\n", i.d_qname.toString().c_str(), i.d_orig_ttl, static_cast<int64_t>(i.d_ttd - now), i.d_qtype.toString().c_str(), j->getZoneRepresentation().c_str(), vStateToString(i.d_state).c_str(), i.d_auth, i.d_authZone.toLogString().c_str(), i.d_from.toString().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str(), !i.d_rtag ? "" : i.d_rtag.get().c_str());
+          fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s nm=%s rtag=%s ss=%hd\n", i.d_qname.toString().c_str(), i.d_orig_ttl, static_cast<int64_t>(i.d_ttd - now), i.d_qtype.toString().c_str(), j->getZoneRepresentation().c_str(), vStateToString(i.d_state).c_str(), i.d_auth, i.d_authZone.toLogString().c_str(), i.d_from.toString().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str(), !i.d_rtag ? "" : i.d_rtag.get().c_str(), i.d_servedStale);
         }
         catch (...) {
           fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
index ae58095f6303558539b57ac713cf7151b79ce6ec..285dba7039f7ee921fef362e4b6e693db64b3352 100644 (file)
@@ -49,6 +49,11 @@ class MemRecursorCache : public boost::noncopyable //  : public RecursorCache
 public:
   MemRecursorCache(size_t mapsCount = 1024);
 
+  // The number of times a state cache entry is extended
+  static uint16_t s_maxServedStaleExtensions;
+  // The time a stale cache entry is extended
+  static constexpr uint32_t s_serveStaleExtensionPeriod = 30;
+
   size_t size() const;
   size_t bytes();
   pair<uint64_t, uint64_t> stats();
@@ -79,14 +84,21 @@ private:
   struct CacheEntry
   {
     CacheEntry(const std::tuple<DNSName, QType, OptTag, Netmask>& key, bool auth) :
-      d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_state(vState::Indeterminate), d_ttd(0), d_orig_ttl{0}, d_qtype(std::get<1>(key)), d_auth(auth), d_submitted(false)
+      d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_state(vState::Indeterminate), d_ttd(0), d_orig_ttl{0}, d_servedStale(0), d_qtype(std::get<1>(key)), d_auth(auth), d_submitted(false)
     {
     }
 
     typedef vector<std::shared_ptr<DNSRecordContent>> records_t;
-    time_t getTTD() const
+
+    bool isStale(time_t now) const
     {
-      return d_ttd;
+      // We like to keep things in cache when we (potentially) should serve stale
+      if  (s_maxServedStaleExtensions > 0) {
+        return d_ttd + s_maxServedStaleExtensions * std::min(s_serveStaleExtensionPeriod, d_orig_ttl) < now;
+      }
+      else {
+        return d_ttd < now;
+      }
     }
 
     records_t d_records;
@@ -100,6 +112,7 @@ private:
     mutable vState d_state;
     mutable time_t d_ttd;
     uint32_t d_orig_ttl;
+    mutable uint16_t d_servedStale;
     QType d_qtype;
     bool d_auth;
     mutable bool d_submitted; // whether this entry has been queued for refetch
@@ -255,9 +268,10 @@ private:
 
   bool entryMatches(OrderedTagIterator_t& entry, QType qt, bool requireAuth, const ComboAddress& who);
   Entries getEntries(MapCombo::LockedContent& content, const DNSName& qname, const QType qt, const OptTag& rtag);
-  cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& content, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who);
+  cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& content, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale);
 
   time_t handleHit(MapCombo::LockedContent& content, OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* authZone, ComboAddress* fromAuthIP);
+  void updateStaleEntry(time_t now, OrderedTagIterator_t& entry);
 
 public:
   void preRemoval(MapCombo::LockedContent& map, const CacheEntry& entry)
index 204406cbd20b403c2dd509b9e5481c1becde964b..83667edefeb022cd94f0cc5aa01e87732e97ea8e 100644 (file)
@@ -65,9 +65,9 @@ public:
     mutable time_t d_ttd; // Timestamp when this entry should die
     mutable vState d_validationState{vState::Indeterminate};
     QType d_qtype; // The denied type
-    time_t getTTD() const
+    bool isStale(time_t now) const
     {
-      return d_ttd;
+      return d_ttd < now;
     };
   };
 
index 3240d2272fa3031d46c8549ed3975dd9e1ed5502..c5971b9b14132edbceb2fd2fc6611127d8d4c5c8 100644 (file)
@@ -1511,6 +1511,7 @@ static int serviceMain(int argc, char* argv[], Logr::log_t log)
   SyncRes::s_event_trace_enabled = ::arg().asNum("event-trace-enabled");
   SyncRes::s_save_parent_ns_set = ::arg().mustDo("save-parent-ns-set");
   SyncRes::s_max_busy_dot_probes = ::arg().asNum("max-busy-dot-probes");
+  MemRecursorCache::s_maxServedStaleExtensions = ::arg().asNum("serve-stale-extensions");
 
   if (SyncRes::s_tcp_fast_open_connect) {
     checkFastOpenSysctl(true, log);
@@ -2810,6 +2811,7 @@ int main(int argc, char** argv)
     ::arg().set("structured-logging-backend", "Structured logging backend") = "default";
     ::arg().setSwitch("save-parent-ns-set", "Save parent NS set to be used if child NS set fails") = "yes";
     ::arg().set("max-busy-dot-probes", "Maximum number of concurrent DoT probes") = "0";
+    ::arg().set("serve-stale-extensions", "Number of times a record's ttl is extended by 30s to be served stale") = "0";
 
     ::arg().setCmd("help", "Provide a helpful message");
     ::arg().setCmd("version", "Print version string");
index 98b5f6296d92f381c34014fdb3f12bf8dda0ff7e..c3aa7d2704b71048e6d3f51dd31616fd7eca952a 100644 (file)
@@ -114,14 +114,15 @@ static struct taskstats s_resolve_tasks;
 
 static void resolve(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
 {
-  auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()));
+  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 sr(now);
   vector<DNSRecord> ret;
   sr.setRefreshAlmostExpired(task.d_refreshMode);
+  sr.setQuerySource(task.d_netmask);
   bool ex = true;
   try {
-    log->info(Logr::Debug, "resolving");
+    log->info(Logr::Debug, "resolving", "refresh", Logging::Loggable(task.d_refreshMode));
     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()));
@@ -231,14 +232,14 @@ bool runTaskOnce(bool logErrors)
   return true;
 }
 
-void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline)
+void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask)
 {
   if (SyncRes::isUnsupported(qtype)) {
-    auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
+    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, {}, {}};
+  pdns::ResolveTask task{qname, qtype, deadline, true, resolve, {}, {}, netmask};
   if (s_taskQueue.lock()->queue.push(std::move(task))) {
     ++s_almost_expired_tasks.pushed;
   }
@@ -251,7 +252,7 @@ void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t de
     log->error(Logr::Error, "Cannot push task", "qtype unsupported");
     return;
   }
-  pdns::ResolveTask task{qname, qtype, deadline, false, resolve, {}, {}};
+  pdns::ResolveTask task{qname, qtype, deadline, false, resolve, {}, {}, {}};
   auto lock = s_taskQueue.lock();
   bool inserted = lock->rateLimitSet.insert(now, task);
   if (inserted) {
@@ -269,7 +270,7 @@ bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip
     return false;
   }
 
-  pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ip, nsname};
+  pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ip, nsname, {}};
   bool pushed = s_taskQueue.lock()->queue.push(std::move(task));
   if (pushed) {
     ++s_almost_expired_tasks.pushed;
index 868b33e0af886be097d5f42390ec28eeb19bdc9a..67bc6e0285a60e284d055e07e0cfb3cc81d1f04f 100644 (file)
@@ -26,6 +26,7 @@
 
 class DNSName;
 union ComboAddress;
+class Netmask;
 
 namespace pdns
 {
@@ -33,7 +34,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);
+void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask);
 void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline);
 bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip, time_t deadline, const DNSName& nsname);
 void taskQueueClear();
index 40df635dfd788b12944a0bbb56e76ee5c37016f6..0d43f06dc3c635d0d6fcfea7f354c17c371b662f 100644 (file)
@@ -31,7 +31,7 @@ size_t hash_value(const ComboAddress&);
 }
 
 #include <boost/multi_index_container.hpp>
-#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/ordered_index.hpp>
 #include <boost/multi_index/key_extractors.hpp>
 #include <boost/multi_index/member.hpp>
 #include <boost/multi_index/sequenced_index.hpp>
@@ -61,10 +61,11 @@ struct ResolveTask
   ComboAddress d_ip;
   // NS name used by DoT probe task, not part of index and not used by operator<()
   DNSName d_nsname;
+  Netmask d_netmask;
 
   bool operator<(const ResolveTask& a) const
   {
-    return std::tie(d_qname, d_qtype, d_refreshMode, d_func, d_ip) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func, d_ip);
+    return std::tie(d_qname, d_qtype, d_refreshMode, d_func, d_ip, d_netmask) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func, a.d_ip, a.d_netmask);
   }
 
   bool run(bool logErrors);
@@ -118,13 +119,14 @@ private:
   typedef multi_index_container<
     ResolveTask,
     indexed_by<
-      hashed_unique<tag<HashTag>,
+      ordered_unique<tag<HashTag>,
                     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::TaskFunction, &ResolveTask::d_func>,
-                                  member<ResolveTask, ComboAddress, &ResolveTask::d_ip>>>,
+                                  member<ResolveTask, ComboAddress, &ResolveTask::d_ip>,
+                                  member<ResolveTask, Netmask, &ResolveTask::d_netmask>>>,
       sequenced<tag<SequencedTag>>>>
     queue_t;
 
index 8c997e0696dbfb599725c3811f59b50232e6e6e1..17ad0be38b44454719e52e045bc455cdd5dc34a6 100644 (file)
@@ -14,16 +14,16 @@ BOOST_AUTO_TEST_SUITE(rec_taskqueue)
 BOOST_AUTO_TEST_CASE(test_almostexpired_queue_no_dups)
 {
   taskQueueClear();
-  pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0);
-  pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0);
-  pushAlmostExpiredTask(DNSName("foo"), QType::A, 0);
+  pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0, Netmask());
+  pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0, Netmask());
+  pushAlmostExpiredTask(DNSName("foo"), QType::A, 0, Netmask());
 
   BOOST_CHECK_EQUAL(getTaskSize(), 2U);
   taskQueuePop();
   taskQueuePop();
   BOOST_CHECK_EQUAL(getTaskSize(), 0U);
   // AE queue is not rate limited
-  pushAlmostExpiredTask(DNSName("foo"), QType::A, 0);
+  pushAlmostExpiredTask(DNSName("foo"), QType::A, 0, Netmask());
   BOOST_CHECK_EQUAL(getTaskSize(), 1U);
 }
 
index 15c09b62f95124854cd5e951f1f38c349b0993f7..8268da90ee572b45c0d315e5566cba543f907ef0 100644 (file)
@@ -1802,212 +1802,221 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType qtyp
   }
   int res=0;
 
-  // 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())) {
-    if(d_cacheonly) { // very limited OOB support
-      LWResult lwr;
-      LOG(prefix<<qname<<": Recursion not requested for '"<<qname<<"|"<<qtype<<"', peeking at auth/forward zones"<<endl);
-      DNSName authname(qname);
-      domainmap_t::const_iterator iter=getBestAuthZone(&authname);
-      if(iter != t_sstorage.domainmap->end()) {
-        if(iter->second.isAuth()) {
-          ret.clear();
-          d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, res);
-          if (fromCache)
-            *fromCache = d_wasOutOfBand;
-          return res;
-        }
-        else if (considerforwards) {
-          const vector<ComboAddress>& servers = iter->second.d_servers;
-          const ComboAddress remoteIP = servers.front();
-          LOG(prefix<<qname<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname<<"'"<<endl);
-
-          boost::optional<Netmask> nm;
-          bool chained = false;
-          // forwardes are "anonymous", so plug in an empty name; some day we might have a fancier config language...
-          auto resolveRet = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, authname, qtype.getCode(), false, false, &d_now, nm, &lwr, &chained, DNSName());
-
-          d_totUsec += lwr.d_usec;
-          accountAuthLatency(lwr.d_usec, remoteIP.sin4.sin_family);
-          if (fromCache)
-            *fromCache = true;
-
-          // filter out the good stuff from lwr.result()
-          if (resolveRet == LWResult::Result::Success) {
-            for(const auto& rec : lwr.d_records) {
-              if(rec.d_place == DNSResourceRecord::ANSWER)
-                ret.push_back(rec);
-            }
-            return 0;
+
+  const int iterations = !d_refresh && MemRecursorCache::s_maxServedStaleExtensions > 0 ? 2 : 1;
+  for (int loop = 0; loop < iterations; loop++) {
+
+    // First try a regular resolve
+    const bool serveStale = loop == 1;
+
+    // When we're not on the last iteration, a timeout is not fatal
+    d_exceptionOnTimeout = loop == iterations - 1;
+
+    // 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())) {
+      if(d_cacheonly) { // very limited OOB support
+        LWResult lwr;
+        LOG(prefix<<qname<<": Recursion not requested for '"<<qname<<"|"<<qtype<<"', peeking at auth/forward zones"<<endl);
+        DNSName authname(qname);
+        domainmap_t::const_iterator iter=getBestAuthZone(&authname);
+        if(iter != t_sstorage.domainmap->end()) {
+          if(iter->second.isAuth()) {
+            ret.clear();
+            d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, res);
+            if (fromCache)
+              *fromCache = d_wasOutOfBand;
+            return res;
           }
-          else {
-            return RCode::ServFail;
+          else if (considerforwards) {
+            const vector<ComboAddress>& servers = iter->second.d_servers;
+            const ComboAddress remoteIP = servers.front();
+            LOG(prefix<<qname<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname<<"'"<<endl);
+
+            boost::optional<Netmask> nm;
+            bool chained = false;
+            // forwardes are "anonymous", so plug in an empty name; some day we might have a fancier config language...
+            auto resolveRet = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, authname, qtype.getCode(), false, false, &d_now, nm, &lwr, &chained, DNSName());
+
+            d_totUsec += lwr.d_usec;
+            accountAuthLatency(lwr.d_usec, remoteIP.sin4.sin_family);
+            if (fromCache)
+              *fromCache = true;
+
+            // filter out the good stuff from lwr.result()
+            if (resolveRet == LWResult::Result::Success) {
+              for(const auto& rec : lwr.d_records) {
+                if(rec.d_place == DNSResourceRecord::ANSWER)
+                  ret.push_back(rec);
+              }
+              return 0;
+            }
+            else {
+              return RCode::ServFail;
+            }
           }
         }
       }
-    }
-
-    DNSName authname(qname);
-    bool wasForwardedOrAuthZone = false;
-    bool wasAuthZone = false;
-    bool wasForwardRecurse = false;
-    domainmap_t::const_iterator iter = getBestAuthZone(&authname);
-    if(iter != t_sstorage.domainmap->end()) {
-      const auto& domain = iter->second;
-      wasForwardedOrAuthZone = true;
-
-      if (domain.isAuth()) {
-        wasAuthZone = true;
-      } else if (domain.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, res, state, wasAuthZone, wasForwardRecurse)) { // 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();
+      DNSName authname(qname);
+      bool wasForwardedOrAuthZone = false;
+      bool wasAuthZone = false;
+      bool wasForwardRecurse = false;
+      domainmap_t::const_iterator iter = getBestAuthZone(&authname);
+      if(iter != t_sstorage.domainmap->end()) {
+        const auto& domain = iter->second;
+        wasForwardedOrAuthZone = true;
 
-      if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
-        *fromCache = true;
+        if (domain.isAuth()) {
+          wasAuthZone = true;
+        } else if (domain.shouldRecurse()) {
+          wasForwardRecurse = 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) {
-            *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, res, state, wasAuthZone, wasForwardRecurse, serveStale)) { // 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 && (!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) {
+              *fromCache = true;
+            }
           }
         }
+        return res;
       }
 
-      return res;
-    }
-
-    if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
-      // we done
-      d_wasOutOfBand = wasAuthZone;
-      if (fromCache) {
-        *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 (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state, serveStale)) {
+        // we done
+        d_wasOutOfBand = wasAuthZone;
+        if (fromCache) {
+          *fromCache = true;
         }
-      }
 
-      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, res, state, wasAuthZone, wasForwardRecurse)) { // 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 (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 (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
-        *fromCache = true;
+        return res;
       }
-      /* 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) {
-            *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, res, state, wasAuthZone, wasForwardRecurse, serveStale)) { // 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 && (!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) {
+              *fromCache = true;
+            }
           }
         }
-      }
 
-      return res;
+        return res;
+      }
     }
-  }
-
-  if (d_cacheonly) {
-    return 0;
-  }
-
-  LOG(prefix<<qname<<": No cache hit for '"<<qname<<"|"<<qtype<<"', trying to find an appropriate NS record"<<endl);
 
-  DNSName subdomain(qname);
-  if (qtype == QType::DS) subdomain.chopOff();
+    if (d_cacheonly) {
+      return 0;
+    }
 
-  NsSet nsset;
-  bool flawedNSSet=false;
+    LOG(prefix<<qname<<": No cache hit for '"<<qname<<"|"<<qtype<<"', trying to find an appropriate NS record"<<endl);
 
-  // the two retries allow getBestNSNamesFromCache&co to reprime the root
-  // hints, in case they ever go missing
-  for(int tries=0;tries<2 && nsset.empty();++tries) {
-    subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); //  pass beenthere to both occasions
-  }
+    DNSName subdomain(qname);
+    if (qtype == QType::DS) subdomain.chopOff();
 
-  res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, nullptr);
+    NsSet nsset;
+    bool flawedNSSet=false;
 
-  if (res == -1 && s_save_parent_ns_set) {
-    // It did not work out, lets check if we have a saved parent NS set
-    map<DNSName, vector<ComboAddress>> fallBack;
-    {
-      auto lock = s_savedParentNSSet.lock();
-      auto domainData= lock->find(subdomain);
-      if (domainData != lock->end() && domainData->d_nsAddresses.size() > 0) {
-        nsset.clear();
-        // Build the nsset arg and fallBack data for the fallback doResolveAt() attempt
-        // Take a copy to be able to release the lock, NsSet is actually a map, go figure
-        for (const auto& ns : domainData->d_nsAddresses) {
-          nsset.emplace(ns.first, pair(std::vector<ComboAddress>(), false));
-          fallBack.emplace(ns.first, ns.second);
+    // the two retries allow getBestNSNamesFromCache&co to reprime the root
+    // hints, in case they ever go missing
+    for(int tries=0;tries<2 && nsset.empty();++tries) {
+      subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); //  pass beenthere to both occasions
+    }
+
+    res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, nullptr);
+
+    if (res == -1 && s_save_parent_ns_set) {
+      // It did not work out, lets check if we have a saved parent NS set
+      map<DNSName, vector<ComboAddress>> fallBack;
+      {
+        auto lock = s_savedParentNSSet.lock();
+        auto domainData= lock->find(subdomain);
+        if (domainData != lock->end() && domainData->d_nsAddresses.size() > 0) {
+          nsset.clear();
+          // Build the nsset arg and fallBack data for the fallback doResolveAt() attempt
+          // Take a copy to be able to release the lock, NsSet is actually a map, go figure
+          for (const auto& ns : domainData->d_nsAddresses) {
+            nsset.emplace(ns.first, pair(std::vector<ComboAddress>(), false));
+            fallBack.emplace(ns.first, ns.second);
+          }
+        }
+      }
+      if (fallBack.size() > 0) {
+        LOG(prefix<<qname<<": Failure, but we have a saved parent NS set, trying that one"<< endl)
+          res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, &fallBack);
+        if (res == 0) {
+          // It did work out
+          s_savedParentNSSet.lock()->inc(subdomain);
         }
       }
     }
-    if (fallBack.size() > 0) {
-      LOG(prefix<<qname<<": Failure, but we have a saved parent NS set, trying that one"<< endl)
-      res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, &fallBack);
-      if (res == 0) {
-        // It did work out
-        s_savedParentNSSet.lock()->inc(subdomain);
+    /* 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);
       }
     }
-  }
-  /* 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 (!res) {
+      return 0;
     }
-  }
 
-  if (!res) {
-    return 0;
+    LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
   }
-
-  LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
-
   return res<0 ? RCode::ServFail : res;
 }
 
@@ -2399,7 +2408,7 @@ static bool scanForCNAMELoop(const DNSName& name, const vector<DNSRecord>& recor
   return false;
 }
 
-bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse)
+bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse, bool serveStale)
 {
   string prefix;
   if(doLog()) {
@@ -2430,6 +2439,9 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType qtype, vector<
   if (d_refresh) {
     flags |= MemRecursorCache::Refresh;
   }
+  if (serveStale) {
+    flags |= MemRecursorCache::ServeStale;
+  }
   if (g_recCache->get(d_now.tv_sec, qname, QType::CNAME, flags, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth, &authZone, &d_fromAuthIP) > 0) {
     foundName = qname;
     foundQT = QType::CNAME;
@@ -2720,7 +2732,7 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne,
   }
 }
 
-bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state)
+bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool serveStale)
 {
   bool giveNegative=false;
 
@@ -2838,7 +2850,9 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
   if (d_refresh) {
     flags |= MemRecursorCache::Refresh;
   }
-
+  if (serveStale) {
+    flags |= MemRecursorCache::ServeStale;
+  }
   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, &d_fromAuthIP) > 0) {
 
     LOG(prefix<<sqname<<": Found cache hit for "<<sqt.toString()<<": ");
@@ -5099,7 +5113,11 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
   checkMaxQperQ(qname);
 
   if(s_maxtotusec && d_totUsec > s_maxtotusec) {
-    throw ImmediateServFailException("Too much time waiting for "+qname.toLogString()+"|"+qtype.toString()+", timeouts: "+std::to_string(d_timeouts) +", throttles: "+std::to_string(d_throttledqueries) + ", queries: "+std::to_string(d_outqueries)+", "+std::to_string(d_totUsec/1000)+"msec");
+    if (d_exceptionOnTimeout) {
+      throw ImmediateServFailException("Too much time waiting for "+qname.toLogString()+"|"+qtype.toString()+", timeouts: "+std::to_string(d_timeouts) +", throttles: "+std::to_string(d_throttledqueries) + ", queries: "+std::to_string(d_outqueries)+", "+std::to_string(d_totUsec/1000)+"msec");
+    } else {
+      return false;
+    }
   }
 
   if(doTCP) {
@@ -5689,6 +5707,16 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
   return -1;
 }
 
+void SyncRes::setQuerySource(const Netmask& netmask)
+{
+  if (!netmask.empty()) {
+    d_outgoingECSNetwork = netmask;
+  }
+  else {
+    d_outgoingECSNetwork = boost::none;
+  }
+}
+
 void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS)
 {
   d_requestor = requestor;
index fd18d37a8adefb8c3a8030ffa92a536819f55ff0..ef1575c1982465f25d9cf1647a66658333a0094a 100644 (file)
@@ -392,6 +392,7 @@ public:
   }
 
   void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS);
+  void setQuerySource(const Netmask& netmask);
 
   void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
   {
@@ -561,8 +562,8 @@ private:
   bool isRecursiveForwardOrAuth(const DNSName &qname) const;
   bool isForwardOrAuth(const DNSName &qname) const;
   domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
-  bool doCNAMECacheCheck(const DNSName &qname, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse);
-  bool doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state);
+  bool doCNAMECacheCheck(const DNSName &qname, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse, bool serveStale);
+  bool doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool serveStale);
   void getBestNSFromCache(const DNSName &qname, QType qtype, vector<DNSRecord>&bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain = boost::none);
   DNSName getBestNSNamesFromCache(const DNSName &qname, QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere);
 
@@ -646,7 +647,8 @@ private:
   bool d_queryReceivedOverTCP{false};
   bool d_followCNAME{true};
   bool d_refresh{false};
-
+  bool d_exceptionOnTimeout{true};
+  
   LogMode d_lm;
 };