]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - pdns/syncres.cc
Merge pull request #5570 from rgacogne/rec-neg-validation
[thirdparty/pdns.git] / pdns / syncres.cc
index 8a6c1e5806c690efd3d327c292a4a6c2de81e05d..1b1e70208015439414745a20903057cc295c05fd 100644 (file)
@@ -64,6 +64,8 @@ std::atomic<uint64_t> SyncRes::s_throttledqueries;
 std::atomic<uint64_t> SyncRes::s_dontqueries;
 std::atomic<uint64_t> SyncRes::s_nodelegated;
 std::atomic<uint64_t> SyncRes::s_unreachables;
+std::atomic<uint64_t> SyncRes::s_ecsqueries;
+std::atomic<uint64_t> SyncRes::s_ecsresponses;
 uint8_t SyncRes::s_ecsipv4limit;
 uint8_t SyncRes::s_ecsipv6limit;
 bool SyncRes::s_doIPv6;
@@ -118,7 +120,7 @@ int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qcl
   d_wasOutOfBand=false;
 
   if (doSpecialNamesResolve(qname, qtype, qclass, ret)) {
-    d_queryValidationState = Secure;
+    d_queryValidationState = Insecure;
     return 0;
   }
 
@@ -133,6 +135,14 @@ int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qcl
   set<GetBestNSAnswer> beenthere;
   int res=doResolve(qname, qtype, ret, 0, beenthere, state);
   d_queryValidationState = state;
+
+  if (d_queryValidationState != Indeterminate) {
+    g_stats.dnssecValidations++;
+  }
+  if (d_DNSSECValidationRequested) {
+    increaseDNSSECStateCounter(d_queryValidationState);
+  }
+
   return res;
 }
 
@@ -558,14 +568,17 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
   NsSet nsset;
   bool flawedNSSet=false;
 
+  computeZoneCuts(qname, g_rootdnsname, depth);
+
   // 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
   }
 
-  state = getValidationStatus(subdomain, depth);
-  LOG("Initial validation status for "<<qname<<" inherited from "<<subdomain<<" is "<<vStates[state]<<endl);
+  state = getValidationStatus(subdomain);
+
+  LOG(prefix<<qname<<": initial validation status for "<<qname<<" inherited from "<<subdomain<<" is "<<vStates[state]<<endl);
 
   if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state)))
     return 0;
@@ -630,7 +643,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
     if(done) {
       if(j==1 && s_doIPv6) { // we got an A record, see if we have some AAAA lying around
        vector<DNSRecord> cset;
-       if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_requestor) > 0) {
+       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)) {
@@ -684,7 +697,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
     vector<DNSRecord> ns;
     *flawedNSSet = false;
 
-    if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_requestor, nullptr) > 0) {
+    if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 0) {
       for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
         if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
           vector<DNSRecord> aset;
@@ -692,7 +705,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
           const DNSRecord& dr=*k;
          auto nrr = getRR<NSRecordContent>(dr);
           if(nrr && (!nrr->getNS().isPartOf(subdomain) || t_RC->get(d_now.tv_sec, nrr->getNS(), s_doIPv6 ? QType(QType::ADDR) : QType(QType::A),
-                                                                    false, doLog() ? &aset : nullptr, d_requestor) > 5)) {
+                                                                    false, doLog() ? &aset : 0, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 5)) {
             bestns.push_back(dr);
             LOG(prefix<<qname<<": NS (with ip, or non-glue) in cache for '"<<subdomain<<"' -> '"<<nrr->getNS()<<"'"<<endl);
             LOG(prefix<<qname<<": within bailiwick: "<< nrr->getNS().isPartOf(subdomain));
@@ -810,11 +823,32 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
   LOG(prefix<<qname<<": Looking for CNAME cache hit of '"<<qname<<"|CNAME"<<"'"<<endl);
   vector<DNSRecord> cset;
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
-  if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_requestor, &signatures, &d_wasVariable, &state) > 0) {
+  vector<std::shared_ptr<DNSRecord>> authorityRecs;
+  bool wasAuth;
+  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_ttl>(unsigned int) d_now.tv_sec) {
+
+        if (d_DNSSECValidationRequested && 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);
+
+          vState recordState = getValidationStatus(qname);
+          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);
+            if (state != Indeterminate) {
+              LOG(prefix<<qname<<": got Indeterminate state from the CNAME cache, new validation result is "<<vStates[state]<<endl);
+              t_RC->updateValidationStatus(d_now.tv_sec, qname, QType(QType::CNAME), d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, state);
+            }
+          }
+        }
+
         LOG(prefix<<qname<<": Found cache CNAME hit for '"<< qname << "|CNAME" <<"' to '"<<j->d_content->getZoneRepresentation()<<"', validation state is "<<vStates[state]<<endl);
