]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
wip commit for auth ixfr slaving
authorbert hubert <bert.hubert@netherlabs.nl>
Sat, 4 Jun 2016 10:19:16 +0000 (12:19 +0200)
committerbert hubert <bert.hubert@powerdns.com>
Tue, 7 Jun 2016 09:25:01 +0000 (11:25 +0200)
pdns/communicator.cc
pdns/communicator.hh
pdns/ixfr.cc
pdns/ixfr.hh
pdns/slavecommunicator.cc

index daf405a2e86f6721efbc25a7e54cabe9c1155cf1..f472a4027e585dfc51a47df752b99fe934684488 100644 (file)
@@ -37,8 +37,7 @@
 #include "arguments.hh"
 #include "packetcache.hh"
 
-// #include "namespaces.hh"
-
+// there can be MANY OF THESE
 void CommunicatorClass::retrievalLoopThread(void)
 {
   for(;;) {
index 767410a271b994f23aacc469501d35e771f1d8e8..a40f18742ad6b02d82d1039f966e6c04b1ff7168 100644 (file)
@@ -1,6 +1,6 @@
 /*
     PowerDNS Versatile Database Driven Nameserver
-    Copyright (C) 2002-2010  PowerDNS.COM BV
+    Copyright (C) 2002-2016  PowerDNS.COM BV
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2
@@ -31,6 +31,7 @@
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/identity.hpp>
 #include <boost/multi_index/sequenced_index.hpp>
+#include <boost/scoped_ptr.hpp>
 using namespace boost::multi_index;
 
 #include <unistd.h>
@@ -140,6 +141,8 @@ private:
 
 };
 
+struct ZoneStatus;
+
 /** this class contains a thread that communicates with other nameserver and does housekeeping.
     Initially, it is notified only of zones that need to be pulled in because they have been updated. */
 
@@ -191,13 +194,15 @@ private:
   pthread_mutex_t d_holelock;
   void launchRetrievalThreads();
   void suck(const DNSName &domain, const string &remote);
-  void ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote);
+  void ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, boost::scoped_ptr<AuthLua>& pdl,
+                ZoneStatus& zs, vector<DNSRecord>* axfr);
 
   void slaveRefresh(PacketHandler *P);
   void masterUpdateCheck(PacketHandler *P);
   pthread_mutex_t d_lock;
   
   UniQueue d_suckdomains;
+  set<DNSName> d_inprogress;
   
   Semaphore d_suck_sem;
   Semaphore d_any_sem;
@@ -210,6 +215,21 @@ private:
   bool d_havepriosuckrequest;
   bool d_masterschanged, d_slaveschanged;
   bool d_preventSelfNotification;
+
+  struct RemoveSentinel
+  {
+    explicit RemoveSentinel(const DNSName& dn, CommunicatorClass* cc) : d_dn(dn), d_cc(cc)
+    {}
+    
+    ~RemoveSentinel()
+    {
+      Lock l(&d_cc->d_lock);
+      d_cc->d_inprogress.erase(d_dn);
+    }
+    DNSName d_dn;
+    CommunicatorClass* d_cc;
+};
+
 };
 
 // class that one day might be more than a function to help you get IP addresses for a nameserver
index b30b84f06fbc8944d4dbd95ba7708fdee9dc5ddb..06d9fe5a9f6129ccd177c57d9b333a3fb3739187 100644 (file)
@@ -5,8 +5,9 @@
 #include "dnssecinfra.hh"
 
 
