]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Qname minimizaton.
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 16 Apr 2019 13:25:46 +0000 (15:25 +0200)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 18 Jun 2019 08:12:38 +0000 (10:12 +0200)
pdns/pdns_recursor.cc
pdns/recursordist/docs/settings.rst
pdns/recursordist/test-syncres_cc.cc
pdns/recursordist/test-syncres_cc.hh
pdns/recursordist/test-syncres_cc3.cc
pdns/syncres.cc
pdns/syncres.hh

index 491ae26bd95d34a0c4e3a5c62b2130bd90c0e4cc..6710556f0425f144dc630709f190cd621ea172f2 100644 (file)
@@ -3846,6 +3846,8 @@ static int serviceMain(int argc, char*argv[])
   SyncRes::s_ecsipv6cachelimit = ::arg().asNum("ecs-ipv6-cache-bits");
   SyncRes::s_ecscachelimitttl = ::arg().asNum("ecs-cache-limit-ttl");
 
+  SyncRes::s_qnameminimization = ::arg().mustDo("qname-minimization");
+
   if (!::arg().isEmpty("ecs-scope-zero-address")) {
     ComboAddress scopeZero(::arg()["ecs-scope-zero-address"]);
     SyncRes::setECSScopeZeroAddress(Netmask(scopeZero, scopeZero.isIPv4() ? 32 : 128));
@@ -4544,6 +4546,7 @@ int main(int argc, char **argv)
     ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto";
     ::arg().set("public-suffix-list-file", "Path to the Public Suffix List file, if any")="";
     ::arg().set("distribution-load-factor", "The load factor used when PowerDNS is distributing queries to worker threads")="0.0";
+    ::arg().setSwitch("qname-minimization", "Use Query Name Minimization")="no";
 #ifdef NOD_ENABLED
     ::arg().set("new-domain-tracking", "Track newly observed domains (i.e. never seen before).")="no";
     ::arg().set("new-domain-log", "Log newly observed domains.")="yes";
index a867200c42503031e563404f35141b8dcb27045a..45cc199e35987aa8325fff1901ce361317223eab 100644 (file)
@@ -1164,6 +1164,17 @@ Whether to compute the latency of responses in protobuf messages using the times
 
 Path to the Public Suffix List file, if any. If set, PowerDNS will try to load the Public Suffix List from this file instead of using the built-in list. The PSL is used to group the queries by relevant domain names when displaying the top queries.
 
+.. _setting-qname-minimization:
+
+``qname-minimization``
+----------------------
+.. versionadded:: 4.3.0
+-  Boolean
+-  Default: no
+
+Enable Query Name Minimization. This is a experimental feature, implementing a relaxed form of Query Name Mimimization as
+described in RFC 7816.
+
 .. _setting-query-local-address:
 
 ``query-local-address``
index aabfe33fe0c53e24a579c7998d937a3ce154103e..b95b56e0f5f059f6030210af3e87f60deb577ed0 100644 (file)
@@ -165,7 +165,7 @@ void initSR(bool debug)
   ::arg().set("version-string", "string reported on version.pdns or version.bind")="PowerDNS Unit Tests";
   ::arg().set("rng")="auto";
   ::arg().set("entropy-source")="/dev/urandom";
-  ::arg().setSwitch("qname-minimisation", "Use Query Name Minimisation") = "no";
+  ::arg().setSwitch("qname-minimization", "Use Query Name Minimization") = "no";
 }
 
 void initSR(std::unique_ptr<SyncRes>& sr, bool dnssec, bool debug, time_t fakeNow)
@@ -199,11 +199,6 @@ void setDNSSECValidation(std::unique_ptr<SyncRes>& sr, const DNSSECMode& mode)
   g_dnssecmode = mode;
 }
 
-void setDoQNameMinimisation(void)
-{
-  ::arg().setSwitch("qname-minimisation", "") = "yes";
-}
-
 void setLWResult(LWResult* res, int rcode, bool aa, bool tc, bool edns, bool validpacket)
 {
   res->d_rcode = rcode;
@@ -461,7 +456,7 @@ int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth
   return 0;
 }
 