+
         DNSRecord dr=*j;
         dr.d_ttl-=d_now.tv_sec;
         ret.push_back(dr);
@@ -830,12 +864,18 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
           ret.push_back(sigdr);
         }
 
+        for(const auto& rec : authorityRecs) {
+          DNSRecord dr(*rec);
+          dr.d_ttl=j->d_ttl - d_now.tv_sec;
+          ret.push_back(dr);
+        }
+
         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("Updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+          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
@@ -931,7 +971,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
     if(d_doDNSSEC)
       addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
 
-    LOG("Updating validation state with negative cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+    LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
     state = cachedState;
     return true;
   }
@@ -939,9 +979,32 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
   vector<DNSRecord> cset;
   bool found=false, expired=false;
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  vector<std::shared_ptr<DNSRecord>> authorityRecs;
   uint32_t ttl=0;
-  if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_requestor, d_doDNSSEC ? &signatures : nullptr, &d_wasVariable, &cachedState) > 0) {
+  bool wasCachedAuth;
+  if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) {
+
     LOG(prefix<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
+
+    if (d_DNSSECValidationRequested && sqt != QType::DNSKEY && 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);
+
+      vState recordState = getValidationStatus(qname);
+      if (recordState == Secure) {
+        LOG(prefix<<sqname<<": got Indeterminate state from the cache, validating.."<<endl);
+        cachedState = SyncRes::validateRecordsWithSigs(depth, sqname, sqt, sqname, cset, signatures);
+
+        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_ttl>(unsigned int) d_now.tv_sec) {
@@ -967,13 +1030,19 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
       dr.d_class=QClass::IN;
       ret.push_back(dr);
     }
-  
+
+    for(const auto& rec : authorityRecs) {
+      DNSRecord dr(*rec);
+      dr.d_ttl=ttl;
+      ret.push_back(dr);
+    }
+
     LOG(endl);
     if(found && !expired) {
       if (!giveNegative)
         res=0;
       d_wasOutOfBand = wasAuth;
-      LOG("Updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+      LOG(prefix<<qname<<": updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
       state = cachedState;
       return true;
     }
@@ -1040,13 +1109,14 @@ static bool magicAddrMatch(const QType& query, const QType& answer)
   return answer.getCode() == QType::A || answer.getCode() == QType::AAAA;
 }
 
+static const set<uint16_t> nsecTypes = {QType::NSEC, QType::NSEC3};
+
 /* Fills the authoritySOA and DNSSECRecords fields from ne with those found in the records
  *
  * \param records The records to parse for the authority SOA and NSEC(3) records
  * \param ne      The NegCacheEntry to be filled out (will not be cleared, only appended to
  */
 static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne) {
-  static const set<uint16_t> nsecTypes = {QType::NSEC, QType::NSEC3};
   for(const auto& rec : records) {
     if(rec.d_place != DNSResourceRecord::AUTHORITY)
       // RFC 4035 section 3.1.3. indicates that NSEC records MUST be placed in
@@ -1196,6 +1266,8 @@ uint32_t SyncRes::computeLowestTTD(const std::vector<DNSRecord>& records, const
   for(const auto& record : records)
     lowestTTD = min(lowestTTD, record.d_ttl);
 
+  /* even if it was not requested for that request (Process, and neither AD nor DO set),
+     it might be requested at a later time so we need to be careful with the TTL. */
   if (validationEnabled() && !signatures.empty()) {
     /* if we are validating, we don't want to cache records after their signatures
        expires. */
@@ -1217,20 +1289,23 @@ void SyncRes::updateValidationState(vState& state, const vState stateUpdate)
 {
   LOG(d_prefix<<"validation state was "<<std::string(vStates[state])<<", state update is "<<std::string(vStates[stateUpdate])<<endl);
 
-  if (state == Indeterminate) {
-    state = stateUpdate;
+  if (stateUpdate == TA) {
+    state = Secure;
   }
   else if (stateUpdate == NTA) {
     state = Insecure;
   }
+  else if (stateUpdate == Bogus) {
+    state = Bogus;
+  }
+  else if (state == Indeterminate) {
+    state = stateUpdate;
+  }
   else if (stateUpdate == Insecure) {
     if (state != Bogus) {
       state = Insecure;
     }
   }
-  else if (stateUpdate == Bogus) {
-    state = Bogus;
-  }
   LOG(d_prefix<<" validation state is now "<<std::string(vStates[state])<<endl);
 }
 
@@ -1245,13 +1320,13 @@ vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds)
 
   std::string reason;
   if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
-    LOG("Got NTA for "<<zone<<endl);
+    LOG(d_prefix<<": got NTA for "<<zone<<endl);
     return NTA;
   }
 
   if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
-    LOG("Got TA for "<<zone<<endl);
-    return Secure;
+    LOG(d_prefix<<": got TA for "<<zone<<endl);
+    return TA;
   }
 
   if (zone.isRoot()) {
@@ -1280,7 +1355,7 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
   vState result = getTA(zone, ds);
 
   if (result != Indeterminate || taOnly) {
-    if (result == Secure && countSupportedDS(ds) == 0) {
+    if ((result == Secure || result == TA) && countSupportedDS(ds) == 0) {
       ds.clear();
       result = Insecure;
     }
@@ -1297,11 +1372,11 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
   std::vector<DNSRecord> dsrecords;
 
   vState state = Indeterminate;
-  int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth, beenthere, state);
+  int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth + 1, beenthere, state);
   d_skipCNAMECheck = oldSkipCNAME;
   d_requireAuthData = oldRequireAuthData;
 
-  if (rcode == RCode::NoError) {
+  if (rcode == RCode::NoError || rcode == RCode::NXDomain) {
     if (state == Secure) {
       for (const auto& record : dsrecords) {
         if (record.d_type == QType::DS) {
@@ -1311,113 +1386,156 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
           }
         }
       }
-    }
 
-    if (ds.empty()) {
-      return Insecure;
+      if (ds.empty()) {
+        return Insecure;
+      }
     }
+
     return state;
   }
 
-  LOG("Returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
+  LOG(d_prefix<<": returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
   return Bogus;
 }
 
-vState SyncRes::getValidationStatus(const DNSName& subdomain, unsigned int depth)
+bool SyncRes::haveExactValidationStatus(const DNSName& domain)
 {
-  if (!validationEnabled()) {
-    return Indeterminate;
-  }
-
-  dsmap_t ds;
-  vState result = getTA(subdomain, ds);
-  if (result != Indeterminate) {
-    if (result == NTA) {
-      result = Insecure;
-    }
-    else if (result == Secure && countSupportedDS(ds) == 0) {
-      ds.clear();
-      result = Insecure;
-    }
-
-    return result;
-  }
-
-  vector<DNSRecord> cset;
-  vector<shared_ptr<RRSIGRecordContent> > signatures;
-  if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::DS), false, &cset, d_requestor, &signatures, nullptr, &result) > 0) {
-    if (result != Indeterminate) {
-      return result;
-    }
+  if (!d_DNSSECValidationRequested) {
+    return false;
   }
-
-  /* we are out of luck */
-  DNSName higher(subdomain);
-  if (!higher.chopOff()) {
-    /* No (N)TA for ".", something is very wrong */
-    return Bogus;
+  const auto& it = d_cutStates.find(domain);
+  if (it != d_cutStates.cend()) {
+    return true;
   }
+  return false;
+}
 
-  result = getValidationStatus(higher, depth);
+vState SyncRes::getValidationStatus(const DNSName& subdomain)
+{
+  vState result = Indeterminate;
 
-  if (result != Secure) {
-    /* we know we don't have any (N)TA, so we are done */
+  if (!d_DNSSECValidationRequested) {
     return result;
   }
-
-  /* get subdomain DS */
-  result = getDSRecords(subdomain, ds, false, depth);
-
-  if (result != Secure) {
-    if (result == NTA) {
-      result = Insecure;
+  DNSName name(subdomain);
+  do {
+    const auto& it = d_cutStates.find(name);
+    if (it != d_cutStates.cend()) {
+      LOG(d_prefix<<": got status "<<vStates[it->second]<<" for name "<<subdomain<<" (from "<<name<<")"<<endl);
+      return it->second;
     }
-    return result;
-  }
-
-  if (ds.empty()) {
-    return Insecure;
   }
+  while (name.chopOff());
 
-  return Secure;
+  return result;
 }
 
-void SyncRes::updateValidationStatusAfterReferral(const DNSName& newauth, vState& state, unsigned int depth)
+void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned int depth)
 {
-  if (!validationEnabled()) {
+  if(!begin.isPartOf(end)) {
+    LOG(d_prefix<<" "<<end.toLogString()<<" is not part of "<<begin.toString()<<endl);
+    throw PDNSException(end.toLogString() + " is not part of " + begin.toString());
+  }
+
+  if (d_cutStates.count(begin) != 0) {
     return;
   }
 
   dsmap_t ds;
-  vState newState = getDSRecords(newauth, ds, state == Insecure || state == Bogus, depth);
-  if (newState == Indeterminate) {
-    /* no (N)TA */
-    return;
+  vState cutState = getDSRecords(end, ds, false, depth);
+  if (cutState == TA) {
+    cutState = Secure;
+  }
+  else if (cutState == NTA) {
+    cutState = Insecure;
   }
+  LOG(d_prefix<<": setting cut state for "<<end<<" to "<<vStates[cutState]<<endl);
+  d_cutStates[end] = cutState;
 
-  if (newState == NTA) {
-    updateValidationState(state, Insecure);
+  if (!d_DNSSECValidationRequested) {
+    return;
   }
-  else if (state == Secure) {
-    if (ds.empty()) {
-      updateValidationState(state, Insecure);
+
+  DNSName qname(end);
+  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) {
+    bool foundCut = false;
+    if (labelsToAdd.empty())
+      break;
+
+    qname.prependRawLabel(labelsToAdd.back());
+    labelsToAdd.pop_back();
+    LOG(d_prefix<<": - Looking for a cut at "<<qname<<endl);
+
+    const auto cutIt = d_cutStates.find(qname);
+    if (cutIt != d_cutStates.cend()) {
+      if (cutIt->second != Indeterminate) {
+        LOG(d_prefix<<": - Cut already known at "<<qname<<endl);
+        continue;
+      }
     }
-    else {
-      updateValidationState(state, Secure);
+
+    std::set<GetBestNSAnswer> beenthere;
+    std::vector<DNSRecord> nsrecords;
+
+    vState state = Indeterminate;
+    /* temporarily mark as Indeterminate, so that we won't enter an endless loop
+       trying to determine that zone cut again. */
+    d_cutStates[qname] = state;
+    int rcode = doResolve(qname, QType(QType::NS), nsrecords, depth + 1, beenthere, state);
+
+    if (rcode == RCode::NoError && !nsrecords.empty()) {
+      for (const auto& record : nsrecords) {
+        if(record.d_type != QType::NS || record.d_name != qname)
+          continue;
+        foundCut = true;
+        break;
+      }
+      if (foundCut) {
+        LOG(d_prefix<<": - Found cut at "<<qname<<endl);
+        /* if we get a Bogus state while retrieving the NS,
+           the cut state is Bogus (we'll look for a (N)TA below though). */
+        if (state == Bogus) {
+          cutState = Bogus;
+        }
+        dsmap_t ds;
+        vState newState = getDSRecords(qname, ds, cutState == Insecure || cutState == Bogus, depth);
+        if (newState != Indeterminate) {
+          cutState = newState;
+        }
+        LOG(d_prefix<<": New state for "<<qname<<" is "<<vStates[cutState]<<endl);
+        if (cutState == TA) {
+          cutState = Secure;
+        }
+        else if (cutState == NTA) {
+          cutState = Insecure;
+        }
+        d_cutStates[qname] = cutState;
+      }
+    }
+    if (!foundCut) {
+      /* remove the temporary cut */
+      LOG(d_prefix<<qname<<": removing cut state for "<<qname<<", was "<<vStates[d_cutStates[qname]]<<endl);
+      d_cutStates.erase(qname);
     }
   }
-  else {
-    updateValidationState(state, newState);
-  }
-}
 
-static DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
-{
-  for (const auto sig : signatures) {
-    return sig->d_signer;
-  }
+  d_skipCNAMECheck = oldSkipCNAME;
+  d_requireAuthData = oldRequireAuthData;
 
-  return DNSName();
+  LOG(d_prefix<<": list of cuts from "<<begin<<" to "<<end<<endl);
+  for (const auto& cut : d_cutStates) {
+    if (cut.first.isRoot() || (begin.isPartOf(cut.first) && cut.first.isPartOf(end))) {
+      LOG(" - "<<cut.first<<": "<<vStates[cut.second]<<endl);
+    }
+  }
 }
 
 vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth)
@@ -1428,6 +1546,10 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
 
     if (!signer.empty() && signer.isPartOf(zone)) {
       vState state = getDSRecords(signer, ds, false, depth);
+
+      if (state == TA) {
+        state = Secure;
+      }
       if (state != Secure) {
         if (state == NTA) {
           state = Insecure;
@@ -1450,14 +1572,18 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
     }
   }
 
-  LOG("Trying to validate "<<std::to_string(tentativeKeys.size())<<" DNSKEYs with "<<std::to_string(ds.size())<<" DS"<<endl);
+  LOG(d_prefix<<": trying to validate "<<std::to_string(tentativeKeys.size())<<" DNSKEYs with "<<std::to_string(ds.size())<<" DS"<<endl);
   skeyset_t validatedKeys;
   validateDNSKeysAgainstDS(d_now.tv_sec, zone, ds, tentativeKeys, toSign, signatures, validatedKeys);
 
-  LOG("We now have "<<std::to_string(validatedKeys.size())<<" DNSKEYs"<<endl);
+  LOG(d_prefix<<": we now have "<<std::to_string(validatedKeys.size())<<" DNSKEYs"<<endl);
 
+  /* if we found at least one valid RRSIG covering the set,
+     all tentative keys are validated keys. Otherwise it means
+     we haven't found at least one DNSKEY and a matching RRSIG
+     covering this set, this looks Bogus. */
   if (validatedKeys.size() != tentativeKeys.size()) {
-    LOG("Returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
+    LOG(d_prefix<<": returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
     return Bogus;
   }
 
@@ -1468,10 +1594,14 @@ vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int
 {
   std::vector<DNSRecord> records;
   std::set<GetBestNSAnswer> beenthere;
-  LOG("Retrieving DNSKeys for "<<signer<<endl);
+  LOG(d_prefix<<"Retrieving DNSKeys for "<<signer<<endl);
 
   vState state = Indeterminate;
+  /* following CNAME might lead to us to the wrong DNSKEY */
+  bool oldSkipCNAME = d_skipCNAMECheck;
+  d_skipCNAMECheck = true;
   int rcode = doResolve(signer, QType(QType::DNSKEY), records, depth + 1, beenthere, state);
+  d_skipCNAMECheck = oldSkipCNAME;
 
   if (rcode == RCode::NoError) {
     if (state == Secure) {
@@ -1484,27 +1614,31 @@ vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int
         }
       }
     }
-    LOG("Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<vStates[state]<<endl);
+    LOG(d_prefix<<"Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<vStates[state]<<endl);
     return state;
   }
 
-  LOG("Returning Bogus state from "<<__func__<<"("<<signer<<")"<<endl);
+  LOG(d_prefix<<"Returning Bogus state from "<<__func__<<"("<<signer<<")"<<endl);
   return Bogus;
 }
 
-vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& qname, const QType& qtype, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
 {
   skeyset_t keys;
   if (!signatures.empty()) {
     const DNSName signer = getSigner(signatures);
     if (!signer.empty() && name.isPartOf(signer)) {
+      if (qtype == QType::DNSKEY && signer == qname) {
+        /* we are already retrieving those keys, sorry */
+        return Indeterminate;
+      }
       vState state = getDNSKeys(signer, keys, depth);
       if (state != Secure) {
         return state;
       }
     }
   } else {
-    LOG("Bogus!"<<endl);
+    LOG(d_prefix<<"Bogus!"<<endl);
     return Bogus;
   }
 
@@ -1513,17 +1647,17 @@ vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& name,
     recordcontents.push_back(record.d_content);
   }
 
-  LOG("Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<endl);
+  LOG(d_prefix<<"Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<endl);
   if (validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, false)) {
-    LOG("Secure!"<<endl);
+    LOG(d_prefix<<"Secure!"<<endl);
     return Secure;
   }
 
-  LOG("Bogus!"<<endl);
+  LOG(d_prefix<<"Bogus!"<<endl);
   return Bogus;
 }
 
-RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state)
+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)
 {
   struct CacheEntry
   {
@@ -1549,10 +1683,37 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
     prefix.append(depth, ' ');
   }
 
+  std::vector<std::shared_ptr<DNSRecord>> authorityRecs;
+  bool isCNAMEAnswer = false;
   for(const auto& rec : lwr.d_records) {
+    if(!isCNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::CNAME && (!(qtype==QType(QType::CNAME))) && rec.d_name == qname) {
+      isCNAMEAnswer = true;
+    }
+
+    if(needWildcardProof) {
+      if (nsecTypes.count(rec.d_type)) {
+        authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
+      }
+      else if (rec.d_type == QType::RRSIG) {
+        auto rrsig = getRR<RRSIGRecordContent>(rec);
+        if (rrsig && nsecTypes.count(rrsig->d_type)) {
+          authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
+        }
+      }
+    }
     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
+           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);
+          needWildcardProof = true;
+        }
+
         //         cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"'"<<endl;
         tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
         tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL = std::min(tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL, rec.d_ttl);
@@ -1611,9 +1772,8 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         rec.d_ttl=min(s_maxcachettl, rec.d_ttl);
 
         DNSRecord dr(rec);
-        dr.d_place=DNSResourceRecord::ANSWER;
-
         dr.d_ttl += d_now.tv_sec;
+        dr.d_place=DNSResourceRecord::ANSWER;
         tcache[{rec.d_name,rec.d_type,rec.d_place}].records.push_back(dr);
       }
     }
@@ -1635,41 +1795,66 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
   }
 
   for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
-    vState recordState = state;
 
     if(i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
       continue;
 
-    if (validationEnabled() && state == Secure) {
-      if (lwr.d_aabit) {
+    bool isAA = lwr.d_aabit;
+    if (isAA && isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME)) {
+      /*
+        rfc2181 states:
+        Note that the answer section of an authoritative answer normally
+        contains only authoritative data.  However when the name sought is an
+        alias (see section 10.1.1) only the record describing that alias is
+        necessarily authoritative.  Clients should assume that other records
+        may have come from the server's cache.  Where authoritative answers
+        are required, the client should query again, using the canonical name
+        associated with the alias.
+      */
+      isAA = false;
+    }
+
+    vState recordState = getValidationStatus(auth);
+    LOG(d_prefix<<": got initial zone status "<<vStates[recordState]<<" for record "<<i->first.name<<endl);
+
+    if (d_DNSSECValidationRequested && recordState == Secure) {
+      if (isAA) {
         if (i->first.place != DNSResourceRecord::ADDITIONAL) {
           /* the additional entries can be insecure,
              like glue:
              "Glue address RRsets associated with delegations MUST NOT be signed"
           */
           if (i->first.type == QType::DNSKEY && i->first.place == DNSResourceRecord::ANSWER) {
+            LOG(d_prefix<<"Validating DNSKEY for "<<i->first.name<<endl);
             recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth);
           }
           else {
-            recordState = validateRecordsWithSigs(depth, i->first.name, i->second.records, i->second.signatures);
+            LOG(d_prefix<<"Validating non-additional record for "<<i->first.name<<endl);
+            recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, i->second.records, i->second.signatures);
+            /* we might have missed a cut (zone cut within the same auth servers), causing the NS query for an Insecure zone to seem Bogus during zone cut determination */
+            if (qtype == QType::NS && i->second.signatures.empty() && recordState == Bogus && haveExactValidationStatus(i->first.name) && getValidationStatus(i->first.name) == Indeterminate) {
+              recordState = Indeterminate;
+            }
           }
         }
       }
       else {
-        /* for non authoritative answer, we only care about the DS record */
-        if (i->first.type == QType::DS && i->first.place == DNSResourceRecord::AUTHORITY) {
-          recordState = validateRecordsWithSigs(depth, i->first.name, i->second.records, i->second.signatures);
+        /* 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);
+          recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, i->second.records, i->second.signatures);
         }
       }
+
       updateValidationState(state, recordState);
     }
     else {
-      if (validationEnabled()) {
-        LOG("Skipping validation because the current state is "<<vStates[state]<<endl);
+      if (d_DNSSECValidationRequested) {
+        LOG(d_prefix<<"Skipping validation because the current state is "<<vStates[recordState]<<endl);
       }
     }
 
-    t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, lwr.d_aabit, 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, isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
 
     if(i->first.place == DNSResourceRecord::ANSWER && ednsmask)
       d_wasVariable=true;
@@ -1678,7 +1863,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
   return RCode::NoError;
 }
 
-void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState expectedState)
+void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState expectedState, bool allowOptOut)
 {
   ne.d_validationState = state;
 
@@ -1686,12 +1871,22 @@ void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& stat
     cspmap_t csp = harvestCSPFromNE(ne);
     dState res = getDenial(csp, ne.d_name, ne.d_qtype.getCode());
     if (res != expectedState) {
-      if (ne.d_qtype.getCode() == QType::DS && res == OPTOUT) {
-        LOG("Invalid denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
+      if (res == OPTOUT && allowOptOut) {
+        LOG(d_prefix<<"OPT-out denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
+        ne.d_validationState = Secure;
+        updateValidationState(state, Insecure);
+        return;
+      }
+      else if (res == INSECURE) {
+        LOG(d_prefix<<"Insecure denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
         ne.d_validationState = Insecure;
       }
+      if (res == NXDOMAIN && expectedState == NXQTYPE) {
+        /* might happen for empty non-terminal, have fun */
+        return;
+      }
       else {
-        LOG("Invalid denial found for "<<ne.d_name<<", retuning Bogus"<<endl);
+        LOG(d_prefix<<"Invalid denial found for "<<ne.d_name<<", retuning Bogus"<<endl);
         ne.d_validationState = Bogus;
       }
       updateValidationState(state, ne.d_validationState);
@@ -1699,7 +1894,7 @@ void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& stat
   }
 }
 
-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 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)
 {
   bool done = false;
 
@@ -1714,15 +1909,17 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       rec.d_ttl = min(rec.d_ttl, s_maxnegttl);
       if(newtarget.empty()) // only add a SOA if we're not going anywhere after this
         ret.push_back(rec);
-      if(!wasVariable()) {
-        NegCache::NegCacheEntry ne;
 
-        ne.d_ttd = d_now.tv_sec + rec.d_ttl;
-        ne.d_name = qname;
-        ne.d_qtype = QType(0); // this encodes 'whole record'
-        ne.d_auth = rec.d_name;
-        harvestNXRecords(lwr.d_records, ne);
-        getDenialValidationState(ne, state, NXDOMAIN);
+      NegCache::NegCacheEntry ne;
+
+      ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+      ne.d_name = qname;
+      ne.d_qtype = QType(0); // this encodes 'whole record'
+      ne.d_auth = rec.d_name;
+      harvestNXRecords(lwr.d_records, ne);
+      getDenialValidationState(ne, state, NXDOMAIN, false);
+
+      if(!wasVariable()) {
         t_sstorage.negcache.add(ne);
         if(s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot()) {
           ne.d_name = ne.d_name.getLastLabel();
@@ -1738,10 +1935,13 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         newtarget=content->getTarget();
       }
     }
-    else if((rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::ANSWER){
+    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)
         ret.push_back(rec); // enjoy your DNSSEC
     }
+    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 &&
             (
@@ -1770,25 +1970,35 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
     else if(rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::DS && qname.isPartOf(rec.d_name)) {
       LOG(prefix<<qname<<": got DS record '"<<rec.d_name<<"' -> '"<<rec.d_content->getZoneRepresentation()<<"'"<<endl);
     }
-    else if(qtype == QType::DS && (rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3)) {
+    else if(realreferral && rec.d_place==DNSResourceRecord::AUTHORITY && (rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && newauth.isPartOf(auth)) {
       /* we might have received a denial of the DS, let's check */
       if (state == Secure) {
         NegCache::NegCacheEntry ne;
         ne.d_auth = auth;
         ne.d_ttd = d_now.tv_sec + rec.d_ttl;
-        ne.d_name = qname;
-        ne.d_qtype = qtype;
+        ne.d_name = newauth;
+        ne.d_qtype = QType::DS;
         harvestNXRecords(lwr.d_records, ne);
         cspmap_t csp = harvestCSPFromNE(ne);
-        dState denialState = getDenial(csp, qname, qtype.getCode());
-        if (denialState == NXQTYPE || denialState == OPTOUT) {
-          LOG(prefix<<qname<<": got negative indication of DS record for '"<<qname<<endl);
+        dState denialState = getDenial(csp, newauth, QType::DS);
+        if (denialState == NXQTYPE || denialState == OPTOUT || denialState == INSECURE) {
+          ne.d_validationState = Secure;
           rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
-          ret.push_back(rec);
+          LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
+          updateValidationState(state, Insecure);
+          auto cut = d_cutStates.find(newauth);
+          if (cut != d_cutStates.end()) {
+            if (cut->second == Indeterminate) {
+              cut->second = state;
+            }
+          }
+          else {
+            LOG(prefix<<qname<<": setting cut state for "<<newauth<<" to "<<vStates[state]<<endl);
+            d_cutStates[newauth] = state;
+          }
           if(!wasVariable()) {
             t_sstorage.negcache.add(ne);
           }
-          negindic = true;
         }
       }
     }
@@ -1802,14 +2012,16 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       else {
         rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
         ret.push_back(rec);
+
+        NegCache::NegCacheEntry ne;
+        ne.d_auth = rec.d_name;
+        ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+        ne.d_name = qname;
+        ne.d_qtype = qtype;
+        harvestNXRecords(lwr.d_records, ne);
+        getDenialValidationState(ne, state, NXQTYPE, qtype == QType::DS);
+
         if(!wasVariable()) {
-          NegCache::NegCacheEntry ne;
-          ne.d_auth = rec.d_name;
-          ne.d_ttd = d_now.tv_sec + rec.d_ttl;
-          ne.d_name = qname;
-          ne.d_qtype = qtype;
-          harvestNXRecords(lwr.d_records, ne);
-          getDenialValidationState(ne, state, NXQTYPE);
           if(qtype.getCode()) {  // prevents us from blacking out a whole domain
             t_sstorage.negcache.add(ne);
           }
@@ -1849,10 +2061,12 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
     ednsmask=getEDNSSubnetMask(d_requestor, qname, remoteIP);
     if(ednsmask) {
       LOG(prefix<<qname<<": Adding EDNS Client Subnet Mask "<<ednsmask->toString()<<" to query"<<endl);
+      s_ecsqueries++;
     }
     resolveret = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname,  qtype.getCode(),
                                      doTCP, sendRDQuery, &d_now, ednsmask, &lwr);    // <- we go out on the wire!
     if(ednsmask) {
+      s_ecsresponses++;
       LOG(prefix<<qname<<": Received EDNS Client Subnet Mask "<<ednsmask->toString()<<" on response"<<endl);
     }
   }
@@ -1955,7 +2169,8 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
     }
   }
 
-  *rcode = updateCacheFromRecords(depth, lwr, qname, auth, wasForwarded, ednsmask, state);
+  bool needWildcardProof = false;
+  *rcode = updateCacheFromRecords(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof);
   if (*rcode != RCode::NoError) {
     return true;
   }
@@ -1967,7 +2182,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);
+  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof);
 
   if(done){
     LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
@@ -1993,9 +2208,8 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
     set<GetBestNSAnswer> beenthere2;
     vState cnameState = Indeterminate;
     *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2, cnameState);
-    LOG("Updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+    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);
-
     return true;
   }
 
@@ -2012,6 +2226,10 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   if(nsset.empty() && !lwr.d_rcode && (negindic || lwr.d_aabit || sendRDQuery)) {
     LOG(prefix<<qname<<": status=noerror, other types may exist, but we are done "<<(negindic ? "(have negative SOA) " : "")<<(lwr.d_aabit ? "(have aa bit) " : "")<<endl);
 
+    if(state == Secure && lwr.d_aabit && !negindic) {
+      updateValidationState(state, Bogus);
+    }
+
     if(d_doDNSSEC)
       addNXNSECS(ret, lwr.d_records);
 
@@ -2021,7 +2239,6 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
 
   if(realreferral) {
     LOG(prefix<<qname<<": status=did not resolve, got "<<(unsigned int)nsset.size()<<" NS, ");
-    auth=newauth;
 
     nameservers.clear();
     for (auto const &nameserver : nsset) {
@@ -2037,7 +2254,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
     }
     LOG("looping to them"<<endl);
     *gotNewServers = true;
-    updateValidationStatusAfterReferral(newauth, state, depth);
+    auth=newauth;
 
     return false;
   }
@@ -2208,7 +2425,7 @@ boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const ComboAddress& local, c
       /* RFC7871 says we MUST NOT send any ECS if the source scope is 0 */
       return result;
     }
-    trunc = d_incomingECS->source.getMaskedNetwork();
+    trunc = d_incomingECSNetwork;
     bits = d_incomingECS->source.getBits();
   }
   else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
@@ -2260,6 +2477,7 @@ int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
   sr.setDoEDNS0(true);
   sr.setUpdatingRootNS();
   sr.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
+  sr.setDNSSECValidationRequested(g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate);
   sr.setAsyncCallback(asyncCallback);
 
   vector<DNSRecord> ret;