-// if you the remove,add pairs always remove a SOA and add a new one. If you get an empty remove, it means you got an AXFR!
-vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& oursr, const TSIGTriplet& tt)
+// Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
+vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& oursr, 
+                                                                   const TSIGTriplet& tt, const ComboAddress* laddr)
 {
   vector<pair<vector<DNSRecord>, vector<DNSRecord> > >  ret;
   vector<uint8_t> packet;
@@ -39,8 +40,8 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
 
   Socket s(master.sin4.sin_family, SOCK_STREAM);
   //  cout<<"going to connect"<<endl;
-  ComboAddress local("2001:470:1f15:86f:4413:6149:48d1:16a3", 0);
-  s.bind(local);
+  if(laddr)
+    s.bind(*laddr);
   s.connect(master);
   //  cout<<"Connected"<<endl;
   s.writen(msg);
index db708586c723a72543d13f90ac0eddb05b8dcca6..543f4454a2b91273c13f7904f46d1e2a81667dff 100644 (file)
@@ -2,5 +2,6 @@
 #include "iputils.hh"
 #include "dnsparser.hh"
 
-vector<pair<vector<DNSRecord>, vector<DNSRecord> > >   getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& sr,
-                                                                     const TSIGTriplet& tt=TSIGTriplet());
+vector<pair<vector<DNSRecord>, vector<DNSRecord> > >   getIXFRDeltas(const ComboAddress& master, const DNSName& zone, 
+                                                                     const DNSRecord& sr, const TSIGTriplet& tt=TSIGTriplet(),
+                                                                     const ComboAddress* laddr=0);
index 4280361d46ad2689f51d69dbcf01a9d86262d4aa..d19383b4716f3ee6f1d581b55580182adce8e068 100644 (file)
@@ -45,7 +45,7 @@
 #include "lua-auth.hh"
 #include "namespaces.hh"
 #include "common_startup.hh"
-#include <boost/scoped_ptr.hpp>
+
 #include "ixfr.hh"
 using boost::scoped_ptr;
 
@@ -59,10 +59,10 @@ void CommunicatorClass::addSuckRequest(const DNSName &domain, const string &mast
   pair<UniQueue::iterator, bool>  res;
 
   res=d_suckdomains.push_back(sr);
-
   if(res.second) {
     d_suck_sem.post();
   }
+
 }
 
 struct ZoneStatus
@@ -77,10 +77,12 @@ struct ZoneStatus
   unsigned int soa_serial{0};
   set<DNSName> nsset, qnames, secured;
   uint32_t domain_id;
+  int numDeltas{0};
 };
 
 