-int basicRecordsForQnameMinimisation(LWResult* res, const DNSName& domain, int type) {
+int basicRecordsForQnameMinimization(LWResult* res, const DNSName& domain, int type) {
   if (domain == DNSName(".") && type == QType::A) {
     setLWResult(res, 0, true);
     addRecordToLW(res, DNSName("."), QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2019042400 1800 900 604800 86400", DNSResourceRecord::AUTHORITY);
index d568a43c5079c9aa20bccf3bf7f2b3d45b838dcc..93d7e6f433a8760cd134cba8864b69f6cd68f5e7 100644 (file)
@@ -41,8 +41,6 @@ void initSR(std::unique_ptr<SyncRes>& sr, bool dnssec=false, bool debug=false, t
 
 void setDNSSECValidation(std::unique_ptr<SyncRes>& sr, const DNSSECMode& mode);
 
-void setDoQNameMinimisation(void);
-
 void setLWResult(LWResult* res, int rcode, bool aa=false, bool tc=false, bool edns=false, bool validpacket=true);
 
 void addRecordToLW(LWResult* res, const DNSName& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, uint32_t ttl=60);
@@ -77,5 +75,5 @@ void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest,
 
 int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut=true);
 
-int basicRecordsForQnameMinimisation(LWResult* res, const DNSName& domain, int type);
+int basicRecordsForQnameMinimization(LWResult* res, const DNSName& domain, int type);
 
index 361016ce39da0eea117e42df4fd3dd4d0af513bb..9a5ea155e5eede11eda63be8caf1e3df4e6d9a26 100644 (file)
@@ -112,7 +112,7 @@ static void test_no_data_f(bool qmin) {
   std::unique_ptr<SyncRes> sr;
   initSR(sr);
   if (qmin)
-    setDoQNameMinimisation();
+    sr->setQNameMinimization();
 
   primeHints();
 
@@ -138,8 +138,6 @@ BOOST_AUTO_TEST_CASE(test_no_data) {
 }
 
 BOOST_AUTO_TEST_CASE(test_no_data_qmin) {
-  // DISABLED UNTIL QNAME MINIMIZATION IS THERE
-  return;
   test_no_data_f(true);
 }
 
index 1c5daf550f06a550c9b260b7903f6d67eecf7789..5269e272111027f3ca982fc3e4fc3ed0f701a33f 100644 (file)
@@ -87,6 +87,7 @@ bool SyncRes::s_doIPv6;
 bool SyncRes::s_nopacketcache;
 bool SyncRes::s_rootNXTrust;
 bool SyncRes::s_noEDNS;
+bool SyncRes::s_qnameminimization;
 
 #define LOG(x) if(d_lm == Log) { g_log <<Logger::Warning << x; } else if(d_lm == Store) { d_trace << x; }
 
@@ -121,9 +122,9 @@ static void accountAuthLatency(int usec, int family)
 
 SyncRes::SyncRes(const struct timeval& now) :  d_authzonequeries(0), d_outqueries(0), d_tcpoutqueries(0), d_throttledqueries(0), d_timeouts(0), d_unreachables(0),
                                               d_totUsec(0), d_now(now),
-                                              d_cacheonly(false), d_doDNSSEC(false), d_doEDNS0(false), d_lm(s_lm)
-                                                 
-{ 
+                                              d_cacheonly(false), d_doDNSSEC(false), d_doEDNS0(false), d_qNameMinimization(s_qnameminimization), d_lm(s_lm)
+
+{
 }
 
 /** everything begins here - this is the entry point just after receiving a packet */
@@ -540,6 +541,119 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, con
   return ret;
 }
 
+#define QLOG(x) LOG(prefix << " child=" <<  child << ": " << x << endl)
+
+int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state) {
+
+  if (!getQNameMinimization()) {
+    return doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state);
+  }
+
+  // The qname minimization algorithm is a simplified version of the one in RFC 7816 (bis).
+  // It could be simplified because the cache maintenance (both positive and negative)
+  // is already done by doResolveNoQNameMinimization().
+  //
+  // Sketch of algorithm:
+  // Check cache
+  //  If result found: done
+  //  Otherwise determine closes ancestor from cache data
+  //    Repeat querying A, adding more labels of the original qname
+  //    If we get a delegation continue at ancestor determination
+  //    Until we have the full name.
+  //
+  // The algorithm starts with adding a single label per iteration, and
+  // moves to three labels per iteration after three iterations.
+
+  DNSName child;
+  string prefix = d_prefix;
+  prefix.append(depth, ' ');
+  prefix.append(string("QM ") + qname.toString() + "|" + qtype.getName());
+
+  QLOG("doResolve");
+
+  // Look in cache only
+  vector<DNSRecord> retq;
+  bool old = setCacheOnly(true);
+  bool fromCache = false;
+  int res = doResolveNoQNameMinimization(qname, qtype, retq, depth + 1, beenthere, state, &fromCache);
+  setCacheOnly(old);
+  if (fromCache) {
+    QLOG("Step0 Found in cache");
+    ret.insert(ret.end(), retq.begin(), retq.end());
+    return res;
+  }
+  QLOG("Step0 Not cached");
+
+  const unsigned int qnamelen = qname.countLabels();
+
+  for (unsigned int i = 0; i <= qnamelen; ) {
+
+    // Step 1
+    vector<DNSRecord> bestns;
+    // the two retries allow getBestNSFromCache&co to reprime the root
+    // hints, in case they ever go missing
+    for (int tries = 0; tries < 2 && bestns.empty(); ++tries) {
+      bool flawedNSSet = false;
+      set<GetBestNSAnswer> beenthereIgnored;
+      getBestNSFromCache(qname, qtype, bestns, &flawedNSSet, depth + 1, beenthereIgnored);
+    }
+    DNSName ancestor;
+    if (bestns.size() > 0) {
+      ancestor = bestns[0].d_name;
+      QLOG("Step1 Ancestor from cache is " << ancestor.toString());
+    } else {
+      QLOG("Step1 No ancestor found return ServFail");
+      return RCode::ServFail;
+    }
+
+    child = ancestor;
+
+    unsigned int targetlen = std::min(child.countLabels() + (i > 3 ? 3 : 1), qnamelen);
+
+    for (; i <= qnamelen; i++) {
+      // Step 2
+      while (child.countLabels() < targetlen) {
+        child.prependRawLabel(qname.getRawLabel(qnamelen - child.countLabels() - 1));
+      }
+      targetlen += i > 3 ? 3 : 1;
+      targetlen = std::min(targetlen, qnamelen);
+
+      QLOG("Step2 New child");
+
+      // Step 3 resolve
+      if (child == qname) {
+        QLOG("Step3 Going to do final resolve");
+        res = doResolveNoQNameMinimization(qname, qtype, ret, depth + 1, beenthere, state);
+        QLOG("Step3 Final resolve: " << RCode::to_s(res) << "/" << ret.size() << endl);
+        return res;
+      }
+
+      // Step 6
+      QLOG("Step4 Resolve A for child");
+      retq.resize(0);
+      StopAtDelegation stopAtDelegation = Stop;
+      res = doResolveNoQNameMinimization(child, QType::A, retq, depth + 1, beenthere, state, NULL, &stopAtDelegation);
+      QLOG("Step4 Resolve A result is " << RCode::to_s(res) << "/" << retq.size() << "/" << stopAtDelegation);
+      if (stopAtDelegation == Stopped) {
+        QLOG("Delegation seen, continue at step 1");
+        break;
+      }
+      if (res != RCode::NoError) {
+        // Case 5: unexpected answer
+        QLOG("Step5: other rcode, last effort final resolve");
+        setQNameMinimization(false);
+        res = doResolveNoQNameMinimization(qname, qtype, ret, depth + 1, beenthere, state);
+        QLOG("Step5 End resolve: " << RCode::to_s(res) << "/" << ret.size() << endl);
+        return res;
+      }
+    }
+  }
+
+  // Should not be reached
+  QLOG("Max iterations reached, return ServFail");
+  return RCode::ServFail;
+}
+
 /*! This function will check the cache and go out to the internet if the answer is not in cache
  *
  * \param qname The name we need an answer for
@@ -549,7 +663,7 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, con
  * \param beenthere
  * \return DNS RCODE or -1 (Error) or -2 (RPZ hit)
  */
-int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state)
+int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool *fromCache, StopAtDelegation *stopAtDelegation)
 {
   string prefix;
   if(doLog()) {
@@ -577,6 +691,8 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
         if(iter->second.isAuth()) {
           ret.clear();
           d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, res);
+          if (fromCache)
+            *fromCache = d_wasOutOfBand;
           return res;
         }
         else {
@@ -590,7 +706,9 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
 
           d_totUsec += lwr.d_usec;
           accountAuthLatency(lwr.d_usec, remoteIP.sin4.sin_family);
-
+          if (fromCache)
+            *fromCache = true;
+          
           // filter out the good stuff from lwr.result()
           if (res == 1) {
             for(const auto& rec : lwr.d_records) {
@@ -630,10 +748,13 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
     if(doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
       // we done
       d_wasOutOfBand = wasAuthZone;
+      if (fromCache)
+        *fromCache = true;
       return res;
     }
   }
 
+  if(d_cacheonly)
   if(d_cacheonly)
     return 0;
 
@@ -658,7 +779,7 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
 
   LOG(prefix<<qname<<": initial validation status for "<<qname<<" is "<<vStates[state]<<endl);
 
-  if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state)))
+  if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation)))
     return 0;
 
   LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
