]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
implement four dnssec modes: off (3.x behaviour), process (ask for DNSSEC, give it...
authorbert hubert <bert.hubert@netherlabs.nl>
Mon, 14 Dec 2015 14:49:37 +0000 (15:49 +0100)
committerbert hubert <bert.hubert@netherlabs.nl>
Mon, 14 Dec 2015 14:49:37 +0000 (15:49 +0100)
Also, improve EDNS probing, plus make recursor packet cache DNSSEC aware.

pdns/lwres.cc
pdns/pdns_recursor.cc
pdns/rec-lua-conf.cc
pdns/recpacketcache.cc
pdns/recpacketcache.hh
pdns/syncres.cc
pdns/syncres.hh
pdns/validate-recursor.cc
pdns/validate-recursor.hh

index 6bd17220adb24b6062f35872b9c5930c03de96a4..2d136bfa23d0cc10e66f58196b40dbf963d5f3d2 100644 (file)
@@ -47,6 +47,7 @@
 #include "dns_random.hh"
 #include <boost/scoped_array.hpp>
 #include <boost/algorithm/string.hpp>
+#include "validate-recursor.hh"
 #include "ednssubnet.hh"
 
 //! returns -2 for OS limits error, -1 for permanent error that has to do with remote **transport**, 0 for timeout, 1 for success
@@ -77,7 +78,7 @@ int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool d
       srcmask=boost::optional<Netmask>(); // this is also our return value
     }
 
-    pw.addOpt(g_outgoingEDNSBufsize, 0, EDNSOpts::DNSSECOK, opts); 
+    pw.addOpt(g_outgoingEDNSBufsize, 0, g_dnssecmode == DNSSECMode::Off ? 0 : EDNSOpts::DNSSECOK, opts); 
     pw.commit();
   }
   lwr->d_rcode = 0;
