From d5385b3570b0d8c13626dcb4cd8fe5fc344db16d Mon Sep 17 00:00:00 2001 From: Chris Hofstaedtler Date: Mon, 19 Oct 2020 16:21:17 +0200 Subject: [PATCH] auth: add a cache of existing domains --- docs/settings.rst | 12 ++ modules/bindbackend/bindbackend2.cc | 5 + modules/lmdbbackend/lmdbbackend.cc | 29 ++-- modules/lmdbbackend/lmdbbackend.hh | 2 +- modules/remotebackend/Makefile.am | 1 + .../remotebackend/test-remotebackend-http.cc | 2 + .../remotebackend/test-remotebackend-json.cc | 2 + .../remotebackend/test-remotebackend-pipe.cc | 2 + .../remotebackend/test-remotebackend-post.cc | 2 + .../remotebackend/test-remotebackend-unix.cc | 2 + .../test-remotebackend-zeromq.cc | 2 + pdns/Makefile.am | 3 + pdns/auth-domaincache.cc | 131 ++++++++++++++++++ pdns/auth-domaincache.hh | 93 +++++++++++++ pdns/backends/gsql/gsqlbackend.cc | 18 ++- pdns/backends/gsql/gsqlbackend.hh | 2 +- pdns/common_startup.cc | 26 +++- pdns/common_startup.hh | 2 + pdns/dnsbackend.hh | 2 +- pdns/pdnsutil.cc | 2 + pdns/receiver.cc | 6 + pdns/test-ueberbackend_cc.cc | 10 ++ pdns/testrunner.cc | 2 + pdns/ueberbackend.cc | 62 +++++++-- pdns/ueberbackend.hh | 4 +- regression-tests/backends/ldap-master | 1 + regression-tests/tests/bind-add-zone/command | 3 +- .../tests/bind-add-zone/expected_result.bind | 1 - 28 files changed, 397 insertions(+), 32 deletions(-) create mode 100644 pdns/auth-domaincache.cc create mode 100644 pdns/auth-domaincache.hh diff --git a/docs/settings.rst b/docs/settings.rst index 07fb6c2b3a..9a3774aeb9 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -171,6 +171,18 @@ Maximum time in seconds for inbound AXFR to start or be idle after starting. Also AXFR a zone from a master with a lower serial. +.. _setting-domain-cache-ttl: + +``domain-cache-ttl`` +-------------------- + +- Integer +- Default: 0 + +Seconds to cache a list of all known domains. A value of 0 will disable the cache. + +If your backends do not respond to unknown domains, it is suggested to enable :ref:`setting-consistent-backends` and set this option to `60`. + .. _setting-cache-ttl: ``cache-ttl`` diff --git a/modules/bindbackend/bindbackend2.cc b/modules/bindbackend/bindbackend2.cc index 1d3312ed2d..3f826feb84 100644 --- a/modules/bindbackend/bindbackend2.cc +++ b/modules/bindbackend/bindbackend2.cc @@ -53,6 +53,9 @@ #include "pdns/misc.hh" #include "pdns/dynlistener.hh" #include "pdns/lock.hh" +#include "pdns/auth-domaincache.hh" + +extern AuthDomainCache g_domainCache; /* All instances of this backend share one s_state, which is indexed by zone name and zone id. @@ -703,6 +706,8 @@ string Bind2Backend::DLAddDomainHandler(const vector& parts, Utility::pi safePutBBDomainInfo(bbd); + g_domainCache.add(domainname, bbd.d_id); // make new domain visible + g_log << Logger::Warning << "Zone " << domainname << " loaded" << endl; return "Loaded zone " + domainname.toLogString() + " from " + filename; } diff --git a/modules/lmdbbackend/lmdbbackend.cc b/modules/lmdbbackend/lmdbbackend.cc index 03b0d5438a..832beb667f 100644 --- a/modules/lmdbbackend/lmdbbackend.cc +++ b/modules/lmdbbackend/lmdbbackend.cc @@ -953,22 +953,29 @@ bool LMDBBackend::setMasters(const DNSName& domain, const vector& }); } -bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector& masters, const string& account) +bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector& masters, const string& account, int* zoneId) { DomainInfo di; - auto txn = d_tdomains->getRWTransaction(); - if (txn.get<0>(domain, di)) { - throw DBException("Domain '" + domain.toLogString() + "' exists already"); - } + { + auto txn = d_tdomains->getRWTransaction(); + if (txn.get<0>(domain, di)) { + throw DBException("Domain '" + domain.toLogString() + "' exists already"); + } + + di.zone = domain; + di.kind = kind; + di.masters = masters; + di.account = account; - di.zone = domain; - di.kind = kind; - di.masters = masters; - di.account = account; + txn.put(di); + txn.commit(); + } - txn.put(di); - txn.commit(); + if (zoneId != nullptr) { + auto txn = d_tdomains->getROTransaction(); + *zoneId = txn.get<0>(domain, di); + } return true; } diff --git a/modules/lmdbbackend/lmdbbackend.hh b/modules/lmdbbackend/lmdbbackend.hh index 9951841d09..f9aca4e702 100644 --- a/modules/lmdbbackend/lmdbbackend.hh +++ b/modules/lmdbbackend/lmdbbackend.hh @@ -59,7 +59,7 @@ public: bool list(const DNSName& target, int id, bool include_disabled) override; bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getserial = true) override; - bool createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector& masters, const string& account) override; + bool createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector& masters, const string& account, int* zoneId) override; bool startTransaction(const DNSName& domain, int domain_id = -1) override; bool commitTransaction() override; diff --git a/modules/remotebackend/Makefile.am b/modules/remotebackend/Makefile.am index bfc378f3f3..f0218cfc44 100644 --- a/modules/remotebackend/Makefile.am +++ b/modules/remotebackend/Makefile.am @@ -95,6 +95,7 @@ BUILT_SOURCES = ../../pdns/dnslabeltext.cc libtestremotebackend_la_SOURCES = \ ../../pdns/arguments.hh ../../pdns/arguments.cc \ + ../../pdns/auth-domaincache.cc ../../pdns/auth-domaincache.hh \ ../../pdns/auth-packetcache.cc ../../pdns/auth-packetcache.hh \ ../../pdns/auth-querycache.cc ../../pdns/auth-querycache.hh \ ../../pdns/base32.cc \ diff --git a/modules/remotebackend/test-remotebackend-http.cc b/modules/remotebackend/test-remotebackend-http.cc index 796737dc38..babf135990 100644 --- a/modules/remotebackend/test-remotebackend-http.cc +++ b/modules/remotebackend/test-remotebackend-http.cc @@ -32,10 +32,12 @@ #include "pdns/arguments.hh" #include "pdns/json.hh" #include "pdns/statbag.hh" +#include "pdns/auth-domaincache.hh" #include "pdns/auth-packetcache.hh" #include "pdns/auth-querycache.hh" StatBag S; +AuthDomainCache g_domainCache; AuthPacketCache PC; AuthQueryCache QC; ArgvMap& arg() diff --git a/modules/remotebackend/test-remotebackend-json.cc b/modules/remotebackend/test-remotebackend-json.cc index 26f905c476..87a8234ef1 100644 --- a/modules/remotebackend/test-remotebackend-json.cc +++ b/modules/remotebackend/test-remotebackend-json.cc @@ -31,10 +31,12 @@ #include "pdns/arguments.hh" #include "pdns/json.hh" #include "pdns/statbag.hh" +#include "pdns/auth-domaincache.hh" #include "pdns/auth-packetcache.hh" #include "pdns/auth-querycache.hh" StatBag S; +AuthDomainCache g_domainCache; AuthPacketCache PC; AuthQueryCache QC; ArgvMap& arg() diff --git a/modules/remotebackend/test-remotebackend-pipe.cc b/modules/remotebackend/test-remotebackend-pipe.cc index 11883b104e..a32dfd0019 100644 --- a/modules/remotebackend/test-remotebackend-pipe.cc +++ b/modules/remotebackend/test-remotebackend-pipe.cc @@ -40,10 +40,12 @@ #include "pdns/dnsrecords.hh" #include "pdns/json.hh" #include "pdns/statbag.hh" +#include "pdns/auth-domaincache.hh" #include "pdns/auth-packetcache.hh" #include "pdns/auth-querycache.hh" StatBag S; +AuthDomainCache g_domainCache; AuthPacketCache PC; AuthQueryCache QC; ArgvMap& arg() diff --git a/modules/remotebackend/test-remotebackend-post.cc b/modules/remotebackend/test-remotebackend-post.cc index 899b51dbb9..0f80df4851 100644 --- a/modules/remotebackend/test-remotebackend-post.cc +++ b/modules/remotebackend/test-remotebackend-post.cc @@ -31,10 +31,12 @@ #include "pdns/arguments.hh" #include "pdns/json.hh" #include "pdns/statbag.hh" +#include "pdns/auth-domaincache.hh" #include "pdns/auth-packetcache.hh" #include "pdns/auth-querycache.hh" StatBag S; +AuthDomainCache g_domainCache; AuthPacketCache PC; AuthQueryCache QC; ArgvMap& arg() diff --git a/modules/remotebackend/test-remotebackend-unix.cc b/modules/remotebackend/test-remotebackend-unix.cc index c2be2f3972..7a0142aa75 100644 --- a/modules/remotebackend/test-remotebackend-unix.cc +++ b/modules/remotebackend/test-remotebackend-unix.cc @@ -40,10 +40,12 @@ #include "pdns/dnsrecords.hh" #include "pdns/json.hh" #include "pdns/statbag.hh" +#include "pdns/auth-domaincache.hh" #include "pdns/auth-packetcache.hh" #include "pdns/auth-querycache.hh" StatBag S; +AuthDomainCache g_domainCache; AuthPacketCache PC; AuthQueryCache QC; ArgvMap& arg() diff --git a/modules/remotebackend/test-remotebackend-zeromq.cc b/modules/remotebackend/test-remotebackend-zeromq.cc index c5e5396edc..01fed7f67c 100644 --- a/modules/remotebackend/test-remotebackend-zeromq.cc +++ b/modules/remotebackend/test-remotebackend-zeromq.cc @@ -39,10 +39,12 @@ #include "pdns/dnsrecords.hh" #include "pdns/json.hh" #include "pdns/statbag.hh" +#include "pdns/auth-domaincache.hh" #include "pdns/auth-packetcache.hh" #include "pdns/auth-querycache.hh" StatBag S; +AuthDomainCache g_domainCache; AuthPacketCache PC; AuthQueryCache QC; ArgvMap& arg() diff --git a/pdns/Makefile.am b/pdns/Makefile.am index 97499ab224..c97adcf5fa 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -185,6 +185,7 @@ pdns_server_SOURCES = \ ascii.hh \ auth-caches.cc auth-caches.hh \ auth-carbon.cc \ + auth-domaincache.cc auth-domaincache.hh \ auth-packetcache.cc auth-packetcache.hh \ auth-querycache.cc auth-querycache.hh \ axfr-retriever.cc axfr-retriever.hh \ @@ -321,6 +322,7 @@ endif pdnsutil_SOURCES = \ arguments.cc \ auth-caches.cc auth-caches.hh \ + auth-domaincache.cc auth-domaincache.hh \ auth-packetcache.cc auth-packetcache.hh \ auth-querycache.cc auth-querycache.hh \ backends/gsql/gsqlbackend.cc backends/gsql/gsqlbackend.hh \ @@ -1304,6 +1306,7 @@ pdns.conf-dist: pdns_server testrunner_SOURCES = \ arguments.cc \ auth-caches.cc auth-caches.hh \ + auth-domaincache.cc auth-domaincache.hh \ auth-packetcache.cc auth-packetcache.hh \ auth-querycache.cc auth-querycache.hh \ base32.cc \ diff --git a/pdns/auth-domaincache.cc b/pdns/auth-domaincache.cc new file mode 100644 index 0000000000..d530272727 --- /dev/null +++ b/pdns/auth-domaincache.cc @@ -0,0 +1,131 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "auth-domaincache.hh" +#include "logger.hh" +#include "statbag.hh" +#include "arguments.hh" +#include "cachecleaner.hh" +extern StatBag S; + +AuthDomainCache::AuthDomainCache(size_t mapsCount): d_maps(mapsCount) +{ + S.declare("domain-cache-hit", "Number of hits on the domain cache"); + S.declare("domain-cache-miss", "Number of misses on the domain cache"); + S.declare("domain-cache-size", "Number of entries in the domain cache", StatType::gauge); + + d_statnumhit=S.getPointer("domain-cache-hit"); + d_statnummiss=S.getPointer("domain-cache-miss"); + d_statnumentries=S.getPointer("domain-cache-size"); + + d_ttl = 0; +} + +AuthDomainCache::~AuthDomainCache() +{ + try { + vector locks; + for(auto& mc : d_maps) { + locks.push_back(WriteLock(mc.d_mut)); + } + locks.clear(); + } + catch(...) { + } +} + +bool AuthDomainCache::getEntry(const DNSName &domain, int& zoneId) +{ + auto& mc = getMap(domain); + bool found = false; + { + ReadLock rl(mc.d_mut); + auto iter = mc.d_map.find(domain); + if (iter != mc.d_map.end()) { + found = true; + zoneId = iter->second.zoneId; + } + } + + if (found) { + (*d_statnumhit)++; + } else { + (*d_statnummiss)++; + } + return found; +} + +bool AuthDomainCache::isEnabled() const +{ + return d_ttl > 0; +} + +void AuthDomainCache::clear() +{ + purgeLockedCollectionsVector(d_maps); +} + +void AuthDomainCache::replace(const vector> &domain_indices) +{ + if(!d_ttl) + return; + + size_t count = domain_indices.size(); + vector newMaps(d_maps.size()); + + // build new maps + for(const tuple& tup: domain_indices) { + const DNSName& domain = tup.get<0>(); + CacheValue val; + val.zoneId = tup.get<1>(); + auto& mc = newMaps[getMapIndex(domain)]; + mc.d_map.emplace(domain, val); + } + + for(size_t mapIndex = 0; mapIndex < d_maps.size(); mapIndex++) + { + auto& mc = d_maps[mapIndex]; + WriteLock l(mc.d_mut); + mc.d_map = newMaps[mapIndex].d_map; + } + + d_statnumentries->store(count); +} + +void AuthDomainCache::add(const DNSName& domain, const int zoneId) +{ + if(!d_ttl) + return; + + CacheValue val; + val.zoneId = zoneId; + + int mapIndex = getMapIndex(domain); + { + auto& mc = d_maps[mapIndex]; + WriteLock l(mc.d_mut); + mc.d_map.emplace(domain, val); + } +} diff --git a/pdns/auth-domaincache.hh b/pdns/auth-domaincache.hh new file mode 100644 index 0000000000..bee0202f97 --- /dev/null +++ b/pdns/auth-domaincache.hh @@ -0,0 +1,93 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once +#include +#include +#include "dns.hh" + +#include + +#include "dns.hh" +#include "dnspacket.hh" +#include "lock.hh" + +class AuthDomainCache : public boost::noncopyable +{ +public: + AuthDomainCache(size_t mapsCount=1024); + ~AuthDomainCache(); + + void replace(const vector> &domains); + void add(const DNSName& domain, const int zoneId); + + bool getEntry(const DNSName &domain, int &zoneId); + + size_t size() { return *d_statnumentries; } //!< number of entries in the cache + + uint32_t getTTL() const { + return d_ttl; + } + + void setTTL(uint32_t ttl) { + d_ttl = ttl; + } + + bool isEnabled() const; + + void clear(); + +private: + struct CacheValue + { + int zoneId{-1}; + }; + + typedef std::unordered_map> cmap_t; + + struct MapCombo + { + MapCombo() { } + ~MapCombo() { } + MapCombo(const MapCombo &) = delete; + MapCombo & operator=(const MapCombo &) = delete; + + ReadWriteLock d_mut; + cmap_t d_map; + }; + + vector d_maps; + size_t getMapIndex(const DNSName& domain) + { + return domain.hash() % d_maps.size(); + } + MapCombo& getMap(const DNSName& qname) + { + return d_maps[getMapIndex(qname)]; + } + + AtomicCounter d_ops{0}; + AtomicCounter *d_statnumhit; + AtomicCounter *d_statnummiss; + AtomicCounter *d_statnumentries; + + time_t d_ttl; +}; diff --git a/pdns/backends/gsql/gsqlbackend.cc b/pdns/backends/gsql/gsqlbackend.cc index 0956808db0..5ad65bd863 100644 --- a/pdns/backends/gsql/gsqlbackend.cc +++ b/pdns/backends/gsql/gsqlbackend.cc @@ -1289,7 +1289,7 @@ bool GSQLBackend::superMasterBackend(const string &ip, const DNSName &domain, co return false; } -bool GSQLBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector &masters, const string &account) +bool GSQLBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector &masters, const string &account, int* zoneId) { vector masters_s; masters_s.reserve(masters.size()); @@ -1307,6 +1307,20 @@ bool GSQLBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKi bind("account", account)-> execute()-> reset(); + + if (zoneId != nullptr) { + // XXX: needs its own stmt as godbc has a table name in it + d_GetLastInsertedKeyIdQuery_stmt->execute(); + if (!d_GetLastInsertedKeyIdQuery_stmt->hasNextRow()) { + return false; + } + SSqlStatement::row_t row; + d_GetLastInsertedKeyIdQuery_stmt->nextRow(row); + ASSERT_ROW_COLUMNS("get-last-inserted-key-id-query", row, 1); + *zoneId = std::stoi(row[0]); + d_GetLastInsertedKeyIdQuery_stmt->reset(); + } + return true; } catch(SSqlException &e) { throw PDNSException("Database error trying to insert new domain '"+domain.toLogString()+"': "+ e.txtReason()); @@ -1340,7 +1354,7 @@ bool GSQLBackend::createSlaveDomain(const string &ip, const DNSName &domain, con masters = tmp; } } - createDomain(domain, DomainInfo::Slave, masters, account); + createDomain(domain, DomainInfo::Slave, masters, account, nullptr); } catch(SSqlException &e) { throw PDNSException("Database error trying to insert new slave domain '"+domain.toLogString()+"': "+ e.txtReason()); diff --git a/pdns/backends/gsql/gsqlbackend.hh b/pdns/backends/gsql/gsqlbackend.hh index 0aa79f54a6..396ff7e0c7 100644 --- a/pdns/backends/gsql/gsqlbackend.hh +++ b/pdns/backends/gsql/gsqlbackend.hh @@ -193,7 +193,7 @@ public: bool feedRecord(const DNSResourceRecord &r, const DNSName &ordername, bool ordernameIsNSEC3=false) override; bool feedEnts(int domain_id, map& nonterm) override; bool feedEnts3(int domain_id, const DNSName &domain, map &nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow) override; - bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector &masters, const string &account) override; + bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector &masters, const string &account, int* zoneId=nullptr) override; bool createSlaveDomain(const string &ip, const DNSName &domain, const string &nameserver, const string &account) override; bool deleteDomain(const DNSName &domain) override; bool superMasterAdd(const string &ip, const string &nameserver, const string &account) override; diff --git a/pdns/common_startup.cc b/pdns/common_startup.cc index 60c81cad96..111a75a39c 100644 --- a/pdns/common_startup.cc +++ b/pdns/common_startup.cc @@ -55,6 +55,7 @@ ArgvMap theArg; StatBag S; //!< Statistics are gathered across PDNS via the StatBag class S AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads AuthQueryCache QC; +AuthDomainCache g_domainCache; std::unique_ptr DP{nullptr}; std::unique_ptr dl{nullptr}; CommunicatorClass Communicator; @@ -180,6 +181,7 @@ void declareArguments() ::arg().set("cache-ttl","Seconds to store packets in the PacketCache")="20"; ::arg().set("negquery-cache-ttl","Seconds to store negative query results in the QueryCache")="60"; ::arg().set("query-cache-ttl","Seconds to store query results in the QueryCache")="20"; + ::arg().set("domain-cache-ttl","Seconds to cache list of known domains")="0"; ::arg().set("server-id", "Returned when queried for 'id.server' TXT or NSID, defaults to hostname - disabled or custom")=""; ::arg().set("default-soa-content","Default SOA content")="a.misconfigured.dns.server.invalid hostmaster.@ 0 10800 3600 604800 3600"; ::arg().set("default-soa-edit","Default SOA-EDIT value")=""; @@ -676,12 +678,28 @@ void mainthread() sd_notify(0, "READY=1"); #endif + const uint32_t secpollInterval = 1800; + uint32_t secpollSince = 0; + uint32_t domainCacheUpdateSince = 0; for(;;) { - sleep(1800); - try { - doSecPoll(false); + const uint32_t slept = g_domainCache.getTTL() == 0 ? secpollInterval : std::min(secpollInterval, g_domainCache.getTTL()); + sleep(slept); // if any signals arrive, we might run more often than expected. + + domainCacheUpdateSince += slept; + if (domainCacheUpdateSince >= g_domainCache.getTTL()) { + domainCacheUpdateSince = 0; + UeberBackend B; + B.updateDomainCache(); + } + + secpollSince += slept; + if (secpollSince >= secpollInterval) { + secpollSince = 0; + try { + doSecPoll(false); + } + catch(...){} } - catch(...){} } g_log< DP; extern std::unique_ptr dl; extern CommunicatorClass Communicator; diff --git a/pdns/dnsbackend.hh b/pdns/dnsbackend.hh index f200a0526a..ee9ddca5e9 100644 --- a/pdns/dnsbackend.hh +++ b/pdns/dnsbackend.hh @@ -356,7 +356,7 @@ public: } //! called by PowerDNS to create a new domain - virtual bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector &masters, const string &account) + virtual bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector &masters, const string &account, int* zoneId = nullptr) { return false; } diff --git a/pdns/pdnsutil.cc b/pdns/pdnsutil.cc index 6d6dcd33cb..c2a7ac1f10 100644 --- a/pdns/pdnsutil.cc +++ b/pdns/pdnsutil.cc @@ -18,6 +18,7 @@ #include "dnsbackend.hh" #include "ueberbackend.hh" #include "arguments.hh" +#include "auth-domaincache.hh" #include "auth-packetcache.hh" #include "auth-querycache.hh" #include "zoneparser-tng.hh" @@ -40,6 +41,7 @@ StatBag S; AuthPacketCache PC; AuthQueryCache QC; +AuthDomainCache g_domainCache; namespace po = boost::program_options; po::variables_map g_vm; diff --git a/pdns/receiver.cc b/pdns/receiver.cc index dcc8051a24..edadbbc3ab 100644 --- a/pdns/receiver.cc +++ b/pdns/receiver.cc @@ -624,6 +624,12 @@ int main(int argc, char **argv) } } + g_domainCache.setTTL(::arg().asNum("domain-cache-ttl")); + { + UeberBackend B; + B.updateDomainCache(); + } + UeberBackend::go(); N=std::make_shared(); // this fails when we are not root, throws exception g_udpReceivers.push_back(N); diff --git a/pdns/test-ueberbackend_cc.cc b/pdns/test-ueberbackend_cc.cc index 3921b02fa7..c8fc32708e 100644 --- a/pdns/test-ueberbackend_cc.cc +++ b/pdns/test-ueberbackend_cc.cc @@ -16,6 +16,7 @@ #include #include "arguments.hh" +#include "auth-domaincache.hh" #include "auth-querycache.hh" #include "ueberbackend.hh" @@ -332,11 +333,14 @@ public: struct UeberBackendSetupArgFixture { UeberBackendSetupArgFixture() { + extern AuthDomainCache g_domainCache; extern AuthQueryCache QC; ::arg().set("query-cache-ttl")="0"; ::arg().set("negquery-cache-ttl")="0"; ::arg().set("consistent-backends")="no"; QC.cleanup(); + ::arg().set("domain-cache-ttl")="0"; + g_domainCache.clear(); BackendMakers().clear(); SimpleBackend::s_zones.clear(); SimpleBackend::s_metadata.clear(); @@ -345,6 +349,7 @@ struct UeberBackendSetupArgFixture { static void testWithoutThenWithCache(std::function func) { + extern AuthDomainCache g_domainCache; extern AuthQueryCache QC; { @@ -352,6 +357,8 @@ static void testWithoutThenWithCache(std::function func) ::arg().set("query-cache-ttl")="0"; ::arg().set("negquery-cache-ttl")="0"; QC.cleanup(); + ::arg().set("domain-cache-ttl")="0"; + g_domainCache.clear(); UeberBackend ub; func(ub); @@ -362,8 +369,11 @@ static void testWithoutThenWithCache(std::function func) ::arg().set("query-cache-ttl")="20"; ::arg().set("negquery-cache-ttl")="60"; QC.cleanup(); + ::arg().set("domain-cache-ttl")="60"; + g_domainCache.clear(); UeberBackend ub; + ub.updateDomainCache(); /* a first time to fill the cache */ func(ub); /* a second time to make sure every call has been tried with the cache filled */ diff --git a/pdns/testrunner.cc b/pdns/testrunner.cc index 7db0337ae2..91b481169d 100644 --- a/pdns/testrunner.cc +++ b/pdns/testrunner.cc @@ -5,10 +5,12 @@ #endif #include #include "arguments.hh" +#include "auth-domaincache.hh" #include "auth-packetcache.hh" #include "auth-querycache.hh" #include "statbag.hh" StatBag S; +AuthDomainCache g_domainCache; AuthPacketCache PC; AuthQueryCache QC; diff --git a/pdns/ueberbackend.cc b/pdns/ueberbackend.cc index 8dc7382658..e34b875066 100644 --- a/pdns/ueberbackend.cc +++ b/pdns/ueberbackend.cc @@ -26,6 +26,7 @@ #include #include "auth-querycache.hh" +#include "auth-domaincache.hh" #include "utility.hh" @@ -47,6 +48,7 @@ #include "logger.hh" #include "statbag.hh" +extern AuthDomainCache g_domainCache; extern StatBag S; vectorUeberBackend::instances; @@ -120,12 +122,18 @@ bool UeberBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool get bool UeberBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector &masters, const string &account) { + bool success = false; + int zoneId; for(DNSBackend* mydb : backends) { - if(mydb->createDomain(domain, kind, masters, account)) { - return true; + if(mydb->createDomain(domain, kind, masters, account, &zoneId)) { + success = true; + break; } } - return false; + if (success) { + g_domainCache.add(domain, zoneId); // make new domain visible + } + return success; } bool UeberBackend::doesDNSSEC() @@ -273,9 +281,26 @@ void UeberBackend::reload() } } +void UeberBackend::updateDomainCache() { + if (!g_domainCache.isEnabled()) { + return; + } + + vector> domain_indices; + + for (vector::iterator i = backends.begin(); i != backends.end(); ++i ) + { + vector domains; + (*i)->getAllDomains(&domains, false); + for(const auto& di: domains) { + domain_indices.push_back({di.zone, (int)di.id}); // this cast should not be necessary + } + } + g_domainCache.replace(domain_indices); +} + void UeberBackend::rediscover(string *status) { - for ( vector< DNSBackend * >::iterator i = backends.begin(); i != backends.end(); ++i ) { string tmpstr; @@ -283,6 +308,8 @@ void UeberBackend::rediscover(string *status) if(status) *status+=tmpstr + (i!=backends.begin() ? "\n" : ""); } + + updateDomainCache(); } @@ -322,20 +349,39 @@ bool UeberBackend::getAuth(const DNSName &target, const QType& qtype, SOAData* s // of them has a more specific zone but don't bother asking this specific // backend again for b.c.example.com., c.example.com. and example.com. // If a backend has no match it may respond with an empty qname. - bool found = false; int cstat; DNSName shorter(target); vector > bestmatch (backends.size(), make_pair(target.wirelength()+1, SOAData())); do { + int zoneId{-1}; + if(cachedOk && g_domainCache.isEnabled()) { + if (g_domainCache.getEntry(shorter, zoneId)) { + // Zone exists in domain cache, directly lookup SOA. + // XXX: this code path and the cache lookup below should be merged; but that needs the code path below to also use ANY. + // Or it should just also use lookup(). + DNSZoneRecord zr; + lookup(QType(QType::SOA), shorter, zoneId, nullptr); + if (!get(zr)) { + throw PDNSException("Backend returned no SOA for existing domain '"+shorter.toLogString()+"'"); + } + sd->qname = zr.dr.d_name; + fillSOAData(zr, *sd); + // leave database handle in a consistent state + while (get(zr)) + ; + goto found; + } + // domain does not exist, try again with shorter name + continue; + } d_question.qtype = QType::SOA; d_question.qname = shorter; - d_question.zoneId = -1; + d_question.zoneId = zoneId; // Check cache if(cachedOk && (d_cache_ttl || d_negcache_ttl)) { - cstat = cacheHas(d_question,d_answers); if(cstat == 1 && !d_answers.empty() && d_cache_ttl) { @@ -400,7 +446,7 @@ bool UeberBackend::getAuth(const DNSName &target, const QType& qtype, SOAData* s DLOG(g_log<qname<qname; - d_question.zoneId = -1; + d_question.zoneId = zoneId; DNSZoneRecord rr; rr.dr.d_name = sd->qname; diff --git a/pdns/ueberbackend.hh b/pdns/ueberbackend.hh index 873b098f16..92963176f1 100644 --- a/pdns/ueberbackend.hh +++ b/pdns/ueberbackend.hh @@ -98,7 +98,6 @@ public: /** Determines if we are authoritative for a zone, and at what level */ bool getAuth(const DNSName &target, const QType &qtype, SOAData* sd, bool cachedOk=true); - /** Load SOA info from backends, ignoring the cache.*/ bool getSOAUncached(const DNSName &domain, SOAData &sd); bool get(DNSZoneRecord &r); @@ -133,6 +132,8 @@ public: bool searchRecords(const string &pattern, int maxResults, vector& result); bool searchComments(const string &pattern, int maxResults, vector& result); + void updateDomainCache(); + bool inTransaction(); private: handle d_handle; @@ -162,5 +163,4 @@ private: int cacheHas(const Question &q, vector &rrs); void addNegCache(const Question &q); void addCache(const Question &q, vector&& rrs); - }; diff --git a/regression-tests/backends/ldap-master b/regression-tests/backends/ldap-master index 7260c10163..c607fdf706 100644 --- a/regression-tests/backends/ldap-master +++ b/regression-tests/backends/ldap-master @@ -31,6 +31,7 @@ __EOF__ --query-logging --dnsupdate=yes \ --expand-alias=yes --outgoing-axfr-expand-alias=yes \ --resolver=$RESOLVERIP \ + --domain-cache-ttl=0 \ --cache-ttl=$cachettl --dname-processing $lua_prequery & skipreasons="nodnssec noent nodyndns nometa noaxfr" diff --git a/regression-tests/tests/bind-add-zone/command b/regression-tests/tests/bind-add-zone/command index bb3bc17dba..1462e274de 100755 --- a/regression-tests/tests/bind-add-zone/command +++ b/regression-tests/tests/bind-add-zone/command @@ -7,7 +7,8 @@ fi cleandig ns1.addzone.com A cleandig ns1.test.com A $PDNSCONTROL --config-name=bind --socket-dir=. --no-config bind-add-zone addzone.com ${PWD}/zones/addzone.com >/dev/null 2>&1 -$PDNSCONTROL --config-name=bind --socket-dir=. --no-config purge addzone.com +# output of purge changes if domain-cache-ttl is set +$PDNSCONTROL --config-name=bind --socket-dir=. --no-config purge addzone.com >/dev/null sleep 1 $PDNSCONTROL --config-name=bind --socket-dir=. --no-config bind-add-zone addzone.com ${PWD}/zones/addzone.com sleep 1 diff --git a/regression-tests/tests/bind-add-zone/expected_result.bind b/regression-tests/tests/bind-add-zone/expected_result.bind index 845856c472..5650c31b21 100644 --- a/regression-tests/tests/bind-add-zone/expected_result.bind +++ b/regression-tests/tests/bind-add-zone/expected_result.bind @@ -3,7 +3,6 @@ Reply to question for qname='ns1.addzone.com.', qtype=A 0 ns1.test.com. IN A 3600 1.1.1.1 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='ns1.test.com.', qtype=A -1 Already loaded 0 ns1.addzone.com. IN A 3600 1.1.1.5 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 -- 2.47.2