]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Support for views on AXFR.
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Thu, 24 Apr 2025 13:16:52 +0000 (15:16 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Mon, 26 May 2025 11:49:12 +0000 (13:49 +0200)
Note that this causes somewhat important plumbing changes, getSOA will now
take an optional zone ID, which callers should provide if they know it, in
order to save a possible expensive getDomainInfo call.

14 files changed:
docs/appendices/backend-writers-guide.rst
modules/bindbackend/bindbackend2.cc
modules/geoipbackend/geoipbackend.cc
modules/lua2backend/lua2api2.hh
pdns/auth-zonecache.hh
pdns/backends/gsql/gsqlbackend.cc
pdns/dnsbackend.cc
pdns/dnsbackend.hh
pdns/dnspacket.hh
pdns/rfc2136handler.cc
pdns/tcpreceiver.cc
pdns/tcpreceiver.hh
pdns/test-ueberbackend_cc.cc
pdns/ueberbackend.cc

index 13c2d4284e86c3ea167e844ac9a37baac0e590ef..143d5f31222f37dfbde6744f0d1675999fce6724 100644 (file)
@@ -70,7 +70,7 @@ following methods are relevant:
         virtual void lookup(const QType &qtype, const string &qdomain, domainid_t zoneId=UnknownDomainID, DNSPacket *pkt_p=nullptr)=0;
         virtual bool list(const string &target, domainid_t domain_id)=0;
         virtual bool get(DNSResourceRecord &r)=0;
-        virtual bool getSOA(const string &name, SOAData &soadata);
+        virtual bool getSOA(const string &name, domainid_t zoneId, SOAData &soadata);
         };
 
 Note that the first four methods must be implemented. ``getSOA()`` has
@@ -143,8 +143,8 @@ The following lists the contents of a zone called "powerdns.com".
 .. code-block:: cpp
 
         SOAData sd;
-        if(!yb.getSOA("powerdns.com",sd))  // are we authoritative over powerdns.com?
-          return RCode::NotAuth;           // no
+        if(!yb.getSOA("powerdns.com", UnknownDomainID, sd))  // are we authoritative over powerdns.com?
+          return RCode::NotAuth;                // no
 
         yb.list(sd.domain_id);
         while(yb.get(rr))
@@ -422,10 +422,11 @@ Methods
 
   Should throw an PDNSException in case a database error occurred.
 
-.. cpp:function:: bool DNSBackend::getSOA(const string &name, SOAData &soadata)
+.. cpp:function:: bool DNSBackend::getSOA(const string &name, domainid_t zoneId, SOAData &soadata)
 
-  If the backend considers itself authoritative over domain ``name``, this
-  method should fill out the passed **SOAData** structure and return true.
+  If the backend considers itself authoritative over domain ``name``, of
+  id ``zoneId`` if known (otherwise, ``UnknownDomainID``), this method should
+  fill out the passed **SOAData** structure and return true.
   If the backend is functioning correctly, but does not consider itself
   authoritative, it should return false. In case of errors, an
   PDNSException should be thrown.