@@ -695,12 +816,11 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
   typedef vector<ComboAddress> ret_t;
   ret_t ret;
 
-  bool oldCacheOnly = d_cacheonly;
+  bool oldCacheOnly = setCacheOnly(cacheOnly);
   bool oldRequireAuthData = d_requireAuthData;
   bool oldValidationRequested = d_DNSSECValidationRequested;
   d_requireAuthData = false;
   d_DNSSECValidationRequested = false;
-  d_cacheonly = cacheOnly;
 
   vState newState = Indeterminate;
   res_t resv4;
@@ -745,7 +865,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
 
   d_requireAuthData = oldRequireAuthData;
   d_DNSSECValidationRequested = oldValidationRequested;
-  d_cacheonly = oldCacheOnly;
+  setCacheOnly(oldCacheOnly);
 
   /* we need to remove from the nsSpeeds collection the existing IPs
      for this nameserver that are no longer in the set, even if there
@@ -3140,7 +3260,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
  */
 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)
+                         unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state, StopAtDelegation* stopAtDelegation)
 {
   auto luaconfsLocal = g_luaconfs.getLocal();
   string prefix;
@@ -3207,6 +3327,10 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
           return rcode;
         }
         if (gotNewServers) {
+          if (stopAtDelegation && *stopAtDelegation == Stop) {
+            *stopAtDelegation = Stopped;
+            return rcode;
+          }
           break;
         }
       }