index 7dab9f0a86a4f474e1d79ae2303ec1ac53a094e2..a6cf25a13f92f50d6aec8a0cf3b658d4ece69704 100644 (file)
@@ -810,7 +810,7 @@ void startDoResolve(void *p)
       pw.getHeader()->rcode=res;
 
       if(haveEDNS) {
-       if(edo.d_Z & EDNSOpts::DNSSECOK) {
+       if(g_dnssecmode != DNSSECMode::Off && ((edo.d_Z & EDNSOpts::DNSSECOK) || g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode==DNSSECMode::ValidateForLog)) {
          auto state=validateRecords(ret);
          if(state == Secure) {
            pw.getHeader()->ad=1;
@@ -819,8 +819,13 @@ void startDoResolve(void *p)
            pw.getHeader()->ad=0;
          }
          else if(state == Bogus && !pw.getHeader()->cd) {
-           pw.getHeader()->rcode=RCode::ServFail;
-           goto sendit;
+            if(g_dnssecmode == DNSSECMode::ValidateAll || (edo.d_Z & EDNSOpts::DNSSECOK)) {
+              pw.getHeader()->rcode=RCode::ServFail;
+              goto sendit;
+            }
+            else {
+              L<<Logger::Warning<<"Failed to validate "<<dc->d_mdp.d_qname<<" for "<<dc->d_remote.toStringWithPort()<<endl;
+            }
          }
        }
       }
@@ -873,6 +878,7 @@ void startDoResolve(void *p)
       sendmsg(dc->d_socket, &msgh, 0);
       if(!SyncRes::s_nopacketcache && !variableAnswer && !sr.wasVariable() ) {
         t_packetCache->insertResponsePacket(string((const char*)&*packet.begin(), packet.size()),
+                                            (edo.d_Z & EDNSOpts::DNSSECOK), // ponder filtering on dnssecmode here
                                             g_now.tv_sec,
                                             min(minTTL,
                                                 (pw.getHeader()->rcode == RCode::ServFail) ? SyncRes::s_packetcacheservfailttl : SyncRes::s_packetcachettl
@@ -1142,8 +1148,16 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
     g_mtracer->clearAllocators();
     */
 #endif
-
-    if(!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(question, g_now.tv_sec, &response, &age)) {
+    bool needsDNSSEC=false;
+    const struct dnsheader* dh = (struct dnsheader*)question.c_str();
+    if(dh->arcount) {
+      unsigned int consumed=0;
+      DNSName qname(question.c_str(), question.length(), sizeof(dnsheader), false, 0, 0, &consumed);
+      if(question.size() > (consumed+12+11) && ((question[consumed+12+11]&0x80)==0x80))
+        needsDNSSEC=true;
+    }
+    
+    if(!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(question, needsDNSSEC, g_now.tv_sec, &response, &age)) {
       if(!g_quiet)
         L<<Logger::Notice<<t_id<< " question answered from packet cache from "<<fromaddr.toString()<<endl;
       // t_queryring->push_back("packetcached");
@@ -2213,6 +2227,19 @@ int serviceMain(int argc, char*argv[])
   setupDelegationOnly();
   g_outgoingEDNSBufsize=::arg().asNum("edns-outgoing-bufsize");
 
+  if(::arg()["dnssec"]=="off") 
+    g_dnssecmode=DNSSECMode::Off;
+  else if(::arg()["dnssec"]=="process") 
+    g_dnssecmode=DNSSECMode::Process;
+  else if(::arg()["dnssec"]=="validate")
+    g_dnssecmode=DNSSECMode::ValidateAll; 
+  else if(::arg()["dnssec"]=="log-fail")
+    g_dnssecmode=DNSSECMode::ValidateForLog;
+  else {
+    L<<Logger::Error<<"Unknown DNSSEC mode "<<::arg()["dnssec"]<<endl;
+    exit(1);
+  }
+
   if(::arg()["trace"]=="fail") {
     SyncRes::setDefaultLogMode(SyncRes::Store);
   }
@@ -2519,6 +2546,7 @@ int main(int argc, char **argv)
     ::arg().set("local-address","IP addresses to listen on, separated by spaces or commas. Also accepts ports.")="127.0.0.1";
     ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options")="no";
     ::arg().set("trace","if we should output heaps of logging. set to 'fail' to only log failing domains")="off";
+    ::arg().set("dnssec", "DNSSEC mode: off/process (default)/log-fail/validate")="process";
     ::arg().set("daemon","Operate as a daemon")="yes";
     ::arg().setSwitch("write-pid","Write a PID file")="yes";
     ::arg().set("loglevel","Amount of logging. Higher is more. Do not set below 3")="4";
index 7831008ec61da6c47076a265bf1d467f9df08bf0..f4b23fb0b1237480563e34eafd18dbe96d492d80 100644 (file)
@@ -13,7 +13,7 @@
 #include "syncres.hh"
 #include "rpzloader.hh"
 
-GlobalStateHolder<LuaConfigItems> g_luaconfs;
+GlobalStateHolder<LuaConfigItems> g_luaconfs; 
 
 /* SO HOW DOES THIS WORK! AND PLEASE PAY ATTENTION!
    This function can be called at any time. It is expected to overwrite all the contents
index 470bb9591d3e187ad3b798102f6650605b5a19a3..8926117f7f4c0dbdb17ba68664e4046daae4ca99 100644 (file)
@@ -22,11 +22,11 @@ int RecursorPacketCache::doWipePacketCache(const DNSName& name, uint16_t qtype,
   pw.getHeader()->rd=1;
   Entry e;
   e.d_packet.assign((const char*)&*packet.begin(), packet.size());
-
+  e.d_wantsDNSSEC=false;
   // so the idea is, we search for a packet with qtype=0, which is ahead of anything with that name
 
   int count=0;
-  for(packetCache_t::iterator iter = d_packetCache.lower_bound(e); iter != d_packetCache.end(); ) {
+  for(auto iter = d_packetCache.lower_bound(e); iter != d_packetCache.end(); ) {
     const struct dnsheader* packet = reinterpret_cast<const struct dnsheader*>((*iter).d_packet.c_str());
     if(packet->qdcount==0)
       break;
@@ -55,11 +55,12 @@ int RecursorPacketCache::doWipePacketCache(const DNSName& name, uint16_t qtype,
   return count;
 }
 
-bool RecursorPacketCache::getResponsePacket(const std::string& queryPacket, time_t now, 
+bool RecursorPacketCache::getResponsePacket(const std::string& queryPacket, bool wantsDNSSEC, time_t now, 
   std::string* responsePacket, uint32_t* age)
 {
   struct Entry e;
   e.d_packet=queryPacket;
+  e.d_wantsDNSSEC = wantsDNSSEC;
   
   packetCache_t::const_iterator iter = d_packetCache.find(e);
   
@@ -96,10 +97,11 @@ bool RecursorPacketCache::getResponsePacket(const std::string& queryPacket, time
   return false;
 }
 
-void RecursorPacketCache::insertResponsePacket(const std::string& responsePacket, time_t now, uint32_t ttl)
+void RecursorPacketCache::insertResponsePacket(const std::string& responsePacket, bool wantsDNSSEC, time_t now, uint32_t ttl)
 {
   struct Entry e;
   e.d_packet = responsePacket;
+  e.d_wantsDNSSEC = wantsDNSSEC;
   e.d_ttd = now+ttl;
   e.d_creation = now;
   packetCache_t::iterator iter = d_packetCache.find(e);
index aa17ff0311662e56964734ebb5562b57b01a417a..cc0cb96f879b0e76e17b7528dfb621ee8e8e8fff 100644 (file)
@@ -23,8 +23,8 @@ class RecursorPacketCache
 {
 public:
   RecursorPacketCache();
-  bool getResponsePacket(const std::string& queryPacket, time_t now, std::string* responsePacket, uint32_t* age);
-  void insertResponsePacket(const std::string& responsePacket, time_t now, uint32_t ttd);
+  bool getResponsePacket(const std::string& queryPacket, bool wantsDNSSEC, time_t now, std::string* responsePacket, uint32_t* age);
+  void insertResponsePacket(const std::string& responsePacket, bool wantsDNSSEC, time_t now, uint32_t ttd);
   void doPruneTo(unsigned int maxSize=250000);
   int doWipePacketCache(const DNSName& name, uint16_t qtype=0xffff, bool subtree=false);
   
@@ -40,7 +40,7 @@ private:
     mutable uint32_t d_ttd;
     mutable uint32_t d_creation;
     mutable std::string d_packet; // "I know what I am doing"
-
+    bool d_wantsDNSSEC;
     inline bool operator<(const struct Entry& rhs) const;
     
     uint32_t getTTD() const
@@ -66,12 +66,12 @@ inline bool RecursorPacketCache::Entry::operator<(const struct RecursorPacketCac
   const struct dnsheader* 
     dh=(const struct dnsheader*) d_packet.c_str(), 
     *rhsdh=(const struct dnsheader*)rhs.d_packet.c_str();
-  if(std::tie(dh->opcode, dh->rd, dh->qdcount) < 
-     std::tie(rhsdh->opcode, rhsdh->rd, rhsdh->qdcount))
+  if(std::tie(d_wantsDNSSEC, dh->opcode, dh->rd, dh->qdcount) < 
+     std::tie(rhs.d_wantsDNSSEC, rhsdh->opcode, rhsdh->rd, rhsdh->qdcount))
     return true;
 
-  if(std::tie(dh->opcode, dh->rd, dh->qdcount) >
-     std::tie(rhsdh->opcode, rhsdh->rd, rhsdh->qdcount))
+  if(std::tie(d_wantsDNSSEC, dh->opcode, dh->rd, dh->qdcount) >
+     std::tie(rhs.d_wantsDNSSEC, rhsdh->opcode, rhsdh->rd, rhsdh->qdcount))
     return false;
 
   return dnspacketLessThan(d_packet, rhs.d_packet);
index ad34208dec9cf25eec9a201cdbe03c59c751f1ab..a974da6c76e86a800d5b7ded0fdfc0032604d95d 100644 (file)
@@ -291,7 +291,7 @@ void SyncRes::doEDNSDumpAndClose(int fd)
   fclose(fp);
 }
 
-int SyncRes::asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res)
+int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res)
 {
   /* what is your QUEST?
      the goal is to get as many remotes as possible on the highest level of EDNS support
@@ -331,7 +331,7 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain,
   for(int tries = 0; tries < 3; ++tries) {
     //    cerr<<"Remote '"<<ip.toString()<<"' currently in mode "<<mode<<endl;
     
-    if(mode==EDNSStatus::UNKNOWN || mode==EDNSStatus::EDNSOK || mode==EDNSStatus::EDNSIGNORANT)
+    if(ednsMANDATORY || mode==EDNSStatus::UNKNOWN || mode==EDNSStatus::EDNSOK || mode==EDNSStatus::EDNSIGNORANT)
       EDNSLevel = 1;
     else if(mode==EDNSStatus::NOEDNS) {
       g_stats.noEdnsOutQueries++;
@@ -341,7 +341,13 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain,
     ret=asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, res);
 
     if(ret == 0 || ret < 0) {
-      //      cerr<< (ret < 0 ? "Transport error" : "Timeout")<<" for query to "<<ip.toString()<<" for '"<<domain.toString()<<"' (ret="<<ret<<"), no change in mode"<<endl;
+      cerr<< (ret < 0 ? "Transport error" : "Timeout")<<" for query to "<<ip.toString()<<" for '"<<domain.toString()<<"' (ret = "<<ret<<", mode = "<<(int)mode<<")"<<endl;
+      // timeout = downgrade to EDNS
+      if(ret==0 && mode != EDNSStatus::NOEDNS) {
+        //     cerr<<"\tDowngrading to NOEDNS"<<endl;
+       mode = EDNSStatus::NOEDNS;
+       continue;
+      }
       return ret;
     }
     else if(mode==EDNSStatus::UNKNOWN || mode==EDNSStatus::EDNSOK || mode == EDNSStatus::EDNSIGNORANT ) {
@@ -362,7 +368,7 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain,
       }
       
     }
-    if(oldmode != mode)
+    if(oldmode != mode || !ednsstatus->modeSetAt)
       ednsstatus->modeSetAt=d_now.tv_sec;
     //    cerr<<"Result: ret="<<ret<<", EDNS-level: "<<EDNSLevel<<", haveEDNS: "<<res->d_haveEDNS<<", new mode: "<<mode<<endl;  
     return ret;
@@ -399,7 +405,7 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
           LOG(prefix<<qname.toString()<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname.toString()<<"'"<<endl);
 
          boost::optional<Netmask> nm;
-          res=asyncresolveWrapper(remoteIP, qname, qtype.getCode(), false, false, &d_now, nm, &lwr);
+          res=asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, qtype.getCode(), false, false, &d_now, nm, &lwr);
           // filter out the good stuff from lwr.result()
 
          for(const auto& rec : lwr.d_records) {
@@ -1033,7 +1039,7 @@ int SyncRes::doResolveAt(set<DNSName> nameservers, DNSName auth, bool flawedNSSe
            }
            else {
              ednsmask=getEDNSSubnetMask(d_requestor, qname, *remoteIP);
-             resolveret=asyncresolveWrapper(*remoteIP, qname,  qtype.getCode(),
+             resolveret=asyncresolveWrapper(*remoteIP, d_doDNSSEC, qname,  qtype.getCode(),
                                             doTCP, sendRDQuery, &d_now, ednsmask, &lwr);    // <- we go out on the wire!
            }
             if(resolveret==-3)
@@ -1139,14 +1145,12 @@ int SyncRes::doResolveAt(set<DNSName> nameservers, DNSName auth, bool flawedNSSe
       typedef map<CacheKey, CachePair> tcache_t;
       tcache_t tcache;
 
-      if(d_doDNSSEC) {
-       for(const auto& rec : lwr.d_records) {
-         if(rec.d_type == QType::RRSIG) {
-           auto rrsig = std::dynamic_pointer_cast<RRSIGRecordContent>(rec.d_content);
-           //      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);
-         }
-       }
+      for(const auto& rec : lwr.d_records) {
+        if(rec.d_type == QType::RRSIG) {
+          auto rrsig = std::dynamic_pointer_cast<RRSIGRecordContent>(rec.d_content);
+          //       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);
+        }
       }
 
       // reap all answers from this packet that are acceptable
index 16602965de33f569ba21f59f8b086a4c67e9bafb..4612c274103990083a58109b29bdb4db2a99939d 100644 (file)
@@ -302,7 +302,7 @@ public:
     return d_wasVariable;
   }
 
-  int asyncresolveWrapper(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res);
+  int asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res);
 
   static void doEDNSDumpAndClose(int fd);
 
@@ -645,6 +645,8 @@ extern RecursorStats g_stats;
 extern unsigned int g_numThreads;
 extern SuffixMatchNode g_delegationOnly;
 extern uint16_t g_outgoingEDNSBufsize;
+
+
 std::string reloadAuthAndForwards();
 ComboAddress parseIPAndPort(const std::string& input, uint16_t port);
 ComboAddress getQueryLocalAddress(int family, uint16_t port);
index 6d056be2332a7f55b47c629801060afcf49e6ec6..232549af254646e281c825d4b2694f89e7e2f5aa 100644 (file)
@@ -2,6 +2,8 @@
 #include "validate-recursor.hh"
 #include "syncres.hh"
 
+DNSSECMode g_dnssecmode{DNSSECMode::Process};
+
 class SRRecordOracle : public DNSRecordOracle
 {
 public:
index 1952061444b735870bfdf6000e75749c161a7758..a892cb54eecaa3086624a7616094da645a05b41b 100644 (file)
@@ -4,3 +4,12 @@
 #include "validate.hh"
 
 vState validateRecords(const vector<DNSRecord>& recs);
+
+/* Off: 3.x behaviour, we do no DNSSEC, no EDNS
+   Process: we gather DNSSEC records on all queries, of you do do=1, we'll validate for you (unless you set cd=1)
+   ValidateForLog: Process + validate all answers, but only log failures
+   ValidateAll: DNSSEC issue -> servfail
+*/
+
+enum class DNSSECMode { Off, Process, ValidateForLog, ValidateAll };
+extern DNSSECMode g_dnssecmode;