-void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote)
+void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, scoped_ptr<AuthLua>& pdl,
+                                 ZoneStatus& zs, vector<DNSRecord>* axfr)
 {
   UeberBackend B; // fresh UeberBackend
 
@@ -101,63 +103,121 @@ void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, c
 
     DNSRecord dr;
     dr.d_content = std::make_shared<SOARecordContent>(DNSName("."), DNSName("."), st);
-    auto deltas = getIXFRDeltas(remote, domain, dr, tt);
-    cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
+    auto deltas = getIXFRDeltas(remote, domain, dr, tt, laddr.sin4.sin_family ? &laddr : 0);
+    zs.numDeltas=deltas.size();
+    //    cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
     
     for(const auto& d : deltas) {
-      const auto& before= d.first;
-      const auto& after = d.second;
+      const auto& remove = d.first;
+      const auto& add = d.second;
+      //      cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl;
       
+      if(remove.empty()) { // we got passed an AXFR!
+        *axfr = add;
+        return;
+      }
+        
+
       // our hammer is 'replaceRRSet(domain_id, qname, qt, vector<DNSResourceRecord>& rrset)
-      // first thing we need to do is delete every {qname/qt} present in before and not in after
+      // which thinks in terms of RRSETs
+      // however, IXFR does not, and removes and adds *records* (bummer)
+      // this means that we must group updates by {qname,qtype}, retrieve the RRSET, apply
+      // the add/remove updates, and replaceRRSet the whole thing. 
       
-      typedef set<pair<DNSName,uint16_t>> present_t;
-      present_t pbefore, pafter;
-      for(const auto& b: before)
-        pbefore.insert({b.d_name, b.d_type});
-      for(const auto& a: after)
-        pafter.insert({a.d_name, a.d_type});
-
-      vector<pair<DNSName,uint16_t>> diff;
-    
-      set_difference(pbefore.cbegin(), pbefore.cend(), pafter.cbegin(), pafter.cend(), back_inserter(diff));
+      
+      map<pair<DNSName,uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord> > > grouped;
+      
+      for(const auto& x: remove)
+        grouped[{x.d_name, x.d_type}].first.push_back(x);
+      for(const auto& x: add)
+        grouped[{x.d_name, x.d_type}].second.push_back(x);
 
       di.backend->startTransaction(domain, -1);
-      for(const auto& gone : diff) {
-        //        cerr<<"Removing "<<gone.first<<"/"<<QType(gone.second).getName()<<endl;
-        di.backend->replaceRRSet(di.id, gone.first+domain, QType(gone.second), vector<DNSResourceRecord>());
-      }
-      cout<<"Removed "<<diff.size()<<" entries"<<endl;
+      for(const auto g : grouped) {
+        DNSResourceRecord rr;
+        vector<DNSRecord> rrset;
+        B.lookup(QType(g.first.second), g.first.first, 0, di.id);
+        while(B.get(rr)) {
+          rrset.push_back(DNSRecord{rr});
+        }
+        // O(N^2)!
+        rrset.erase(remove_if(rrset.begin(), rrset.end(), 
+                              [&g](const DNSRecord& dr) {
+                                return count(g.second.first.cbegin(), 
+                                             g.second.first.cend(), dr);
+                              }), rrset.end());
+        // the DNSRecord== operator compares on name, type, class and lowercase content representation
+
+        for(const auto& x : g.second.second) {
+          rrset.push_back(x);
+        }
 
-      map<pair<DNSName,uint16_t>, vector<DNSResourceRecord>> replacement;
-      
-      for(const auto& add : after) {
-        DNSResourceRecord dr(add);
-        dr.qname += domain;
-        dr.domain_id=di.id;
-        if(add.d_type == QType::SOA)
-          cout<<"New SOA: "<<add.d_content->getZoneRepresentation()<<endl;
-        replacement[{add.d_name, add.d_type}].push_back(dr);
-      }
-      for(const auto& rep : replacement) {
-        //        cout<<"Adding back in "<<rep.first.first<<"/"<<QType(rep.first.second).getName()<<": "<<rep.second.begin()->content<<endl;
-        di.backend->replaceRRSet(di.id, rep.first.first+domain, QType(rep.first.second), rep.second);
-      }
+        vector<DNSResourceRecord> replacement;
+        for(const auto& x : rrset) {
+          DNSResourceRecord dr(x);
+          dr.qname += domain;
+          dr.domain_id = di.id;
+          if(x.d_type == QType::SOA) {
+            //            cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl;
+            auto sr = getRR<SOARecordContent>(x);
+            zs.soa_serial=sr->d_st.serial;
+          }
+          
+          replacement.push_back(dr);
+        }
 
-      cout<<"Added "<<replacement.size()<<" rrsets. And commit!"<<endl;
+        di.backend->replaceRRSet(di.id, g.first.first+domain, QType(g.first.second), replacement);
+      }
       di.backend->commitTransaction();
     }
   }
   catch(std::exception& p) {
-    cerr<<"Got exception: "<<p.what()<<endl;
-    exit(1);
+    L<<Logger::Error<<"Got exception during IXFR: "<<p.what()<<endl;
+    throw;
   }
   catch(PDNSException& p) {
-    cerr<<"Got exception: "<<p.reason<<endl;
-    exit(1);
-  }
+    L<<Logger::Error<<"Got exception during IXFR: "<<p.reason<<endl;
+    throw;
+  }  
+}
 