@@ -3272,6 +3396,10 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
             return rcode;
           }
           if (gotNewServers) {
+            if (stopAtDelegation && *stopAtDelegation == Stop) {
+              *stopAtDelegation = Stopped;
+              return rcode;
+            }
             break;
           }
           /* was lame */
index 248e1bc0837e68916f61b321264a98337b546c4f..d0edf346d852efb8ae719e04d12e5a7515f25126 100644 (file)
@@ -598,9 +598,16 @@ public:
     return d_lm != LogNone;
   }
 
-  void setCacheOnly(bool state=true)
+  bool setCacheOnly(bool state = true)
   {
-    d_cacheonly=state;
+    bool old = d_cacheonly;
+    d_cacheonly = state;
+    return old;
+  }
+
+  void setQNameMinimization(bool state=true)
+  {
+    d_qNameMinimization=state;
   }
 
   void setDoEDNS0(bool state=true)
@@ -643,6 +650,11 @@ public:
     return d_trace.str();
   }
 
+  bool getQNameMinimization() const
+  {
+    return d_qNameMinimization;
+  }
+
   void setLuaEngine(shared_ptr<RecursorLua4> pdl)
   {
     d_pdl = pdl;
@@ -740,6 +752,7 @@ public:
   static bool s_noEDNS;
   static bool s_rootNXTrust;
   static bool s_nopacketcache;
+  static bool s_qnameminimization;
 
   std::unordered_map<std::string,bool> d_discardedPolicies;
   DNSFilterEngine::Policy d_appliedPolicy;
@@ -777,13 +790,15 @@ private:
   };
 
   typedef std::map<DNSName,vState> zonesStates_t;
+  enum StopAtDelegation { DontStop, Stop, Stopped };
 
   int doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret,
-                  unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state);
+                  unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state, StopAtDelegation* stopAtDelegation);
   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 DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool* truncated);
   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);
 
   int doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state);
+  int doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL);
   bool doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res);
   bool doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res);
   domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
@@ -862,6 +877,7 @@ private:
   bool d_wantsRPZ{true};
   bool d_wasOutOfBand{false};
   bool d_wasVariable{false};
+  bool d_qNameMinimization{false};
 
   LogMode d_lm;
 };