]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - pdns/syncres.cc
rec: Count a lookup into an internal auth zone as a cache miss
[thirdparty/pdns.git] / pdns / syncres.cc
index 9f7f548f4f293fbdbac9400f5354af5b97f5e48b..d03be8e807c7b90ec44bfc526d2ef8746cad6959 100644 (file)
@@ -33,6 +33,7 @@
 #include "lua-recursor4.hh"
 #include "rec-lua-conf.hh"
 #include "syncres.hh"
+#include "dnsseckeeper.hh"
 #include "validate-recursor.hh"
 
 thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage;
@@ -140,7 +141,7 @@ int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qcl
   if (d_queryValidationState != Indeterminate) {
     g_stats.dnssecValidations++;
   }
-  if (d_DNSSECValidationRequested) {
+  if (shouldValidate()) {
     increaseDNSSECStateCounter(d_queryValidationState);
   }
 
@@ -318,8 +319,10 @@ int SyncRes::AuthDomain::getRecords(const DNSName& qname, uint16_t qtype, std::v
   return result;
 }
 
-bool SyncRes::doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res) const
+bool SyncRes::doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res)
 {
+  d_authzonequeries++;
+
   res = domain.getRecords(qname, qtype.getCode(), ret);
   return true;
 }
@@ -368,7 +371,9 @@ uint64_t SyncRes::doDumpNSSpeeds(int fd)
   for(const auto& i : t_sstorage.nsSpeeds)
   {
     count++;
-    fprintf(fp, "%s -> ", i.first.toString().c_str());
+
+    // an <empty> can appear hear in case of authoritative (hosted) zones
+    fprintf(fp, "%s -> ", i.first.toLogString().c_str());
     for(const auto& j : i.second.d_collection)
     {
       // typedef vector<pair<ComboAddress, DecayingEwma> > collection_t;
@@ -559,11 +564,28 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
       }
     }
 
-    if(!d_skipCNAMECheck && doCNAMECacheCheck(qname,qtype,ret,depth,res,state)) // will reroute us if needed
+    DNSName authname(qname);
+    bool wasForwardedOrAuthZone = false;
+    bool wasAuthZone = false;
+    domainmap_t::const_iterator iter = getBestAuthZone(&authname);
+    if(iter != t_sstorage.domainmap->end()) {
+      wasForwardedOrAuthZone = true;
+      const vector<ComboAddress>& servers = iter->second.d_servers;
+      if(servers.empty()) {
+        wasAuthZone = true;
+      }
+    }
+
+    if(!d_skipCNAMECheck && doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone)) { // will reroute us if needed
+      d_wasOutOfBand = wasAuthZone;
       return res;
+    }
 
-    if(doCacheCheck(qname,qtype,ret,depth,res,state)) // we done
+    if(doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, qtype, ret, depth, res, state)) {
+      // we done
+      d_wasOutOfBand = wasAuthZone;
       return res;
+    }
   }
 
   if(d_cacheonly)
@@ -609,9 +631,19 @@ static bool ipv6First(const ComboAddress& a, const ComboAddress& b)
 }
 #endif
 
+struct speedOrderCA
+{
+  speedOrderCA(std::map<ComboAddress,double>& speeds): d_speeds(speeds) {}
+  bool operator()(const ComboAddress& a, const ComboAddress& b) const
+  {
+    return d_speeds[a] < d_speeds[b];
+  }
+  std::map<ComboAddress, double>& d_speeds;
+};
+
 /** This function explicitly goes out for A or AAAA addresses
 */
-vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere)
+vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly)
 {
   typedef vector<DNSRecord> res_t;
   res_t res;
@@ -620,8 +652,12 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
   ret_t ret;
 
   QType type;
+  bool oldCacheOnly = d_cacheonly;
   bool oldRequireAuthData = d_requireAuthData;
+  bool oldValidationRequested = d_DNSSECValidationRequested;
   d_requireAuthData = false;
+  d_DNSSECValidationRequested = false;
+  d_cacheonly = cacheOnly;
 
   for(int j=1; j<2+s_doIPv6; j++)
   {
@@ -642,9 +678,9 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
     if(!doResolve(qname, type, res,depth+1, beenthere, newState) && !res.empty()) {  // this consults cache, OR goes out
       for(res_t::const_iterator i=res.begin(); i!= res.end(); ++i) {
         if(i->d_type == QType::A || i->d_type == QType::AAAA) {
-         if(auto rec = std::dynamic_pointer_cast<ARecordContent>(i->d_content))
+         if(auto rec = getRR<ARecordContent>(*i))
            ret.push_back(rec->getCA(53));
-         else if(auto aaaarec = std::dynamic_pointer_cast<AAAARecordContent>(i->d_content))
+         else if(auto aaaarec = getRR<AAAARecordContent>(*i))
            ret.push_back(aaaarec->getCA(53));
           done=true;
         }
@@ -656,7 +692,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
        if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 0) {
          for(auto k=cset.cbegin();k!=cset.cend();++k) {
            if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
-             if (auto drc = std::dynamic_pointer_cast<AAAARecordContent>(k->d_content)) {
+             if (auto drc = getRR<AAAARecordContent>(*k)) {
                ComboAddress ca=drc->getCA(53);
                ret.push_back(ca);
              }
@@ -669,23 +705,42 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
   }
 
   d_requireAuthData = oldRequireAuthData;
+  d_DNSSECValidationRequested = oldValidationRequested;
+  d_cacheonly = oldCacheOnly;
 
-  if(ret.size() > 1) {
-    random_shuffle(ret.begin(), ret.end(), dns_random);
+  /* we need to remove from the nsSpeeds collection the existing IPs
+     for this nameserver that are no longer in the set, even if there
+     is only one or none at all in the current set.
+  */
+  map<ComboAddress, double> speeds;
+  auto& collection = t_sstorage.nsSpeeds[qname].d_collection;
+  for(const auto& val: ret) {
+    speeds[val] = collection[val].get(&d_now);
+  }
 
-    // move 'best' address for this nameserver name up front
-    nsspeeds_t::iterator best = t_sstorage.nsSpeeds.find(qname);
+  t_sstorage.nsSpeeds[qname].purge(speeds);
 
-    if(best != t_sstorage.nsSpeeds.end())
-      for(ret_t::iterator i=ret.begin(); i != ret.end(); ++i) {
-        if(*i==best->second.d_best) {  // got the fastest one
-          if(i!=ret.begin()) {
-            *i=*ret.begin();
-            *ret.begin()=best->second.d_best;
-          }
-          break;
+  if(ret.size() > 1) {
+    random_shuffle(ret.begin(), ret.end(), dns_random);
+    speedOrderCA so(speeds);
+    stable_sort(ret.begin(), ret.end(), so);
+
+    if(doLog()) {
+      string prefix=d_prefix;
+      prefix.append(depth, ' ');
+      LOG(prefix<<"Nameserver "<<qname<<" IPs: ");
+      bool first = true;
+      for(const auto& addr : ret) {
+        if (first) {
+          first = false;
         }
+        else {
+          LOG(", ");
+        }
+        LOG((addr.toString())<<"(" << (boost::format("%0.2f") % (speeds[addr]/1000.0)).str() <<"ms)");
       }
+      LOG(endl);
+    }
   }
 
   return ret;
@@ -737,8 +792,11 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
         GetBestNSAnswer answer;
         answer.qname=qname;
        answer.qtype=qtype.getCode();
-       for(const auto& dr : bestns)
-         answer.bestns.insert(make_pair(dr.d_name, getRR<NSRecordContent>(dr)->getNS()));
+       for(const auto& dr : bestns) {
+          if (auto nsContent = getRR<NSRecordContent>(dr)) {
+            answer.bestns.insert(make_pair(dr.d_name, nsContent->getNS()));
+          }
+        }
 
         if(beenthere.count(answer)) {
          brokeloop=true;
@@ -809,14 +867,17 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName &qname, const QType& qtyp
 
   for(auto k=bestns.cbegin() ; k != bestns.cend(); ++k) {
     // The actual resolver code will not even look at the ComboAddress or bool
-    nsset.insert({std::dynamic_pointer_cast<NSRecordContent>(k->d_content)->getNS(), {{}, false}}); 
-    if(k==bestns.cbegin())
-      subdomain=k->d_name;
+    const auto nsContent = getRR<NSRecordContent>(*k);
+    if (nsContent) {
+      nsset.insert({nsContent->getNS(), {{}, false}});
+      if(k==bestns.cbegin())
+        subdomain=k->d_name;
+    }
   }
   return subdomain;
 }
 
-bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state)
+bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone)
 {
   string prefix;
   if(doLog()) {
@@ -838,15 +899,24 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
   if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
 
     for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
+      if (j->d_class != QClass::IN) {
+        continue;
+      }
+
       if(j->d_ttl>(unsigned int) d_now.tv_sec) {
 
-        if (d_DNSSECValidationRequested && wasAuth && state == Indeterminate && d_requireAuthData) {
+        if (!wasAuthZone && shouldValidate() && wasAuth && state == Indeterminate && d_requireAuthData) {
           /* This means we couldn't figure out the state when this entry was cached,
              most likely because we hadn't computed the zone cuts yet. */
           /* make sure they are computed before validating */
-          computeZoneCuts(qname, g_rootdnsname, depth);
+          DNSName subdomain(qname);
+          /* if we are retrieving a DS, we only care about the state of the parent zone */
+          if(qtype == QType::DS)
+            subdomain.chopOff();
+
+          computeZoneCuts(subdomain, g_rootdnsname, depth);
 
-          vState recordState = getValidationStatus(qname);
+          vState recordState = getValidationStatus(qname, false);
           if (recordState == Secure) {
             LOG(prefix<<qname<<": got Indeterminate state from the CNAME cache, validating.."<<endl);
             state = SyncRes::validateRecordsWithSigs(depth, qname, QType(QType::CNAME), qname, cset, signatures);
@@ -875,18 +945,21 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
         }
 
         for(const auto& rec : authorityRecs) {
-          DNSRecord dr(*rec);
-          dr.d_ttl=j->d_ttl - d_now.tv_sec;
-          ret.push_back(dr);
+          DNSRecord authDR(*rec);
+          authDR.d_ttl=j->d_ttl - d_now.tv_sec;
+          ret.push_back(authDR);
         }
 
         if(qtype != QType::CNAME) { // perhaps they really wanted a CNAME!
           set<GetBestNSAnswer>beenthere;
 
           vState cnameState = Indeterminate;
-          res=doResolve(std::dynamic_pointer_cast<CNAMERecordContent>(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere, cnameState);
-          LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
-          updateValidationState(state, cnameState);
+          const auto cnameContent = getRR<CNAMERecordContent>(*j);
+          if (cnameContent) {
+            res=doResolve(cnameContent->getTarget(), qtype, ret, depth+1, beenthere, cnameState);
+            LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+            updateValidationState(state, cnameState);
+          }
         }
         else
           res=0;
@@ -899,6 +972,39 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
   return false;
 }
 
+namespace {
+struct CacheEntry
+{
+  vector<DNSRecord> records;
+  vector<shared_ptr<RRSIGRecordContent>> signatures;
+  uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
+};
+struct CacheKey
+{
+  DNSName name;
+  uint16_t type;
+  DNSResourceRecord::Place place;
+  bool operator<(const CacheKey& rhs) const {
+    return tie(name, type) < tie(rhs.name, rhs.type);
+  }
+};
+typedef map<CacheKey, CacheEntry> tcache_t;
+}
+
+static void reapRecordsFromNegCacheEntryForValidation(tcache_t& tcache, const vector<DNSRecord>& records)
+{
+  for (const auto& rec : records) {
+    if (rec.d_type == QType::RRSIG) {
+      auto rrsig = getRR<RRSIGRecordContent>(rec);
+      if (rrsig) {
+        tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
+      }
+    } else {
+      tcache[{rec.d_name,rec.d_type,rec.d_place}].records.push_back(rec);
+    }
+  }
+}
+
 /*!
  * Convience function to push the records from records into ret with a new TTL
  *
@@ -914,8 +1020,58 @@ static void addTTLModifiedRecords(const vector<DNSRecord>& records, const uint32
   }
 }
 
+void SyncRes::computeNegCacheValidationStatus(NegCache::NegCacheEntry& ne, const DNSName& qname, const QType& qtype, const int res, vState& state, unsigned int depth)
+{
+  DNSName subdomain(qname);
+  /* if we are retrieving a DS, we only care about the state of the parent zone */
+  if(qtype == QType::DS)
+    subdomain.chopOff();
+
+  computeZoneCuts(subdomain, g_rootdnsname, depth);
+
+  tcache_t tcache;
+  reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.records);
+  reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.signatures);
+  reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.records);
+  reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.signatures);
+
+  for (const auto& entry : tcache) {
+    // this happens when we did store signatures, but passed on the records themselves
+    if (entry.second.records.empty()) {
+      continue;
+    }
+
+    const DNSName& owner = entry.first.name;
+
+    vState recordState = getValidationStatus(owner, false);
+    if (state == Indeterminate) {
+      state = recordState;
+    }
+
+    if (recordState == Secure) {
+      recordState = SyncRes::validateRecordsWithSigs(depth, qname, qtype, owner, entry.second.records, entry.second.signatures);
+    }
 
-bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state)
+    if (recordState != Indeterminate && recordState != state) {
+      updateValidationState(state, recordState);
+      if (state != Secure) {
+        break;
+      }
+    }
+  }
+
+  if (state == Secure) {
+    dState expectedState = res == RCode::NXDomain ? NXDOMAIN : NXQTYPE;
+    dState denialState = getDenialValidationState(ne, state, expectedState, false);
+    updateDenialValidationState(ne, state, denialState, expectedState, qtype == QType::DS);
+  }
+  if (state != Indeterminate) {
+    /* validation succeeded, let's update the cache entry so we don't have to validate again */
+    t_sstorage.negcache.updateValidationStatus(ne.d_name, ne.d_qtype, state);
+  }
+}
+
+bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state)
 {
   bool giveNegative=false;
 
@@ -930,25 +1086,13 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
   QType sqt(qtype);
   uint32_t sttl=0;
   //  cout<<"Lookup for '"<<qname<<"|"<<qtype.getName()<<"' -> "<<getLastLabel(qname)<<endl;
-
-  DNSName authname(qname);
   vState cachedState;
-  bool wasForwardedOrAuth = false;
-  bool wasAuth = false;
-  domainmap_t::const_iterator iter=getBestAuthZone(&authname);
-  if(iter != t_sstorage.domainmap->end()) {
-    wasForwardedOrAuth = true;
-    const vector<ComboAddress>& servers = iter->second.d_servers;
-    if(servers.empty()) {
-      wasAuth = true;
-    }
-  }
   NegCache::NegCacheEntry ne;
 
   if(s_rootNXTrust &&
      t_sstorage.negcache.getRootNXTrust(qname, d_now, ne) &&
       ne.d_auth.isRoot() &&
-      !(wasForwardedOrAuth && !authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
+      !(wasForwardedOrAuthZone && !authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
     sttl = ne.d_ttd - d_now.tv_sec;
     LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' & '"<<ne.d_name<<"' for another "<<sttl<<" seconds"<<endl);
     res = RCode::NXDomain;
@@ -956,7 +1100,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
     cachedState = ne.d_validationState;
   }
   else if (t_sstorage.negcache.get(qname, qtype, d_now, ne) &&
-           !(wasForwardedOrAuth && ne.d_auth != authname)) { // Only the authname nameserver can neg cache entries
+           !(wasForwardedOrAuthZone && ne.d_auth != authname)) { // Only the authname nameserver can neg cache entries
 
     /* If we are looking for a DS, discard NXD if auth == qname
        and ask for a specific denial instead */
@@ -979,6 +1123,14 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
   }
 
   if (giveNegative) {
+
+    state = cachedState;
+
+    if (!wasAuthZone && shouldValidate() && state == Indeterminate) {
+      LOG(prefix<<qname<<": got Indeterminate state for records retrieved from the negative cache, validating.."<<endl);
+      computeNegCacheValidationStatus(ne, qname, qtype, res, state, depth);
+    }
+
     // Transplant SOA to the returned packet
     addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
     if(d_doDNSSEC) {
@@ -987,8 +1139,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
       addTTLModifiedRecords(ne.DNSSECRecords.signatures, sttl, ret);
     }
 
-    LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
-    state = cachedState;
+    LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<vStates[state]<<endl);
     return true;
   }
 
@@ -1002,27 +1153,41 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
 
     LOG(prefix<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
 
-    if (d_DNSSECValidationRequested && sqt != QType::DNSKEY && wasCachedAuth && cachedState == Indeterminate && d_requireAuthData) {
+    if (!wasAuthZone && shouldValidate() && wasCachedAuth && cachedState == Indeterminate && d_requireAuthData) {
 
       /* This means we couldn't figure out the state when this entry was cached,
          most likely because we hadn't computed the zone cuts yet. */
       /* make sure they are computed before validating */
-      computeZoneCuts(sqname, g_rootdnsname, depth);
+      DNSName subdomain(sqname);
+      /* if we are retrieving a DS, we only care about the state of the parent zone */
+      if(qtype == QType::DS)
+        subdomain.chopOff();
 
-      vState recordState = getValidationStatus(qname);
+      computeZoneCuts(subdomain, g_rootdnsname, depth);
+
+      vState recordState = getValidationStatus(qname, false);
       if (recordState == Secure) {
         LOG(prefix<<sqname<<": got Indeterminate state from the cache, validating.."<<endl);
         cachedState = SyncRes::validateRecordsWithSigs(depth, sqname, sqt, sqname, cset, signatures);
+      }
+      else {
+        cachedState = recordState;
+      }
 
-        if (cachedState != Indeterminate) {
-          LOG(prefix<<qname<<": got Indeterminate state from the cache, validation result is "<<vStates[cachedState]<<endl);
-          t_RC->updateValidationStatus(d_now.tv_sec, sqname, sqt, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, cachedState);
-        }
+      if (cachedState != Indeterminate) {
+        LOG(prefix<<qname<<": got Indeterminate state from the cache, validation result is "<<vStates[cachedState]<<endl);
+        t_RC->updateValidationStatus(d_now.tv_sec, sqname, sqt, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, cachedState);
       }
     }
 
     for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
+
       LOG(j->d_content->getZoneRepresentation());
+
+      if (j->d_class != QClass::IN) {
+        continue;
+      }
+
       if(j->d_ttl>(unsigned int) d_now.tv_sec) {
         DNSRecord dr=*j;
         ttl = (dr.d_ttl-=d_now.tv_sec);
@@ -1057,7 +1222,6 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
     if(found && !expired) {
       if (!giveNegative)
         res=0;
-      d_wasOutOfBand = wasAuth;
       LOG(prefix<<qname<<": updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
       state = cachedState;
       return true;
@@ -1088,8 +1252,10 @@ inline vector<DNSName> SyncRes::shuffleInSpeedOrder(NsSet &tnameservers, const s
 {
   vector<DNSName> rnameservers;
   rnameservers.reserve(tnameservers.size());
-  for(const auto& tns:tnameservers) {
+  for(const auto& tns: tnameservers) {
     rnameservers.push_back(tns.first);
+    if(tns.first.empty()) // this was an authoritative OOB zone, don't pollute the nsSpeeds with that
+      return rnameservers;
   }
   map<DNSName, double> speeds;
 
@@ -1118,13 +1284,6 @@ inline vector<DNSName> SyncRes::shuffleInSpeedOrder(NsSet &tnameservers, const s
   return rnameservers;
 }
 
-static bool magicAddrMatch(const QType& query, const QType& answer)
-{
-  if(query.getCode() != QType::ADDR)
-    return false;
-  return answer.getCode() == QType::A || answer.getCode() == QType::AAAA;
-}
-
 static uint32_t getRRSIGTTL(const time_t now, const std::shared_ptr<RRSIGRecordContent>& rrsig)
 {
   uint32_t res = 0;
@@ -1250,13 +1409,13 @@ bool SyncRes::nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAd
   return false;
 }
 
-vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<DNSName >::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<DNSName >& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet)
+vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<DNSName >::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<DNSName >& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly)
 {
   vector<ComboAddress> result;
 
   if(!tns->empty()) {
     LOG(prefix<<qname<<": Trying to resolve NS '"<<*tns<< "' ("<<1+tns-rnameservers.begin()<<"/"<<(unsigned int)rnameservers.size()<<")"<<endl);
-    result = getAddrs(*tns, depth+2, beenthere);
+    result = getAddrs(*tns, depth+2, beenthere, cacheOnly);
     pierceDontQuery=false;
   }
   else {
@@ -1352,20 +1511,24 @@ vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds)
   auto luaLocal = g_luaconfs.getLocal();
 
   if (luaLocal->dsAnchors.empty()) {
+    LOG(d_prefix<<": No trust anchors configured, everything is Insecure"<<endl);
     /* We have no TA, everything is insecure */
     return Insecure;
   }
 
   std::string reason;
   if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
-    LOG(d_prefix<<": got NTA for "<<zone<<endl);
+    LOG(d_prefix<<": got NTA for '"<<zone<<"'"<<endl);
     return NTA;
   }
 
   if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
-    LOG(d_prefix<<": got TA for "<<zone<<endl);
+    LOG(d_prefix<<": got TA for '"<<zone<<"'"<<endl);
     return TA;
   }
+  else {
+    LOG(d_prefix<<": no TA found for '"<<zone<<"' among "<< luaLocal->dsAnchors.size()<<endl);
+  }
 
   if (zone.isRoot()) {
     /* No TA for the root */
@@ -1414,9 +1577,7 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
   }
 
   bool oldSkipCNAME = d_skipCNAMECheck;
-  bool oldRequireAuthData = d_requireAuthData;
   d_skipCNAMECheck = true;
-  d_requireAuthData = false;
 
   std::set<GetBestNSAnswer> beenthere;
   std::vector<DNSRecord> dsrecords;
@@ -1424,16 +1585,24 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
   vState state = Indeterminate;
   int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth + 1, beenthere, state);
   d_skipCNAMECheck = oldSkipCNAME;
-  d_requireAuthData = oldRequireAuthData;
 
   if (rcode == RCode::NoError || (rcode == RCode::NXDomain && !bogusOnNXD)) {
 
+    uint8_t bestDigestType = 0;
+
     if (state == Secure) {
       bool gotCNAME = false;
       for (const auto& record : dsrecords) {
         if (record.d_type == QType::DS) {
           const auto dscontent = getRR<DSRecordContent>(record);
           if (dscontent && isSupportedDS(*dscontent)) {
+            // Make GOST a lower prio than SHA256
+            if (dscontent->d_digesttype == DNSSECKeeper::GOST && bestDigestType == DNSSECKeeper::SHA256) {
+              continue;
+            }
+            if (dscontent->d_digesttype > bestDigestType || (bestDigestType == DNSSECKeeper::GOST && dscontent->d_digesttype == DNSSECKeeper::SHA256)) {
+              bestDigestType = dscontent->d_digesttype;
+            }
             ds.insert(*dscontent);
           }
         }
@@ -1442,6 +1611,19 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
         }
       }
 
+      /* RFC 4509 section 3: "Validator implementations SHOULD ignore DS RRs containing SHA-1
+       * digests if DS RRs with SHA-256 digests are present in the DS RRset."
+       * As SHA348 is specified as well, the spirit of the this line is "use the best algorithm".
+       */
+      for (auto dsrec = ds.begin(); dsrec != ds.end(); ) {
+        if (dsrec->d_digesttype != bestDigestType) {
+          dsrec = ds.erase(dsrec);
+        }
+        else {
+          ++dsrec;
+        }
+      }
+
       if (rcode == RCode::NoError && ds.empty()) {
         if (foundCut) {
           if (gotCNAME || denialProvesNoDelegation(zone, dsrecords)) {
@@ -1469,7 +1651,7 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
 
 bool SyncRes::haveExactValidationStatus(const DNSName& domain)
 {
-  if (!d_DNSSECValidationRequested) {
+  if (!shouldValidate()) {
     return false;
   }
   const auto& it = d_cutStates.find(domain);
@@ -1483,7 +1665,7 @@ vState SyncRes::getValidationStatus(const DNSName& subdomain, bool allowIndeterm
 {
   vState result = Indeterminate;
 
-  if (!d_DNSSECValidationRequested) {
+  if (!shouldValidate()) {
     return result;
   }
   DNSName name(subdomain);
@@ -1530,7 +1712,7 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
   LOG(d_prefix<<": setting cut state for "<<end<<" to "<<vStates[cutState]<<endl);
   d_cutStates[end] = cutState;
 
-  if (!d_DNSSECValidationRequested) {
+  if (!shouldValidate()) {
     return;
   }
 
@@ -1538,9 +1720,7 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
   std::vector<string> labelsToAdd = begin.makeRelative(end).getRawLabels();
 
   bool oldSkipCNAME = d_skipCNAMECheck;
-  bool oldRequireAuthData = d_requireAuthData;
   d_skipCNAMECheck = true;
-  d_requireAuthData = false;
 
   while(qname != begin) {
     if (labelsToAdd.empty())
@@ -1563,8 +1743,8 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
        just look for (N)TA
     */
     if (cutState == Insecure || cutState == Bogus) {
-      dsmap_t ds;
-      vState newState = getDSRecords(qname, ds, true, depth);
+      dsmap_t cutDS;
+      vState newState = getDSRecords(qname, cutDS, true, depth);
       if (newState == Indeterminate) {
         continue;
       }
@@ -1592,13 +1772,12 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
     }
     else {
       /* remove the temporary cut */
-      LOG(d_prefix<<qname<<": removing cut state for "<<qname<<", was "<<vStates[d_cutStates[qname]]<<endl);
+      LOG(d_prefix<<qname<<": removing cut state for "<<qname<<endl);
       d_cutStates.erase(qname);
     }
   }
 
   d_skipCNAMECheck = oldSkipCNAME;
-  d_requireAuthData = oldRequireAuthData;
 
   LOG(d_prefix<<": list of cuts from "<<begin<<" to "<<end<<endl);
   for (const auto& cut : d_cutStates) {
@@ -1614,7 +1793,7 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
   if (!signatures.empty()) {
     DNSName signer = getSigner(signatures);
 
-    if (!signer.empty() && signer.isPartOf(zone)) {
+    if (!signer.empty() && zone.isPartOf(signer)) {
       vState state = getDSRecords(signer, ds, false, depth);
 
       if (state != Secure) {
@@ -1721,24 +1900,8 @@ vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& qname
   return Bogus;
 }
 
-RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state, bool& needWildcardProof)
+RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state, bool& needWildcardProof, unsigned int& wildcardLabelsCount)
 {
-  struct CacheEntry
-  {
-    vector<DNSRecord> records;
-    vector<shared_ptr<RRSIGRecordContent>> signatures;
-    uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
-  };
-  struct CacheKey
-  {
-    DNSName name;
-    uint16_t type;
-    DNSResourceRecord::Place place;
-    bool operator<(const CacheKey& rhs) const {
-      return tie(name, type) < tie(rhs.name, rhs.type);
-    }
-  };
-  typedef map<CacheKey, CacheEntry> tcache_t;
   tcache_t tcache;
 
   string prefix;
@@ -1748,12 +1911,20 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
   }
 
   std::vector<std::shared_ptr<DNSRecord>> authorityRecs;
+  const unsigned int labelCount = qname.countLabels();
   bool isCNAMEAnswer = false;
   for(const auto& rec : lwr.d_records) {
+    if (rec.d_class != QClass::IN) {
+      continue;
+    }
+
     if(!isCNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::CNAME && (!(qtype==QType(QType::CNAME))) && rec.d_name == qname) {
       isCNAMEAnswer = true;
     }
 
+    /* if we have a positive answer synthetized from a wildcard,
+       we need to store the corresponding NSEC/NSEC3 records proving
+       that the exact name did not exist in the negative cache */
     if(needWildcardProof) {
       if (nsecTypes.count(rec.d_type)) {
         authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
@@ -1768,14 +1939,14 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
     if(rec.d_type == QType::RRSIG) {
       auto rrsig = getRR<RRSIGRecordContent>(rec);
       if (rrsig) {
-        unsigned int labelCount = rec.d_name.countLabels();
         /* As illustrated in rfc4035's Appendix B.6, the RRSIG label
            count can be lower than the name's label count if it was
-           synthesized from the wildcard. Note that the difference might
+           synthetized from the wildcard. Note that the difference might
            be > 1. */
         if (rec.d_name == qname && rrsig->d_labels < labelCount) {
-          LOG(prefix<<qname<<": RRSIG indicates the name was expanded from a wildcard, we need a wildcard proof"<<endl);
+          LOG(prefix<<qname<<": RRSIG indicates the name was synthetized from a wildcard, we need a wildcard proof"<<endl);
           needWildcardProof = true;
+          wildcardLabelsCount = rrsig->d_labels;
         }
 
         //         cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"'"<<endl;
@@ -1791,9 +1962,14 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
       LOG(prefix<<qname<<": OPT answer '"<<rec.d_name<<"' from '"<<auth<<"' nameservers" <<endl);
       continue;
     }
-    LOG(prefix<<qname<<": accept answer '"<<rec.d_name<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"|"<<rec.d_content->getZoneRepresentation()<<"' from '"<<auth<<"' nameservers? "<<(int)rec.d_place<<" ");
+    LOG(prefix<<qname<<": accept answer '"<<rec.d_name<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"|"<<rec.d_content->getZoneRepresentation()<<"' from '"<<auth<<"' nameservers? ttl="<<rec.d_ttl<<", place="<<(int)rec.d_place<<" ");
     if(rec.d_type == QType::ANY) {
-      LOG("NO! - we don't accept 'ANY' data"<<endl);
+      LOG("NO! - we don't accept 'ANY'-typed data"<<endl);
+      continue;
+    }
+
+    if(rec.d_class != QClass::IN) {
+      LOG("NO! - we don't accept records for any other class than 'IN'"<<endl);
       continue;
     }
 
@@ -1863,8 +2039,20 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
     if(i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
       continue;
 
-    bool isAA = lwr.d_aabit;
-    if (isAA && isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME)) {
+    /* Even if the AA bit is set, additional data cannot be considered
+       as authoritative. This is especially important during validation
+       because keeping records in the additional section is allowed even
+       if the corresponding RRSIGs are not included, without setting the TC
+       bit, as stated in rfc4035's section 3.1.1.  Including RRSIG RRs in a Response:
+       "When placing a signed RRset in the Additional section, the name
+       server MUST also place its RRSIG RRs in the Additional section.
+       If space does not permit inclusion of both the RRset and its
+       associated RRSIG RRs, the name server MAY retain the RRset while
+       dropping the RRSIG RRs.  If this happens, the name server MUST NOT
+       set the TC bit solely because these RRSIG RRs didn't fit."
+    */
+    bool isAA = lwr.d_aabit && i->first.place != DNSResourceRecord::ADDITIONAL;
+    if (isAA && isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME || i->first.name != qname)) {
       /*
         rfc2181 states:
         Note that the answer section of an authoritative answer normally
@@ -1881,7 +2069,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
     vState recordState = getValidationStatus(i->first.name, false);
     LOG(d_prefix<<": got initial zone status "<<vStates[recordState]<<" for record "<<i->first.name<<endl);
 
-    if (d_DNSSECValidationRequested && recordState == Secure) {
+    if (shouldValidate() && recordState == Secure) {
       vState initialState = recordState;
 
       if (isAA) {
@@ -1905,6 +2093,8 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         }
       }
       else {
+        recordState = Indeterminate;
+
         /* in a non authoritative answer, we only care about the DS record (or lack of)  */
         if ((i->first.type == QType::DS || i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && i->first.place == DNSResourceRecord::AUTHORITY) {
           LOG(d_prefix<<"Validating DS record for "<<i->first.name<<endl);
@@ -1912,12 +2102,12 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         }
       }
 
-      if (initialState == Secure && state != recordState) {
+      if (initialState == Secure && state != recordState && isAA) {
         updateValidationState(state, recordState);
       }
     }
     else {
-      if (d_DNSSECValidationRequested) {
+      if (shouldValidate()) {
         LOG(d_prefix<<"Skipping validation because the current state is "<<vStates[recordState]<<endl);
       }
     }
@@ -1928,7 +2118,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
        - denial of existence proofs for negative responses are stored in the negative cache
     */
     if (i->first.type != QType::NSEC3) {
-      t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, authorityRecs, isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
+      t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
     }
 
     if(i->first.place == DNSResourceRecord::ANSWER && ednsmask)
@@ -1938,33 +2128,36 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
   return RCode::NoError;
 }
 
-void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState expectedState, bool allowOptOut, bool referralToUnsigned)
+void SyncRes::updateDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState denialState, const dState expectedState, bool allowOptOut)
 {
-  ne.d_validationState = state;
-
-  if (state == Secure) {
-    cspmap_t csp = harvestCSPFromNE(ne);
-    dState res = getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == NXQTYPE);
-    if (res != expectedState) {
-      if (res == OPTOUT && allowOptOut) {
-        LOG(d_prefix<<"OPT-out denial found for "<<ne.d_name<<endl);
-        ne.d_validationState = Secure;
-        return;
-      }
-      else if (res == INSECURE) {
-        LOG(d_prefix<<"Insecure denial found for "<<ne.d_name<<", returning Insecure"<<endl);
-        ne.d_validationState = Insecure;
-      }
-      else {
-        LOG(d_prefix<<"Invalid denial found for "<<ne.d_name<<", returning Bogus, res="<<res<<", expectedState="<<expectedState<<endl);
-        ne.d_validationState = Bogus;
-      }
-      updateValidationState(state, ne.d_validationState);
+  if (denialState == expectedState) {
+    ne.d_validationState = Secure;
+  }
+  else {
+    if (denialState == OPTOUT && allowOptOut) {
+      LOG(d_prefix<<"OPT-out denial found for "<<ne.d_name<<endl);
+      ne.d_validationState = Secure;
+      return;
+    }
+    else if (denialState == INSECURE) {
+      LOG(d_prefix<<"Insecure denial found for "<<ne.d_name<<", returning Insecure"<<endl);
+      ne.d_validationState = Insecure;
     }
+    else {
+      LOG(d_prefix<<"Invalid denial found for "<<ne.d_name<<", returning Bogus, res="<<denialState<<", expectedState="<<expectedState<<endl);
+      ne.d_validationState = Bogus;
+    }
+    updateValidationState(state, ne.d_validationState);
   }
 }
 
-bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, bool needWildcardProof)
+dState SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, const vState state, const dState expectedState, bool referralToUnsigned)
+{
+  cspmap_t csp = harvestCSPFromNE(ne);
+  return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == NXQTYPE);
+}
+
+bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const unsigned int wildcardLabelsCount)
 {
   bool done = false;
 
@@ -1983,15 +2176,28 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       NegCache::NegCacheEntry ne;
 
       uint32_t lowestTTL = rec.d_ttl;
-      ne.d_name = qname;
+      /* if we get an NXDomain answer with a CNAME, the name
+         does exist but the target does not */
+      ne.d_name = newtarget.empty() ? qname : newtarget;
       ne.d_qtype = QType(0); // this encodes 'whole record'
       ne.d_auth = rec.d_name;
       harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
       ne.d_ttd = d_now.tv_sec + lowestTTL;
 
-      getDenialValidationState(ne, state, NXDOMAIN, false, false);
+      if (state == Secure) {
+        dState denialState = getDenialValidationState(ne, state, NXDOMAIN, false);
+        updateDenialValidationState(ne, state, denialState, NXDOMAIN, false);
+      }
+      else {
+        ne.d_validationState = state;
+      }
 
-      if(!wasVariable()) {
+      /* if we get an NXDomain answer with a CNAME, let's not cache the
+         target, even the server was authoritative for it,
+         and do an additional query for the CNAME target.
+         We have a regression test making sure we do exactly that.
+      */
+      if(!wasVariable() && newtarget.empty()) {
         t_sstorage.negcache.add(ne);
         if(s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot()) {
           ne.d_name = ne.d_name.getLastLabel();
@@ -2007,13 +2213,16 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         newtarget=content->getTarget();
       }
     }
+    /* if we have a positive answer synthetized from a wildcard, we need to
+       return the corresponding NSEC/NSEC3 records from the AUTHORITY section
+       proving that the exact name did not exist */
     else if(needWildcardProof && (rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::AUTHORITY) {
       ret.push_back(rec); // enjoy your DNSSEC
     }
     // for ANY answers we *must* have an authoritative answer, unless we are forwarding recursively
     else if(rec.d_place==DNSResourceRecord::ANSWER && rec.d_name == qname &&
             (
-              rec.d_type==qtype.getCode() || (lwr.d_aabit && (qtype==QType(QType::ANY) || magicAddrMatch(qtype, QType(rec.d_type)) ) ) || sendRDQuery
+              rec.d_type==qtype.getCode() || ((lwr.d_aabit || sendRDQuery) && qtype == QType(QType::ANY))
               )
       )
     {
@@ -2021,6 +2230,38 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
       done=true;
       ret.push_back(rec);
+
+      if (state == Secure && needWildcardProof) {
+        /* We have a positive answer synthetized from a wildcard, we need to check that we have
+           proof that the exact name doesn't exist so the wildcard can be used,
+           as described in section 5.3.4 of RFC 4035 and 5.3 of FRC 7129.
+        */
+        NegCache::NegCacheEntry ne;
+
+        uint32_t lowestTTL = rec.d_ttl;
+        ne.d_name = qname;
+        ne.d_qtype = QType(0); // this encodes 'whole record'
+        harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+
+        cspmap_t csp = harvestCSPFromNE(ne);
+        dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, false, wildcardLabelsCount);
+        if (res != NXDOMAIN) {
+          vState st = Bogus;
+          if (res == INSECURE) {
+            /* Some part could not be validated, for example a NSEC3 record with a too large number of iterations,
+               this is not enough to warrant a Bogus, but go Insecure. */
+            st = Insecure;
+            LOG(d_prefix<<"Unable to validate denial in wildcard expanded positive response found for "<<qname<<", returning Insecure, res="<<res<<endl);
+          }
+          else {
+            LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<<qname<<", returning Bogus, res="<<res<<endl);
+          }
+
+          updateValidationState(state, st);
+          /* we already stored the record with a different validation status, let's fix it */
+          t_RC->updateValidationStatus(d_now.tv_sec, qname, qtype, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, lwr.d_aabit, st);
+        }
+      }
     }
     else if((rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::ANSWER) {
       if(rec.d_type != QType::RRSIG || rec.d_name == qname)
@@ -2053,24 +2294,13 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         uint32_t lowestTTL = rec.d_ttl;
         harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
 
-        cspmap_t csp = harvestCSPFromNE(ne);
-        dState denialState = getDenial(csp, newauth, QType::DS, true, true);
+        dState denialState = getDenialValidationState(ne, state, NXQTYPE, true);
+
         if (denialState == NXQTYPE || denialState == OPTOUT || denialState == INSECURE) {
           ne.d_ttd = lowestTTL + d_now.tv_sec;
           ne.d_validationState = Secure;
           LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
 
-          auto cut = d_cutStates.find(newauth);
-          if (cut != d_cutStates.end()) {
-            if (cut->second == Indeterminate) {
-              cut->second = Insecure;
-            }
-          }
-          else {
-            LOG(prefix<<qname<<": setting cut state for "<<newauth<<" to "<<vStates[Insecure]<<endl);
-            d_cutStates[newauth] = Insecure;
-          }
-
           if(!wasVariable()) {
             t_sstorage.negcache.add(ne);
           }
@@ -2102,7 +2332,12 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
         ne.d_ttd = d_now.tv_sec + lowestTTL;
 
-        getDenialValidationState(ne, state, NXQTYPE, qtype == QType::DS, false);
+        if (state == Secure) {
+          dState denialState = getDenialValidationState(ne, state, NXQTYPE, false);
+          updateDenialValidationState(ne, state, denialState, NXQTYPE, qtype == QType::DS);
+        } else {
+          ne.d_validationState = state;
+        }
 
         if(!wasVariable()) {
           if(qtype.getCode()) {  // prevents us from blacking out a whole domain
@@ -2119,7 +2354,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
 bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType& qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool* truncated)
 {
-  int resolveret;
+  int resolveret = RCode::NoError;
   s_outqueries++;
   d_outqueries++;
 
@@ -2253,7 +2488,8 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   }
 
   bool needWildcardProof = false;
-  *rcode = updateCacheFromRecords(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof);
+  unsigned int wildcardLabelsCount;
+  *rcode = updateCacheFromRecords(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof, wildcardLabelsCount);
   if (*rcode != RCode::NoError) {
     return true;
   }
@@ -2265,7 +2501,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   DNSName newauth;
   DNSName newtarget;
 
-  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof);
+  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof, wildcardLabelsCount);
 
   if(done){
     LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
@@ -2396,10 +2632,13 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
         return -1;
       }
 
-      // this line needs to identify the 'self-resolving' behaviour, but we get it wrong now
-      if(qname == *tns && qtype.getCode()==QType::A && rnameservers.size() > (size_t)(1+1*s_doIPv6)) {
-        LOG(prefix<<qname<<": Not using NS to resolve itself! ("<<(1+tns-rnameservers.cbegin())<<"/"<<rnameservers.size()<<")"<<endl);
-        continue;
+      bool cacheOnly = false;
+      // this line needs to identify the 'self-resolving' behaviour
+      if(qname == *tns && (qtype.getCode() == QType::A || qtype.getCode() == QType::AAAA)) {
+        /* we might have a glue entry in cache so let's try this NS
+           but only if we have enough in the cache to know how to reach it */
+        LOG(prefix<<qname<<": Using NS to resolve itself, but only using what we have in cache ("<<(1+tns-rnameservers.cbegin())<<"/"<<rnameservers.size()<<")"<<endl);
+        cacheOnly = true;
       }
 
       typedef vector<ComboAddress> remoteIPs_t;
@@ -2415,7 +2654,9 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
 
       if(tns->empty() && !wasForwarded) {
         LOG(prefix<<qname<<": Domain is out-of-band"<<endl);
-        state = Insecure;
+        /* setting state to indeterminate since validation is disabled for local auth zone,
+           and Insecure would be misleading. */
+        state = Indeterminate;
         d_wasOutOfBand = doOOBResolve(qname, qtype, lwr.d_records, depth, lwr.d_rcode);
         lwr.d_tcbit=false;
         lwr.d_aabit=true;
@@ -2431,7 +2672,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
       }
       else {
         /* if tns is empty, retrieveAddressesForNS() knows we have hardcoded servers (i.e. "forwards") */
-        remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet);
+        remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly);
 
         if(remoteIPs.empty()) {
           LOG(prefix<<qname<<": Failed to get IP for NS "<<*tns<<", trying next if available"<<endl);