index 3cd05868dab3296664d1983ba013ba59119c333d..675a372fa9b969695d382b0e20d783fd55d7f102 100644 (file)
@@ -348,7 +348,7 @@ void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::
   for (DomainInfo& di : consider) {
     soadata.serial = 0;
     try {
-      this->getSOA(di.zone, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
+      this->getSOA(di.zone, di.id, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
     }
     catch (...) {
       continue;
@@ -394,7 +394,7 @@ void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bo
       if (di.backend != this)
         continue;
       try {
-        this->getSOA(di.zone, soadata);
+        this->getSOA(di.zone, di.id, soadata);
       }
       catch (...) {
         continue;
@@ -430,7 +430,7 @@ void Bind2Backend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
     soadata.refresh = 0;
     soadata.serial = 0;
     try {
-      getSOA(sd.zone, soadata); // we might not *have* a SOA yet
+      getSOA(sd.zone, sd.id, soadata); // we might not *have* a SOA yet
     }
     catch (...) {
     }
@@ -459,7 +459,7 @@ bool Bind2Backend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool
       SOAData sd;
       sd.serial = 0;
 
-      getSOA(bbd.d_name, sd); // we might not *have* a SOA yet
+      getSOA(bbd.d_name, bbd.d_id, sd); // we might not *have* a SOA yet
       info.serial = sd.serial;
     }
     catch (...) {
index 6381d04f2df62c663e94cdde4f857e82052a63a1..137d932aaecebc0991976148fb08e9ebdd59c292 100644 (file)
@@ -911,7 +911,7 @@ bool GeoIPBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool
   for (const GeoIPDomain& dom : s_domains) {
     if (dom.domain == domain) {
       SOAData sd;
-      this->getSOA(domain, sd);
+      this->getSOA(dom.domain, dom.id, sd);
       info.id = dom.id;
       info.zone = dom.domain;
       info.serial = sd.serial;
@@ -930,7 +930,7 @@ void GeoIPBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial
   DomainInfo di;
   for (const auto& dom : s_domains) {
     SOAData sd;
-    this->getSOA(dom.domain, sd);
+    this->getSOA(dom.domain, dom.id, sd);
     di.id = dom.id;
     di.zone = dom.domain;
     di.serial = sd.serial;
index 18742000b73dbfe29d33ada81ca64b9dc5b502f8..d53384d7c7f5e1f824f90eeee28871c36f24789e 100644 (file)
@@ -282,7 +282,13 @@ public:
   bool getDomainInfo(const ZoneName& domain, DomainInfo& di, bool /* getSerial */ = true) override
   {
     if (f_get_domaininfo == nullptr) {
-      // use getAuth instead
+      // use getAuth instead... but getAuth wraps getSOA which will call
+      // getDomainInfo if this is a domain variant, so protect against this
+      // would-be infinite recursion.
+      if (domain.hasVariant()) {
+        g_log << Logger::Info << "Unable to return domain information for '" << domain.toLogString() << "' due to unimplemented dns_get_domaininfo" << endl;
+        return false;
+      }
       SOAData sd;
       if (!getAuth(domain, &sd))
         return false;
index d2632c20ee7d71482dc91fe665d51bdbf246f94f..29c530cb34391ec533a9ef6207471daba153e4b6 100644 (file)
@@ -27,6 +27,7 @@
 #include "lock.hh"
 #include "misc.hh"
 #include "iputils.hh"
+#include "dnspacket.hh"
 
 class AuthZoneCache : public boost::noncopyable
 {
index 6d12caf937267073477514d4d3e2a03362556b6b..8f531b557707a89dc7682cafbf025a8c1459786b 100644 (file)
@@ -405,8 +405,9 @@ bool GSQLBackend::getDomainInfo(const ZoneName &domain, DomainInfo &info, bool g
   if(getSerial) {
     try {
       SOAData sd;
-      if(!getSOA(domain, sd))
+      if(!getSOA(domain, info.id, sd)) {
         g_log<<Logger::Notice<<"No serial for '"<<domain<<"' found - zone is missing?"<<endl;
+      }
       else
         info.serial = sd.serial;
     }
index 24fb970e2de21b9b485803fc0aa00d0548e2b8cd..5d71833660d72e44948c70fd3117f264713cdeeb 100644 (file)
@@ -44,7 +44,7 @@ std::function<std::string(const std::string&, int)> g_getGeo;
 
 bool DNSBackend::getAuth(const ZoneName& target, SOAData* soaData)
 {
-  return this->getSOA(target, *soaData);
+  return this->getSOA(target, UnknownDomainID, *soaData);
 }
 
 void DNSBackend::setArgPrefix(const string& prefix)
@@ -245,19 +245,27 @@ vector<std::unique_ptr<DNSBackend>> BackendMakerClass::all(bool metadataOnly)
     answer, in which case you need to perform a getDomainInfo call!
 
     \param domain Domain we want to get the SOA details of
-    \param sd SOAData which is filled with the SOA details
+    \param zoneId Domain id, if known
+    \param soaData SOAData which is filled with the SOA details
     \param unmodifiedSerial bool if set, serial will be returned as stored in the backend (maybe 0)
 */
-bool DNSBackend::getSOA(const ZoneName& domain, SOAData& soaData)
+bool DNSBackend::getSOA(const ZoneName& domain, domainid_t zoneId, SOAData& soaData)
 {
-  this->lookup(QType(QType::SOA), domain.operator const DNSName&(), -1);
+  soaData.db = nullptr;
+
+  if (domain.hasVariant() && zoneId == UnknownDomainID) {
+    DomainInfo domaininfo;
+    if (!this->getDomainInfo(domain, domaininfo, false)) {
+      return false;
+    }
+    zoneId = domaininfo.id;
+  }
+  this->lookup(QType(QType::SOA), domain.operator const DNSName&(), zoneId);
   S.inc("backend-queries");
 
   DNSResourceRecord resourceRecord;
   int hits = 0;
 
-  soaData.db = nullptr;
-
   try {
     while (this->get(resourceRecord)) {
       if (resourceRecord.qtype != QType::SOA) {
index a57e76a03be5ebaf0abac09e54ac05adf69285bf..e445c96ef9ea039d10008fe420dd1e6b3bb24f75 100644 (file)
@@ -184,7 +184,7 @@ public:
   virtual ~DNSBackend() = default;
 
   //! fills the soadata struct with the SOA details. Returns false if there is no SOA.
-  virtual bool getSOA(const ZoneName& domain, SOAData& soaData);
+  virtual bool getSOA(const ZoneName& domain, domainid_t zoneId, SOAData& soaData);
 
   virtual bool replaceRRSet(domainid_t /* domain_id */, const DNSName& /* qname */, const QType& /* qt */, const vector<DNSResourceRecord>& /* rrset */)
   {
index 7003fb295d433412648ab68690f9aca85b1c7ee8..13146fde4b5c2dc0d35fa3b522bbcf86a9d19e34 100644 (file)
@@ -139,7 +139,7 @@ public:
 
   DNSName qdomain;  //!< qname of the question 4 - unsure how this is used
   DNSName qdomainwild;  //!< wildcard matched by qname, used by LuaPolicyEngine
-  ZoneName qdomainzone;  //!< zone name for the answer (as reflected in SOA for negative responses), used by LuaPolicyEngine
+  ZoneName qdomainzone;  //!< zone name for the answer (as reflected in SOA for negative responses), used by LuaPolicyEngine and AXFR
   string d_peer_principal;
   const DNSName& getTSIGKeyname() const;
 
index 30ca1b917c3d75f76578613e12d01abba0910271..d4844427d4ea988acc60d89b4e48b51749dace20 100644 (file)
@@ -1047,7 +1047,7 @@ int PacketHandler::processUpdate(DNSPacket& packet) { // NOLINT(readability-func
 
 void PacketHandler::increaseSerial(const string &msgPrefix, const DomainInfo *di, const string& soaEditSetting,  bool haveNSEC3, bool narrow, const NSEC3PARAMRecordContent *ns3pr) {
   SOAData sd;
-  if (!di->backend->getSOA(di->zone, sd)) {
+  if (!di->backend->getSOA(di->zone, di->id, sd)) {
     throw PDNSException("SOA-Serial update failed because there was no SOA. Wowie.");
   }
 
index d5615e2c107a7c5ed13bfe3d8de308ce7b4ceefb..454ef41e6af75a5cf70d8be1ab0c74759a8266f4 100644 (file)
@@ -19,6 +19,7 @@
     along with this program; if not, write to the Free Software
     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
+#include "pdns/auth-zonecache.hh"
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -368,12 +369,14 @@ void TCPNameserver::doConnection(int fd)
 
       if(packet->qtype.getCode()==QType::AXFR) {
         packet->d_xfr=true;
-        doAXFR(packet->qdomain, packet, fd);
+        g_zoneCache.setZoneVariant(packet);
+        doAXFR(packet->qdomainzone, packet, fd);
         continue;
       }
 
       if(packet->qtype.getCode()==QType::IXFR) {
         packet->d_xfr=true;
+        g_zoneCache.setZoneVariant(packet);
         doIXFR(packet, fd);
         continue;
       }
@@ -460,8 +463,7 @@ bool TCPNameserver::canDoAXFR(std::unique_ptr<DNSPacket>& q, bool isAXFR, std::u
   if(::arg().mustDo("disable-axfr"))
     return false;
 
-  ZoneName zonename(q->qdomain);
-  string logPrefix=string(isAXFR ? "A" : "I")+"XFR-out zone '"+zonename.toLogString()+"', client '"+q->getInnerRemote().toStringWithPort()+"', ";
+  string logPrefix=string(isAXFR ? "A" : "I")+"XFR-out zone '"+q->qdomainzone.toLogString()+"', client '"+q->getInnerRemote().toStringWithPort()+"', ";
 
   if(q->d_havetsig) { // if you have one, it must be good
     TSIGRecordContent tsigContent;
@@ -485,18 +487,18 @@ bool TCPNameserver::canDoAXFR(std::unique_ptr<DNSPacket>& q, bool isAXFR, std::u
 #ifdef ENABLE_GSS_TSIG
     if (g_doGssTSIG && q->d_tsig_algo == TSIG_GSS) {
       vector<string> princs;
-      packetHandler->getBackend()->getDomainMetadata(zonename, "GSS-ALLOW-AXFR-PRINCIPAL", princs);
+      packetHandler->getBackend()->getDomainMetadata(q->qdomainzone, "GSS-ALLOW-AXFR-PRINCIPAL", princs);
       for(const std::string& princ :  princs) {
         if (q->d_peer_principal == princ) {
-          g_log<<Logger::Warning<<"AXFR of domain '"<<zonename<<"' allowed: TSIG signed request with authorized principal '"<<q->d_peer_principal<<"' and algorithm 'gss-tsig'"<<endl;
+          g_log<<Logger::Warning<<"AXFR of domain '"<<q->qdomainzone<<"' allowed: TSIG signed request with authorized principal '"<<q->d_peer_principal<<"' and algorithm 'gss-tsig'"<<endl;
           return true;
         }
       }
-      g_log<<Logger::Warning<<"AXFR of domain '"<<zonename<<"' denied: TSIG signed request with principal '"<<q->d_peer_principal<<"' and algorithm 'gss-tsig' is not permitted"<<endl;
+      g_log<<Logger::Warning<<"AXFR of domain '"<<q->qdomainzone<<"' denied: TSIG signed request with principal '"<<q->d_peer_principal<<"' and algorithm 'gss-tsig' is not permitted"<<endl;
       return false;
     }
 #endif
-    if(!dk.TSIGGrantsAccess(zonename, tsigkeyname)) {
+    if(!dk.TSIGGrantsAccess(q->qdomainzone, tsigkeyname)) {
       g_log<<Logger::Warning<<logPrefix<<"denied: key with name '"<<tsigkeyname<<"' and algorithm '"<<getTSIGAlgoName(q->d_tsig_algo)<<"' does not grant access"<<endl;
       return false;
     }
@@ -516,10 +518,10 @@ bool TCPNameserver::canDoAXFR(std::unique_ptr<DNSPacket>& q, bool isAXFR, std::u
 
   // cerr<<"doing per-zone-axfr-acls"<<endl;
   SOAData sd;
-  if(packetHandler->getBackend()->getSOAUncached(zonename,sd)) {
+  if(packetHandler->getBackend()->getSOAUncached(q->qdomainzone,sd)) {
     // cerr<<"got backend and SOA"<<endl;
     vector<string> acl;
-    packetHandler->getBackend()->getDomainMetadata(zonename, "ALLOW-AXFR-FROM", acl);
+    packetHandler->getBackend()->getDomainMetadata(q->qdomainzone, "ALLOW-AXFR-FROM", acl);
     for (const auto & i : acl) {
       // cerr<<"matching against "<<*i<<endl;
       if(pdns_iequals(i, "AUTO-NS")) {
@@ -560,7 +562,7 @@ bool TCPNameserver::canDoAXFR(std::unique_ptr<DNSPacket>& q, bool isAXFR, std::u
 
   extern CommunicatorClass Communicator;
 
-  if(Communicator.justNotified(zonename, q->getInnerRemote().toString())) { // we just notified this ip
+  if(Communicator.justNotified(q->qdomainzone, q->getInnerRemote().toString())) { // we just notified this ip
     g_log<<Logger::Notice<<logPrefix<<"allowed: client IP is from recently notified secondary"<<endl;
     return true;
   }
@@ -589,9 +591,9 @@ namespace {
 
 
 /** do the actual zone transfer. Return 0 in case of error, 1 in case of success */
-int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock)  // NOLINT(readability-function-cognitive-complexity)
+int TCPNameserver::doAXFR(const ZoneName &targetZone, std::unique_ptr<DNSPacket>& q, int outsock)  // NOLINT(readability-function-cognitive-complexity)
 {
-  ZoneName targetZone(target);
+  DNSName target = targetZone.operator const DNSName&();
   string logPrefix="AXFR-out zone '"+targetZone.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', ";
 
   std::unique_ptr<DNSPacket> outpacket= getFreshAXFRPacket(q);
@@ -713,9 +715,8 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
   if(securedZone && !presignedZone) { // this is where the DNSKEYs, CDNSKEYs and CDSs go in
     bool doCDNSKEY = true, doCDS = true;
     string publishCDNSKEY, publishCDS;
-    ZoneName zonename(q->qdomain);
-    dk.getPublishCDNSKEY(zonename, publishCDNSKEY);
-    dk.getPublishCDS(zonename, publishCDS);
+    dk.getPublishCDNSKEY(q->qdomainzone, publishCDNSKEY);
+    dk.getPublishCDS(q->qdomainzone, publishCDS);
 
     set<uint32_t> entryPointIds;
     DNSSECKeeper::keyset_t entryPoints = dk.getEntryPoints(targetZone);
@@ -1189,8 +1190,7 @@ send:
 
 int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
 {
-  ZoneName zonename(q->qdomain);
-  string logPrefix="IXFR-out zone '"+zonename.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', ";
+  string logPrefix="IXFR-out zone '"+q->qdomainzone.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', ";
 
   std::unique_ptr<DNSPacket> outpacket=getFreshAXFRPacket(q);
   if(q->d_dnssecOk)
@@ -1242,7 +1242,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
     }
 
     // canDoAXFR does all the ACL checks, and has the if(disable-axfr) shortcut, call it first.
-    if(!canDoAXFR(q, false, *packetHandler) || !(*packetHandler)->getBackend()->getSOAUncached(zonename, sd)) {
+    if(!canDoAXFR(q, false, *packetHandler) || !(*packetHandler)->getBackend()->getSOAUncached(q->qdomainzone, sd)) {
       g_log<<Logger::Warning<<logPrefix<<"failed: not authoritative"<<endl;
       outpacket->setRcode(RCode::NotAuth);
       sendPacket(outpacket,outsock);
@@ -1250,10 +1250,10 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
     }
 
     DNSSECKeeper dk((*packetHandler)->getBackend());
-    DNSSECKeeper::clearCaches(zonename);
+    DNSSECKeeper::clearCaches(q->qdomainzone);
     bool narrow = false;
-    securedZone = dk.isSecuredZone(zonename);
-    if(dk.getNSEC3PARAM(zonename, nullptr, &narrow)) {
+    securedZone = dk.isSecuredZone(q->qdomainzone);
+    if(dk.getNSEC3PARAM(q->qdomainzone, nullptr, &narrow)) {
       if(narrow) {
         g_log<<Logger::Warning<<logPrefix<<"not doing IXFR of an NSEC3 narrow zone"<<endl;
         outpacket->setRcode(RCode::Refused);
@@ -1266,7 +1266,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
   }
 
   if (serialPermitsIXFR) {
-    const ZoneName& target = zonename;
+    const ZoneName& target = q->qdomainzone;
     TSIGRecordContent trc;
     DNSName tsigkeyname;
     string tsigsecret;
@@ -1312,7 +1312,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
   }
 
   g_log<<Logger::Notice<<logPrefix<<"IXFR fallback to AXFR"<<endl;
-  return doAXFR(q->qdomain, q, outsock);
+  return doAXFR(q->qdomainzone, q, outsock);
 }
 
 TCPNameserver::~TCPNameserver() = default;
index fbb6bd8ea0bfc826ed9a98805b2f08c86661044f..81036de5ce4325299e1228169a5b040848f2788f 100644 (file)
@@ -50,7 +50,7 @@ private:
 
   static void sendPacket(std::unique_ptr<DNSPacket>& p, int outsock, bool last=true);
   static void getQuestion(int fd, char *mesg, int pktlen, const ComboAddress& remote, unsigned int totalTime);
-  static int doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock);
+  static int doAXFR(const ZoneName &target, std::unique_ptr<DNSPacket>& q, int outsock);
   static int doIXFR(std::unique_ptr<DNSPacket>& q, int outsock);
   static bool canDoAXFR(std::unique_ptr<DNSPacket>& q, bool isAXFR, std::unique_ptr<PacketHandler>& packetHandler);
   static void doConnection(int fd);
index 4181c22b4089b09f8006d85a123824bab3829e1e..0225b76dbc25ebf751900a9e319a259bb2c7551e 100644 (file)
@@ -275,7 +275,7 @@ public:
       return true;
     }
 
-    return getSOA(target, *soadata);
+    return getSOA(target, UnknownDomainID, *soadata);
   }
 
   size_t d_authLookupCount{0};
index 8916ccbdf410e2e98e40e75f79d60ea3177c4185..b62dc57179cc05f60c9cc3cf127c660449fa22bb 100644 (file)
@@ -649,7 +649,7 @@ bool UeberBackend::getSOAUncached(const ZoneName& domain, SOAData& soaData)
     if (domain.hasVariant() && (backend->getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
       continue;
     }
-    if (backend->getSOA(domain, soaData)) {
+    if (backend->getSOA(domain, UnknownDomainID, soaData)) {
       if (domain.operator const DNSName&() != soaData.qname) {
         throw PDNSException("getSOA() returned an SOA for the wrong zone. Question: '" + domain.toLogString() + "', answer: '" + soaData.qname.toLogString() + "'");
       }