+
+static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResourceRecord& rr, ZoneStatus& zs)
+{
+  switch(rr.qtype.getCode()) {
+  case QType::NSEC3PARAM: 
+    zs.ns3pr = NSEC3PARAMRecordContent(rr.content);
+    zs.isDnssecZone = zs.isNSEC3 = true;
+    zs.isNarrow = false;
+  
+  case QType::NSEC3: {
+    NSEC3RecordContent ns3rc(rr.content);
+    if (firstNSEC3) {
+      zs.isDnssecZone = zs.isPresigned = true;
+      firstNSEC3 = false;
+    } else if (zs.optOutFlag != (ns3rc.d_flags & 1))
+      throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported.");
+    zs.optOutFlag = ns3rc.d_flags & 1;
+    if (ns3rc.d_set.count(QType::NS) && !(rr.qname==domain)) {
+      DNSName hashPart = DNSName(toLower(rr.qname.makeRelative(domain).toString()));
+      zs.secured.insert(hashPart);
+    }
+  }
+  
+  case QType::NSEC: 
+    zs.isDnssecZone = zs.isPresigned = true;
+  
+  
+  case QType::NS: 
+    if(rr.qname!=domain)
+      zs.nsset.insert(rr.qname);
   
+  }
+
+  zs.qnames.insert(rr.qname);
+
+  rr.domain_id=zs.domain_id;
+  return true;
 }
 
 /* So this code does a number of things. 
@@ -196,65 +256,40 @@ vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domai
 
       vector<DNSResourceRecord> out;
       if(!pdl || !pdl->axfrfilter(raddr, domain, *i, out)) {
-        out.push_back(*i);
+        out.push_back(*i); // if axfrfilter didn't do anything, we put our record in 'out' ourselves
       }
 
       for(DNSResourceRecord& rr :  out) {
-        switch(rr.qtype.getCode()) {
-        case QType::NSEC3PARAM: {
-          zs.ns3pr = NSEC3PARAMRecordContent(rr.content);
-          zs.isDnssecZone = zs.isNSEC3 = true;
-          zs.isNarrow = false;
-          continue;
-        }
-        case QType::NSEC3: {
-          NSEC3RecordContent ns3rc(rr.content);
-          if (firstNSEC3) {
-            zs.isDnssecZone = zs.isPresigned = true;
-            firstNSEC3 = false;
-          } else if (zs.optOutFlag != (ns3rc.d_flags & 1))
-            throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported.");
-          zs.optOutFlag = ns3rc.d_flags & 1;
-          if (ns3rc.d_set.count(QType::NS) && !(rr.qname==domain)) {
-            DNSName hashPart = DNSName(toLower(rr.qname.makeRelative(domain).toString()));
-            zs.secured.insert(hashPart);
-          }
-          continue;
-        }
-        case QType::NSEC: {
-          zs.isDnssecZone = zs.isPresigned = true;
-          continue;
-        }
-        case QType::SOA: {
+        processRecordForZS(domain, firstNSEC3, rr, zs);
+        if(rr.qtype.getCode() == QType::SOA) {
           if(soa_received)
             continue; //skip the last SOA
           SOAData sd;
           fillSOAData(rr.content,sd);
           zs.soa_serial = sd.serial;
           soa_received = true;
-          break;
-        }
-        case QType::NS: {
-          if(rr.qname!=domain)
-            zs.nsset.insert(rr.qname);
-          break;
-        }
-        default:
-          break;
         }
 
-        zs.qnames.insert(rr.qname);
-
-        rr.domain_id=zs.domain_id;
         rrs.push_back(rr);
+
       }
     }
   }
   return rrs;
 }   
 
+
 void CommunicatorClass::suck(const DNSName &domain, const string &remote)
 {
+  {
+    Lock l(&d_lock);
+    if(d_inprogress.count(domain)) {
+      return; 
+    }
+    d_inprogress.insert(domain);
+  }
+  RemoveSentinel rs(domain, this); // this removes us from d_inprogress when we go out of scope
+
   L<<Logger::Error<<"Initiating transfer of '"<<domain<<"' from remote '"<<remote<<"'"<<endl;
   UeberBackend B; // fresh UeberBackend
 
@@ -321,7 +356,7 @@ void CommunicatorClass::suck(const DNSName &domain, const string &remote)
     bool hadNarrow=false;
 
     ComboAddress raddr(remote, 53);
-
+    vector<DNSResourceRecord> rrs;
     if(dk.isSecuredZone(domain)) {
       hadDnssecZone=true;
       hadPresigned=dk.isPresigned(domain);
@@ -334,11 +369,37 @@ void CommunicatorClass::suck(const DNSName &domain, const string &remote)
     else if(di.serial) {
       vector<string> meta;
       B.getDomainMetadata(domain, "IXFR", meta);
-      if(!meta.empty() && meta[0]=="1")
-        return ixfrSuck(domain, tt, laddr, raddr);
+      if(!meta.empty() && meta[0]=="1") {
+        vector<DNSRecord> axfr;
+        ixfrSuck(domain, tt, laddr, raddr, pdl, zs, &axfr);
+        if(!axfr.empty()) {
+          L<<Logger::Warning<<"IXFR of '"<<domain<<"' from remote '"<<raddr.toStringWithPort()<<"' turned into an AXFR"<<endl;
+          bool firstNSEC3=true;
+          rrs.reserve(axfr.size());
+          for(const auto& dr : axfr) {
+            DNSResourceRecord rr(dr);
+            rr.qname += domain;
+            rr.domain_id = zs.domain_id;
+            processRecordForZS(domain, firstNSEC3, rr, zs);
+            if(dr.d_type == QType::SOA) {
+              auto sd = getRR<SOARecordContent>(dr);
+              zs.soa_serial = sd->d_st.serial;
+            }
+            rrs.push_back(rr);
+          }
+        }
+        else {
+          L<<Logger::Warning<<"Done with IXFR of '"<<domain<<"' from remote '"<<remote<<"', got "<<zs.numDeltas<<" delta"<<addS(zs.numDeltas)<<", serial now "<<zs.soa_serial<<endl;
+          return;
+        }
+      }
     }
 
-    vector<DNSResourceRecord> rrs = doAxfr(raddr, domain, tt, laddr, pdl, zs);
+    if(rrs.empty()) {
+      L<<Logger::Warning<<"Starting AXFR of '"<<domain<<"' from remote "<<raddr.toStringWithPort()<<endl;
+      rrs = doAxfr(raddr, domain, tt, laddr, pdl, zs);
+      L<<Logger::Warning<<"AXFR of '"<<domain<<"' from remote "<<raddr.toStringWithPort()<<" done"<<endl;
+    }
  
     if(zs.isNSEC3) {
       zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0;
@@ -366,7 +427,7 @@ void CommunicatorClass::suck(const DNSName &domain, const string &remote)
 
 
     transaction=di.backend->startTransaction(domain, zs.domain_id);
-    L<<Logger::Error<<"Transaction started for '"<<domain<<"'"<<endl;
+    L<<Logger::Error<<"Backend transaction started for '"<<domain<<"' storage"<<endl;
 
     // update the presigned flag and NSEC3PARAM
     if (zs.isDnssecZone) {
@@ -643,7 +704,7 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
 
   UeberBackend *B=P->getBackend();
   vector<DomainInfo> rdomains;
-  vector<DomainNotificationInfo> sdomains; // the bool is for 'presigned'
+  vector<DomainNotificationInfo> sdomains; 
   vector<DNSPacket> trysuperdomains;
 
   {
@@ -670,10 +731,9 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
       delete r;
     }
   }
-
-  if(rdomains.empty()) // if we have priority domains, check them first
+  if(rdomains.empty()) { // if we have priority domains, check them first
     B->getUnfreshSlaveInfos(&rdomains);
-
+  }
   DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
   {
     Lock l(&d_lock);
@@ -687,9 +747,12 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
         continue;
       // remove unfresh domains already queued for AXFR, no sense polling them again
       sr.master=*di.masters.begin();
-      if(nameindex.count(sr)) {
+      if(nameindex.count(sr)) {  // this does NOT however protect us against AXFRs already in progress!
         continue;
       }
+      if(d_inprogress.count(sr.domain)) // this does
+        continue;
+
       DomainNotificationInfo dni;
       dni.di=di;
       dni.dnssecOk = dk.isPresigned(di.zone);
@@ -718,12 +781,11 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
       sdomains.push_back(dni);
     }
   }
-
   if(sdomains.empty())
   {
     if(d_slaveschanged) {
       Lock l(&d_lock);
-      L<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already"<<endl;
+      L<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already, "<<d_inprogress.size()<<" in progress"<<endl;
     }
     d_slaveschanged = !rdomains.empty();
     return;
@@ -753,7 +815,7 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
       L<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl;
     }
   }
-  L<<Logger::Warning<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zones, had "<<ifl.getTimeouts()<<" timeouts"<<endl;
+  L<<Logger::Warning<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zone"<<addS(ssr.d_freshness.size())<<", had "<<ifl.getTimeouts()<<" timeout"<<addS(ifl.getTimeouts())<<endl;
 
   typedef DomainNotificationInfo val_t;
   for(val_t& val :  sdomains) {
@@ -764,7 +826,7 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
         continue;
     }
 
-    if(!ssr.d_freshness.count(di.id))
+    if(!ssr.d_freshness.count(di.id)) // what does this mean? XXX
       continue;
     uint32_t theirserial = ssr.d_freshness[di.id].theirSerial, ourserial = di.serial;