]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Generate EDE in more cases, specifically on unreachable auths or synthesized results.
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Wed, 14 Dec 2022 13:06:36 +0000 (14:06 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Fri, 16 Dec 2022 08:54:00 +0000 (09:54 +0100)
As there is no specific EDE for synthesised, use noError with a text.

We have to be careful here: a single client query can lead to
multiple beginResolve calls. Some of these are done after the main
result has been looked up, for example to validate the result. These
subsequent calls can generate EDE's but we do not want to copy the
EDE to the main result in those cases. A typical example would be
an absent DS for an Insecure domain. Nothing wrong with these but
we do not want the potential absent DS EDE (which could be synthesize)
to be returned with the main query,

To solve this, mimic the processing of validation state and add
an extra argument to a few methods.

I am not terribly happy with the extra argument. Maybe we should
move to an object holding the parameters and result status of the
nested or subsequent calls.  This would also avoid some of the saveX,
setX, beginResolve, restore X sequences.

So marking this as Draft for now.

pdns/recursordist/pdns_recursor.cc
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-main.hh
pdns/recursordist/syncres.cc
pdns/recursordist/syncres.hh

index 5bc0969d4cec59a9e8db8ea22a8a038266520781..2e409a9a587eef4f1fbf43e370a0fc6a81217c1d 100644 (file)
@@ -1474,7 +1474,7 @@ void startDoResolve(void* p)
 
     if (haveEDNS) {
       auto state = sr.getValidationState();
-      if (dc->d_extendedErrorCode || (g_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
+      if (dc->d_extendedErrorCode || sr.d_extendedError || (SyncRes::s_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
         EDNSExtendedError::code code;
         std::string extra;
 
@@ -1482,6 +1482,10 @@ void startDoResolve(void* p)
           code = static_cast<EDNSExtendedError::code>(*dc->d_extendedErrorCode);
           extra = std::move(dc->d_extendedErrorExtra);
         }
+        else if (sr.d_extendedError) {
+          code = static_cast<EDNSExtendedError::code>(sr.d_extendedError->infoCode);
+          extra = std::move(sr.d_extendedError->extraText);
+        }
         else {
           switch (state) {
           case vState::BogusNoValidDNSKEY:
index b30f7fa26266e371781933ccbd3a9d93c61a0c81..0160aaedde49fc706246889c9569172e19413255 100644 (file)
@@ -89,7 +89,6 @@ std::shared_ptr<NetmaskGroup> g_initialAllowNotifyFrom; // new threads need this
 std::shared_ptr<notifyset_t> g_initialAllowNotifyFor; // new threads need this to be setup
 bool g_logRPZChanges{false};
 static time_t s_statisticsInterval;
-bool g_addExtendedResolutionDNSErrors;
 static std::atomic<uint32_t> s_counter;
 int g_argc;
 char** g_argv;
@@ -1703,7 +1702,7 @@ static int serviceMain(int argc, char* argv[], Logr::log_t log)
 
   s_statisticsInterval = ::arg().asNum("statistics-interval");
 
-  g_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
+  SyncRes::s_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
 
   if (::arg().asNum("aggressive-nsec-cache-size") > 0) {
     if (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || g_dnssecmode == DNSSECMode::Process) {
index cb3787a8aea11fa7340ef2f2a1eff49d70e065ad..9416f4a8d7e7db83d006f7ac98a87c982ff02510 100644 (file)
@@ -217,7 +217,6 @@ extern bool g_useIncomingECS;
 extern boost::optional<ComboAddress> g_dns64Prefix;
 extern DNSName g_dns64PrefixReverse;
 extern uint64_t g_latencyStatSize;
-extern bool g_addExtendedResolutionDNSErrors;
 extern NetmaskGroup g_proxyProtocolACL;
 extern std::atomic<bool> g_statsWanted;
 extern uint32_t g_disthashseed;
index 79201e4f0f174b1a80d5fb93279785a27c947892..ce92c99d39b7c4c59a26aeabd57db5ab7ac23166 100644 (file)
@@ -467,6 +467,7 @@ bool SyncRes::s_dot_to_port_853;
 int SyncRes::s_event_trace_enabled;
 bool SyncRes::s_save_parent_ns_set;
 unsigned int SyncRes::s_max_busy_dot_probes;
+bool SyncRes::s_addExtendedResolutionDNSErrors;
 
 #define LOG(x)                     \
   if (d_lm == Log) {               \
@@ -519,7 +520,8 @@ void SyncRes::resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMo
   switch (mode) {
   case AdditionalMode::ResolveImmediately: {
     set<GetBestNSAnswer> beenthere;
-    int res = doResolve(qname, qtype, addRecords, depth, beenthere, state);
+    boost::optional<EDNSExtendedError> extendedError;
+    int res = doResolve(qname, qtype, addRecords, depth, beenthere, state, extendedError);
     if (res != 0) {
       return;
     }
@@ -563,7 +565,8 @@ void SyncRes::resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMo
   case AdditionalMode::ResolveDeferred: {
     const bool oldCacheOnly = setCacheOnly(true);
     set<GetBestNSAnswer> beenthere;
-    int res = doResolve(qname, qtype, addRecords, depth, beenthere, state);
+    boost::optional<EDNSExtendedError> extendedError;
+    int res = doResolve(qname, qtype, addRecords, depth, beenthere, state, extendedError);
     setCacheOnly(oldCacheOnly);
     if (res == 0 && addRecords.size() > 0) {
       // We're conservative here. We do not add Bogus records in any circumstance, we add Indeterminates only if no
@@ -729,8 +732,10 @@ int SyncRes::beginResolve(const DNSName& qname, const QType qtype, QClass qclass
   }
 
   set<GetBestNSAnswer> beenthere;
-  int res = doResolve(qname, qtype, ret, depth, beenthere, state);
+  boost::optional<EDNSExtendedError> extendedError;
+  int res = doResolve(qname, qtype, ret, depth, beenthere, state, extendedError);
   d_queryValidationState = state;
+  d_extendedError = extendedError;
 
   if (shouldValidate()) {
     if (d_queryValidationState != vState::Indeterminate) {
@@ -1600,7 +1605,7 @@ static unsigned int qmStepLen(unsigned int labels, unsigned int qnamelen, unsign
   return targetlen;
 }
 
-int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state)
+int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, boost::optional<EDNSExtendedError>& extendedError)
 {
 
   string prefix = d_prefix;
@@ -1624,7 +1629,7 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
 
   // In the auth or recursive forward case, it does not make sense to do qname-minimization
   if (!getQNameMinimization() || isRecursiveForwardOrAuth(qname)) {
-    return doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state);
+    return doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state, extendedError);
   }
 
   // The qname minimization algorithm is a simplified version of the one in RFC 7816 (bis).
@@ -1654,7 +1659,7 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
   // For cache peeking, we tell doResolveNoQNameMinimization not to consider the (non-recursive) forward case.
   // Otherwise all queries in a forward domain will be forwarded, while we want to consult the cache.
   // The out-of-band cases for doResolveNoQNameMinimization() should be reconsidered and redone some day.
-  int res = doResolveNoQNameMinimization(qname, qtype, retq, depth, beenthere, state, &fromCache, nullptr, false);
+  int res = doResolveNoQNameMinimization(qname, qtype, retq, depth, beenthere, state, extendedError, &fromCache, nullptr, false);
   setCacheOnly(old);
   if (fromCache) {
     QLOG("Step0 Found in cache");
@@ -1736,7 +1741,7 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
       // Step 3 resolve
       if (child == qname) {
         QLOG("Step3 Going to do final resolve");
-        res = doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state);
+        res = doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state, extendedError);
         QLOG("Step3 Final resolve: " << RCode::to_s(res) << "/" << ret.size());
         return res;
       }
@@ -1760,7 +1765,7 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
       d_followCNAME = false;
       retq.resize(0);
       StopAtDelegation stopAtDelegation = Stop;
-      res = doResolveNoQNameMinimization(child, QType::A, retq, depth, beenthere, state, nullptr, &stopAtDelegation);
+      res = doResolveNoQNameMinimization(child, QType::A, retq, depth, beenthere, state, extendedError, nullptr, &stopAtDelegation);
       d_followCNAME = oldFollowCNAME;
       QLOG("Step4 Resolve A result is " << RCode::to_s(res) << "/" << retq.size() << "/" << stopAtDelegation);
       if (stopAtDelegation == Stopped) {
@@ -1775,7 +1780,7 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
         // We might have hit a depth level check, but we still want to allow some recursion levels in the fallback
         // no-qname-minimization case. This has the effect that a qname minimization fallback case might reach 150% of
         // maxdepth.
-        res = doResolveNoQNameMinimization(qname, qtype, ret, depth / 2, beenthere, state);
+        res = doResolveNoQNameMinimization(qname, qtype, ret, depth / 2, beenthere, state, extendedError);
 
         if (res == RCode::NoError) {
           t_Counters.at(rec::Counter::qnameminfallbacksuccess)++;
@@ -1803,7 +1808,7 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
  * \param stopAtDelegation if non-nullptr and pointed-to value is Stop requests the callee to stop at a delegation, if so pointed-to value is set to Stopped
  * \return DNS RCODE or -1 (Error)
  */
-int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool* fromCache, StopAtDelegation* stopAtDelegation, bool considerforwards)
+int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, boost::optional<EDNSExtendedError>& extendedError, bool* fromCache, StopAtDelegation* stopAtDelegation, bool considerforwards)
 {
   string prefix;
   if (doLog()) {
@@ -1926,7 +1931,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
           return res;
         }
 
-        if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
+        if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state, extendedError)) {
           // we done
           d_wasOutOfBand = wasAuthZone;
           if (fromCache) {
@@ -1996,7 +2001,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
         subdomain = getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); //  pass beenthere to both occasions
       }
 
-      res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, nullptr);
+      res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, extendedError, stopAtDelegation, nullptr);
 
       if (res == -1 && s_save_parent_ns_set) {
         // It did not work out, lets check if we have a saved parent NS set
@@ -2015,8 +2020,8 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
           }
         }
         if (fallBack.size() > 0) {
-          LOG(prefix << qname << ": Failure, but we have a saved parent NS set, trying that one" << endl)
-          res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, &fallBack);
+          LOG(prefix << qname << ": Failure, but we have a saved parent NS set, trying that one" << endl);
+          res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, extendedError, stopAtDelegation, &fallBack);
           if (res == 0) {
             // It did work out
             s_savedParentNSSet.lock()->inc(subdomain);
@@ -2113,9 +2118,10 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth,
     if (ret.empty()) {
       // Neither A nor AAAA in the cache...
       vState newState = vState::Indeterminate;
+      boost::optional<EDNSExtendedError> extendedError;
       cset.clear();
       // Go out to get A's
-      if (s_doIPv4 && doResolve(qname, QType::A, cset, depth + 1, beenthere, newState) == 0) { // this consults cache, OR goes out
+      if (s_doIPv4 && doResolve(qname, QType::A, cset, depth + 1, beenthere, newState, extendedError) == 0) { // this consults cache, OR goes out
         for (auto const& i : cset) {
           if (i.d_type == QType::A) {
             if (auto rec = getRR<ARecordContent>(i)) {
@@ -2129,7 +2135,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth,
           // We only go out immediately to find IPv6 records if we did not find any IPv4 ones.
           newState = vState::Indeterminate;
           cset.clear();
-          if (doResolve(qname, QType::AAAA, cset, depth + 1, beenthere, newState) == 0) { // this consults cache, OR goes out
+          if (doResolve(qname, QType::AAAA, cset, depth + 1, beenthere, newState, extendedError) == 0) { // this consults cache, OR goes out
             for (const auto& i : cset) {
               if (i.d_type == QType::AAAA) {
                 if (auto rec = getRR<AAAARecordContent>(i)) {
@@ -2644,9 +2650,10 @@ bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<
 
       set<GetBestNSAnswer> beenthere;
       vState cnameState = vState::Indeterminate;
+      boost::optional<EDNSExtendedError> extendedError;
       // Be aware that going out on the network might be disabled (cache-only), for example because we are in QM Step0,
       // so you can't trust that a real lookup will have been made.
-      res = doResolve(newTarget, qtype, ret, depth + 1, beenthere, cnameState);
+      res = doResolve(newTarget, qtype, ret, depth + 1, beenthere, cnameState, extendedError);
       LOG(prefix << qname << ": updating validation state for response to " << qname << " from " << state << " with the state from the DNAME/CNAME quest: " << cnameState << endl);
       updateValidationState(state, cnameState);
 
@@ -2775,7 +2782,7 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne,
   }
 }
 
-bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, int& res, vState& state)
+bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, int& res, vState& state, boost::optional<EDNSExtendedError>& extendedError)
 {
   bool giveNegative = false;
 
@@ -2799,6 +2806,9 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
     res = RCode::NXDomain;
     giveNegative = true;
     cachedState = ne.d_validationState;
+    if (s_addExtendedResolutionDNSErrors) {
+      extendedError = EDNSExtendedError{0, "Result synthesized by root-nx-trust"};
+    }
   }
   else if (g_negCache->get(qname, qtype, d_now, ne, false, d_serveStale, d_refresh)) {
     /* If we are looking for a DS, discard NXD if auth == qname
@@ -2822,6 +2832,10 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
         else {
           LOG(prefix << qname << ": Entire name '" << qname << "' is negatively cached via '" << ne.d_auth << "' for another " << sttl << " seconds" << endl);
         }
+        if (s_addExtendedResolutionDNSErrors) {
+          // XXX Do we want this?
+          extendedError = EDNSExtendedError{0, "Result from negative cache"};
+        }
       }
     }
   }
@@ -2844,6 +2858,9 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
           giveNegative = true;
           cachedState = ne.d_validationState;
           LOG(prefix << qname << ": Name '" << negCacheName << "' and below, is negatively cached via '" << ne.d_auth << "' for another " << sttl << " seconds" << endl);
+          if (s_addExtendedResolutionDNSErrors) {
+            extendedError = EDNSExtendedError{0, "Result synthesized by nothing-below-nxdomain (RFC8020)"};
+          }
           break;
         }
       }
@@ -3003,6 +3020,9 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
   if (g_aggressiveNSECCache && !wasForwardedOrAuthZone) {
     if (g_aggressiveNSECCache->getDenial(d_now.tv_sec, qname, qtype, ret, res, d_cacheRemote, d_routingTag, d_doDNSSEC)) {
       state = vState::Secure;
+      if (s_addExtendedResolutionDNSErrors) {
+        extendedError = EDNSExtendedError{0, "Result synthesized from aggressive NSEC cache (RFC8198)"};
+      }
       return true;
     }
   }
@@ -3612,9 +3632,11 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
   std::vector<DNSRecord> dsrecords;
 
   vState state = vState::Indeterminate;
+  boost::optional<EDNSExtendedError> extendedError;
+
   const bool oldCacheOnly = setCacheOnly(false);
   const bool oldQM = setQNameMinimization(true);
-  int rcode = doResolve(zone, QType::DS, dsrecords, depth + 1, beenthere, state);
+  int rcode = doResolve(zone, QType::DS, dsrecords, depth + 1, beenthere, state, extendedError);
   setCacheOnly(oldCacheOnly);
   setQNameMinimization(oldQM);
 
@@ -3869,8 +3891,10 @@ vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, bool& servFai
   LOG(d_prefix << "Retrieving DNSKeys for " << signer << endl);
 
   vState state = vState::Indeterminate;
+  boost::optional<EDNSExtendedError> extendedError;
+
   const bool oldCacheOnly = setCacheOnly(false);
-  int rcode = doResolve(signer, QType::DNSKEY, records, depth + 1, beenthere, state);
+  int rcode = doResolve(signer, QType::DNSKEY, records, depth + 1, beenthere, state, extendedError);
   setCacheOnly(oldCacheOnly);
 
   if (rcode == RCode::ServFail) {
@@ -5126,7 +5150,8 @@ bool SyncRes::tryDoT(const DNSName& qname, const QType qtype, const DNSName& nsN
   // We use the fact that qname equals auth
   bool ok = false;
   try {
-    ok = doResolveAtThisIP("", qname, qtype, lwr, nm, qname, false, false, nsName, address, true, true, truncated, spoofed, true);
+    boost::optional<EDNSExtendedError> extendedError;
+    ok = doResolveAtThisIP("", qname, qtype, lwr, nm, qname, false, false, nsName, address, true, true, truncated, spoofed, extendedError, true);
     ok = ok && lwr.d_rcode == RCode::NoError && lwr.d_records.size() > 0;
   }
   catch (const PDNSException& e) {
@@ -5148,7 +5173,7 @@ bool SyncRes::tryDoT(const DNSName& qname, const QType qtype, const DNSName& nsN
   return ok;
 }
 
-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 bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, bool dontThrottle)
+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 bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle)
 {
   bool chained = false;
   LWResult::Result resolveret = LWResult::Result::Success;
@@ -5157,6 +5182,9 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
   checkMaxQperQ(qname);
 
   if (s_maxtotusec && d_totUsec > s_maxtotusec) {
+    if (s_addExtendedResolutionDNSErrors) {
+      extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::NoReachableAuthority), "Timeout waiting for answer(s)"};
+    }
     throw ImmediateServFailException("Too much time waiting for " + qname.toLogString() + "|" + qtype.toString() + ", timeouts: " + std::to_string(d_timeouts) + ", throttles: " + std::to_string(d_throttledqueries) + ", queries: " + std::to_string(d_outqueries) + ", " + std::to_string(d_totUsec / 1000) + "msec");
   }
 
@@ -5379,7 +5407,8 @@ void SyncRes::handleNewTarget(const std::string& prefix, const DNSName& qname, c
 
   set<GetBestNSAnswer> beenthere;
   vState cnameState = vState::Indeterminate;
-  rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere, cnameState);
+  boost::optional<EDNSExtendedError> extendedError;
+  rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere, cnameState, extendedError);
   LOG(prefix << qname << ": updating validation state for response to " << qname << " from " << state << " with the state from the CNAME quest: " << cnameState << endl);
   updateValidationState(state, cnameState);
 }
@@ -5526,7 +5555,7 @@ bool SyncRes::doDoTtoAuth(const DNSName& ns) const
  */
 int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, const DNSName& qname, const QType qtype,
                          vector<DNSRecord>& ret,
-                         unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, StopAtDelegation* stopAtDelegation,
+                         unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, boost::optional<EDNSExtendedError>& extendedError, StopAtDelegation* stopAtDelegation,
                          map<DNSName, vector<ComboAddress>>* fallBack)
 {
   auto luaconfsLocal = g_luaconfs.getLocal();
@@ -5571,11 +5600,14 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
       }
       if (tns == rnameservers.cend()) {
         LOG(prefix << qname << ": Failed to resolve via any of the " << (unsigned int)rnameservers.size() << " offered NS at level '" << auth << "'" << endl);
+        if (s_addExtendedResolutionDNSErrors) {
+          extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::NoReachableAuthority), "delegation " + auth.toLogString()};
+        }
         if (!auth.isRoot() && flawedNSSet) {
           LOG(prefix << qname << ": Ageing nameservers for level '" << auth << "', next query might succeed" << endl);
-
-          if (g_recCache->doAgeCache(d_now.tv_sec, auth, QType::NS, 10))
+          if (g_recCache->doAgeCache(d_now.tv_sec, auth, QType::NS, 10)) {
             t_Counters.at(rec::Counter::nsSetInvalidations)++;
+          }
         }
         return -1;
       }
@@ -5691,12 +5723,12 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
           }
           if (!forceTCP) {
             gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery, wasForwarded,
-                                          tns->first, *remoteIP, false, false, truncated, spoofed);
+                                          tns->first, *remoteIP, false, false, truncated, spoofed, extendedError);
           }
           if (forceTCP || (spoofed || (gotAnswer && truncated))) {
             /* retry, over TCP this time */
             gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery, wasForwarded,
-                                          tns->first, *remoteIP, true, doDoT, truncated, spoofed);
+                                          tns->first, *remoteIP, true, doDoT, truncated, spoofed, extendedError);
           }
 
           if (!gotAnswer) {
index 097e1db7d262b76c4e4dd43ba939768b6ff0a324..43bd45e381bf1d7e2086d2cc34464bd557a0fb40 100644 (file)
@@ -54,6 +54,7 @@
 #include "rec-eventtrace.hh"
 #include "logr.hh"
 #include "rec-tcounters.hh"
+#include "ednsextendederror.hh"
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -520,6 +521,7 @@ public:
   static const int event_trace_to_log = 2;
   static int s_event_trace_enabled;
   static bool s_save_parent_ns_set;
+  static bool s_addExtendedResolutionDNSErrors;
 
   std::unordered_map<std::string, bool> d_discardedPolicies;
   DNSFilterEngine::Policy d_appliedPolicy;
@@ -528,6 +530,7 @@ public:
   ComboAddress d_fromAuthIP;
   RecEventTrace d_eventTrace;
   std::shared_ptr<Logr::Logger> d_slog = g_slog->withName("syncres");
+  boost::optional<EDNSExtendedError> d_extendedError;
 
   unsigned int d_authzonequeries;
   unsigned int d_outqueries;
@@ -575,20 +578,20 @@ private:
 
   bool doDoTtoAuth(const DNSName& ns) const;
   int doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, const DNSName& qname, QType qtype, vector<DNSRecord>& ret,
-                  unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, StopAtDelegation* stopAtDelegation,
+                  unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, boost::optional<EDNSExtendedError>& extendedError, StopAtDelegation* stopAtDelegation,
                   std::map<DNSName, std::vector<ComboAddress>>* fallback);
-  bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, bool dontThrottle = false);
+  bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle = false);
   bool processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet& nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state, const ComboAddress& remoteIP);
 
-  int doResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state);
-  int doResolveNoQNameMinimization(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL, bool considerforwards = true);
+  int doResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, boost::optional<EDNSExtendedError>& extendedError);
+  int doResolveNoQNameMinimization(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, boost::optional<EDNSExtendedError>& extendedError, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL, bool considerforwards = true);
   bool doOOBResolve(const AuthDomain& domain, const DNSName& qname, QType qtype, vector<DNSRecord>& ret, int& res);
   bool doOOBResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, int& res);
   bool isRecursiveForwardOrAuth(const DNSName& qname) const;
   bool isForwardOrAuth(const DNSName& qname) const;
   domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
   bool doCNAMECacheCheck(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, int& res, vState& state, bool wasAuthZone, bool wasForwardRecurse);
-  bool doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, int& res, vState& state);
+  bool doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, int& res, vState& state,           boost::optional<EDNSExtendedError>& extendedError);
   void getBestNSFromCache(const DNSName& qname, QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain = boost::none);
   DNSName getBestNSNamesFromCache(const DNSName& qname, QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere);