]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth: Add unit tests for the UeberBackend
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 27 May 2020 08:25:06 +0000 (10:25 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 10 Aug 2020 09:34:11 +0000 (11:34 +0200)
20 files changed:
modules/bindbackend/bindbackend2.cc
modules/geoipbackend/geoipbackend.cc
modules/gmysqlbackend/gmysqlbackend.cc
modules/godbcbackend/godbcbackend.cc
modules/gpgsqlbackend/gpgsqlbackend.cc
modules/gsqlite3backend/gsqlite3backend.cc
modules/ldapbackend/ldapbackend.cc
modules/lmdbbackend/lmdbbackend.cc
modules/lua2backend/lua2backend.cc
modules/pipebackend/pipebackend.cc
modules/randombackend/randombackend.cc
modules/remotebackend/remotebackend.cc
modules/tinydnsbackend/tinydnsbackend.cc
pdns/Makefile.am
pdns/dnsbackend.cc
pdns/dnsbackend.hh
pdns/pdnsutil.cc
pdns/test-ueberbackend_cc.cc [new file with mode: 0644]
pdns/ueberbackend.cc
pdns/ueberbackend.hh

index a882614323f73631d157710f3232744cb47ab6ac..d29345c12c1c902e8035b5df6b16f0b071d0f390 100644 (file)
@@ -1443,7 +1443,7 @@ class Bind2Factory : public BackendFactory
    public:
       Bind2Factory() : BackendFactory("bind") {}
 
-      void declareArguments(const string &suffix="")
+      void declareArguments(const string &suffix="") override
       {
          declare(suffix,"ignore-broken-records","Ignore records that are out-of-bound for the zone.","no");
          declare(suffix,"config","Location of named.conf","");
@@ -1456,13 +1456,13 @@ class Bind2Factory : public BackendFactory
          declare(suffix,"hybrid","Store DNSSEC metadata in other backend","no");
       }
 
-      DNSBackend *make(const string &suffix="")
+      DNSBackend *make(const string &suffix="") override
       {
          assertEmptySuffix(suffix);
          return new Bind2Backend(suffix);
       }
       
-      DNSBackend *makeMetadataOnly(const string &suffix="")
+      DNSBackend *makeMetadataOnly(const string &suffix="") override
       {
         assertEmptySuffix(suffix);
         return new Bind2Backend(suffix, false);
index 612c9a5298a7605ef1bcce5d784f0ebb7ce092cd..7b66211810155fde8349ce54a22cf4000a1e58a6 100644 (file)
@@ -929,13 +929,13 @@ class GeoIPFactory : public BackendFactory{
 public:
   GeoIPFactory() : BackendFactory("geoip") {}
 
-  void declareArguments(const string &suffix = "") {
+  void declareArguments(const string &suffix = "") override {
     declare(suffix, "zones-file", "YAML file to load zone(s) configuration", "");
     declare(suffix, "database-files", "File(s) to load geoip data from ([driver:]path[;opt=value]", "");
     declare(suffix, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
   }
 
-  DNSBackend *make(const string &suffix) {
+  DNSBackend *make(const string &suffix) override {
     return new GeoIPBackend(suffix);
   }
 };
index ef80c9d88a55ec7c40da17db4ee301fcc61f64ba..5a3e93a34f9f72c11a06132b3be7ba963ab1627e 100644 (file)
@@ -69,7 +69,7 @@ class gMySQLFactory : public BackendFactory
 public:
   gMySQLFactory(const string &mode) : BackendFactory(mode),d_mode(mode) {}
 
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"dbname","Database name to connect to","powerdns");
     declare(suffix,"user","Database backend user to connect as","powerdns");
@@ -159,7 +159,7 @@ public:
     declare(suffix, "search-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE name LIKE ? OR comment LIKE ? LIMIT ?");
   }
 
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     return new gMySQLBackend(d_mode,suffix);
   }
index 02139c24c4b0cb7fc959b2b828d18cb37c777b39..e9ada2e91d2a6a76fbec16141097c4e1ad22b4ae 100644 (file)
@@ -61,7 +61,7 @@ public:
   }
 
   //! Declares all needed arguments.
-  void declareArguments( const std::string & suffix = "" )
+  void declareArguments( const std::string & suffix = "" ) override
   {
     declare( suffix, "datasource", "Datasource (DSN) to use","PowerDNS");
     declare( suffix, "username", "User to connect as","powerdns");
@@ -146,7 +146,7 @@ public:
   }
 
   //! Constructs a new gODBCBackend object.
-  DNSBackend *make(const string & suffix = "" )
+  DNSBackend *make(const string & suffix = "" ) override
   {
     return new gODBCBackend( d_mode, suffix );
   }
index 53833ee65a2a6e2b505bca118b2beff0377d530d..699d8ebfce726dca06d262c8026cd88436d6283b 100644 (file)
@@ -80,7 +80,7 @@ class gPgSQLFactory : public BackendFactory
 public:
   gPgSQLFactory(const string &mode) : BackendFactory(mode),d_mode(mode) {}
 
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"dbname","Backend database name to connect to","");
     declare(suffix,"user","Database backend user to connect as","");
@@ -167,7 +167,7 @@ public:
 
   }
 
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     return new gPgSQLBackend(d_mode,suffix);
   }
index d697b83bd5cc5460bcad96c9945148a89b02494b..37dee9228926f6eee960311d7ab2ff320c1468bd 100644 (file)
@@ -72,7 +72,7 @@ public:
   }
   
   //! Declares all needed arguments.
-  void declareArguments( const std::string & suffix = "" )
+  void declareArguments( const std::string & suffix = "" ) override
   {
     declare(suffix, "database", "Filename of the SQLite3 database", "powerdns.sqlite");
     declare(suffix, "pragma-synchronous", "Set this to 0 for blazing speed", "");
@@ -156,7 +156,7 @@ public:
   }
 
   //! Constructs a new gSQLite3Backend object.
-  DNSBackend *make( const string & suffix = "" )
+  DNSBackend *make( const string & suffix = "" ) override
   {
     return new gSQLite3Backend( d_mode, suffix );
   }
index 7078bf5513e48fa03426eb0300da7024e8b816f2..f428a1adf0c311a24068dc3cf1dcf7ea2311361e 100644 (file)
@@ -277,7 +277,7 @@ class LdapFactory : public BackendFactory
 
     LdapFactory() : BackendFactory( "ldap" ) {}
 
-    void declareArguments( const string &suffix="" )
+    void declareArguments( const string &suffix="" ) override
     {
       declare( suffix, "host", "One or more LDAP server with ports or LDAP URIs (separated by spaces)","ldap://127.0.0.1:389/" );
       declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" );
@@ -297,7 +297,7 @@ class LdapFactory : public BackendFactory
     }
 
 
-    DNSBackend* make( const string &suffix="" )
+    DNSBackend* make( const string &suffix="" ) override
     {
       return new LdapBackend( suffix );
     }
index b768905076869fbc48b0769548ac0bd1f87eb18e..95985034a4ef9ef4804f3f639f8167c62ce956c1 100644 (file)
@@ -1558,7 +1558,7 @@ class LMDBFactory : public BackendFactory
 {
 public:
   LMDBFactory() : BackendFactory("lmdb") {}
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"filename","Filename for lmdb","./pdns.lmdb");
     declare(suffix,"sync-mode","Synchronisation mode: nosync, nometasync, mapasync, sync","mapasync");
@@ -1566,7 +1566,7 @@ public:
     declare(suffix,"shards","Records database will be split into this number of shards", (sizeof(long) == 4) ? "2" : "64");
     declare(suffix,"schema-version","Maximum allowed schema version to run on this DB. If a lower version is found, auto update is performed", SCHEMAVERSION_TEXT); 
   }
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     return new LMDBBackend(suffix);
   }
index 378203eccdac2c8418b5f3267678e777463468da..b3ce01e0d5f8e5b2c5485d242aaf22814397f7bd 100644 (file)
@@ -31,14 +31,14 @@ class Lua2Factory : public BackendFactory
 public:
   Lua2Factory() : BackendFactory("lua2") {}
 
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"filename","Filename of the script for lua backend","powerdns-luabackend.lua");
     declare(suffix,"query-logging","Logging of the Lua2 Backend","no");
     declare(suffix,"api","Lua backend API version","2");
   }
 
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     const std::string apiSet = "lua2" + suffix + "-api";
     const int api = ::arg().asNum(apiSet);
index 103d39e1f1a6bcc7279f7c8c79bbd4e7f52f09dc..208ef2e5d4f0987006db807f19dd98a1ad1a88ec 100644 (file)
@@ -357,7 +357,7 @@ class PipeFactory : public BackendFactory
    public:
       PipeFactory() : BackendFactory("pipe") {}
 
-      void declareArguments(const string &suffix="")
+      void declareArguments(const string &suffix="") override
       {
          declare(suffix,"command","Command to execute for piping questions to","");
          declare(suffix,"timeout","Number of milliseconds to wait for an answer","2000");
@@ -365,7 +365,7 @@ class PipeFactory : public BackendFactory
          declare(suffix,"abi-version","Version of the pipe backend ABI","1");
       }
 
-      DNSBackend *make(const string &suffix="")
+      DNSBackend *make(const string &suffix="") override
       {
          return new PipeBackend(suffix);
       }
index 577ba63904c5ce464dd91353e2892626aa8b7192..20b179b8d3a2df721e8b1a1e658bb52f1299f531 100644 (file)
@@ -102,11 +102,11 @@ class RandomFactory : public BackendFactory
 {
 public:
   RandomFactory() : BackendFactory("random") {}
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"hostname","Hostname which is to be random","random.example.com");
   }
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     return new RandomBackend(suffix);
   }
index 77e74ab893330c093e21545a09055ef7e6975e6b..220b518f0e0a73a5a820ffc5b54a3121a656335d 100644 (file)
@@ -1036,13 +1036,13 @@ class RemoteBackendFactory : public BackendFactory
   public:
       RemoteBackendFactory() : BackendFactory("remote") {}
 
-      void declareArguments(const std::string &suffix="")
+      void declareArguments(const std::string &suffix="") override
       {
           declare(suffix,"dnssec","Enable dnssec support","no");
           declare(suffix,"connection-string","Connection string","");
       }
 
-      DNSBackend *make(const std::string &suffix="")
+      DNSBackend *make(const std::string &suffix="") override
       {
          return new RemoteBackend(suffix);
       }
index 3f77b0e2f8a76816c771300278e6c21b73be1abe..1de99d5e0232fef5b15401ab76efa1bd43bbd977 100644 (file)
@@ -348,7 +348,7 @@ class TinyDNSFactory: public BackendFactory
 public:
   TinyDNSFactory() : BackendFactory("tinydns") {}
 
-  void declareArguments(const string &suffix="") {
+  void declareArguments(const string &suffix="") override {
     declare(suffix, "notify-on-startup", "Tell the TinyDNSBackend to notify all the slave nameservers on startup. Default is no.", "no");
     declare(suffix, "dbfile", "Location of the cdb data file", "data.cdb");
     declare(suffix, "tai-adjust", "This adjusts the TAI value if timestamps are used. These seconds will be added to the start point (1970) and will allow you to adjust for leap seconds. The default is 11.", "11");
@@ -356,7 +356,7 @@ public:
     declare(suffix, "ignore-bogus-records", "The data.cdb file might have some incorrect record data, this causes PowerDNS to fail, where tinydns would send out truncated data. This option makes powerdns ignore that data!", "no");
   }
 
-  DNSBackend *make(const string &suffix="") {
+  DNSBackend *make(const string &suffix="") override {
     return new TinyDNSBackend(suffix);
   }
 };
index bde0e29c5dd7ad4216afec452e48b8397c1c48f0..ff2dc1058f53dab449983d2784b1595f8974c111 100644 (file)
@@ -1343,11 +1343,12 @@ testrunner_SOURCES = \
        test-sha_hh.cc \
        test-statbag_cc.cc \
        test-tsig.cc \
+       test-ueberbackend_cc.cc \
        test-zoneparser_tng_cc.cc \
        testrunner.cc \
        threadname.hh threadname.cc \
        tsigverifier.cc tsigverifier.hh \
-       ueberbackend.cc \
+       ueberbackend.cc ueberbackend.hh \
        unix_utility.cc \
        zoneparser-tng.cc zoneparser-tng.hh
 
index 5869df3be1ba45cb28a570e5b4c8de8df64646e2..5c8d2950291d9f901277ab3d0c282d62b8aaee4b 100644 (file)
@@ -85,6 +85,15 @@ void BackendMakerClass::report(BackendFactory *bf)
   d_repository[bf->getName()]=bf;
 }
 
+void BackendMakerClass::clear()
+{
+  d_instances.clear();
+  for (auto& repo : d_repository) {
+    delete repo.second;
+    repo.second = nullptr;
+  }
+  d_repository.clear();
+}
 
 vector<string> BackendMakerClass::getModules()
 {
@@ -164,41 +173,50 @@ void BackendMakerClass::launch(const string &instr)
   }
 }
 
-int BackendMakerClass::numLauncheable()
+size_t BackendMakerClass::numLauncheable() const
 {
   return d_instances.size();
 }
 
-vector<DNSBackend *>BackendMakerClass::all(bool metadataOnly)
+vector<DNSBackend *> BackendMakerClass::all(bool metadataOnly)
 {
-  vector<DNSBackend *>ret;
+  vector<DNSBackend *> ret;
   if(d_instances.empty())
     throw PDNSException("No database backends configured for launch, unable to function");
 
+  ret.reserve(d_instances.size());
+
   try {
-    for(vector<pair<string,string> >::const_iterator i=d_instances.begin();i!=d_instances.end();++i) {
-      DNSBackend *made;
-      if(metadataOnly)
-        made = d_repository[i->first]->makeMetadataOnly(i->second);
-      else
-        made = d_repository[i->first]->make(i->second);
-      if(!made)
-        throw PDNSException("Unable to launch backend '"+i->first+"'");
+    for (const auto& instance : d_instances) {
+      DNSBackend *made = nullptr;
+
+      if (metadataOnly) {
+        made = d_repository[instance.first]->makeMetadataOnly(instance.second);
+      }
+      else {
+        made = d_repository[instance.first]->make(instance.second);
+      }
+
+      if (!made) {
+        throw PDNSException("Unable to launch backend '" + instance.first + "'");
+      }
 
       ret.push_back(made);
     }
   }
-  catch(PDNSException &ae) {
+  catch(const PDNSException &ae) {
     g_log<<Logger::Error<<"Caught an exception instantiating a backend: "<<ae.reason<<endl;
     g_log<<Logger::Error<<"Cleaning up"<<endl;
-    for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i)
-      delete *i;
+    for (auto i : ret) {
+      delete i;
+    }
     throw;
   } catch(...) {
     // and cleanup
     g_log<<Logger::Error<<"Caught an exception instantiating a backend, cleaning up"<<endl;
-    for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i)
-      delete *i;
+    for (auto i : ret) {
+      delete i;
+    }
     throw;
   }
 
index b599dca3919120c6a9c3086b84e5aa400e121439..479aa5cba429988b8ddf0039732eba1e8ad5732c 100644 (file)
@@ -412,10 +412,11 @@ class BackendMakerClass
 public:
   void report(BackendFactory *bf);
   void launch(const string &instr);
-  vector<DNSBackend *>all(bool skipBIND=false);
+  vector<DNSBackend *> all(bool skipBIND=false);
   void load(const string &module);
-  int numLauncheable();
+  size_t numLauncheable() const;
   vector<string> getModules();
+  void clear();
 
 private:
   void load_all();
index ce1fc7562568bce965b5b10d63a02f32c8856510..6f36fc8522535da1699212bdc0cb76a2791b5fb0 100644 (file)
@@ -2,6 +2,9 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+
+#include <fcntl.h>
+
 #include "dnsseckeeper.hh"
 #include "dnssecinfra.hh"
 #include "statbag.hh"
diff --git a/pdns/test-ueberbackend_cc.cc b/pdns/test-ueberbackend_cc.cc
new file mode 100644 (file)
index 0000000..2ca4a56
--- /dev/null
@@ -0,0 +1,1126 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <unordered_map>
+
+#include <boost/test/unit_test.hpp>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+
+#include "arguments.hh"
+#include "auth-querycache.hh"
+#include "ueberbackend.hh"
+
+class SimpleBackend : public DNSBackend
+{
+public:
+  struct SimpleDNSRecord
+  {
+    SimpleDNSRecord(const DNSName& name, uint16_t type, const std::string& content, uint32_t ttl): d_content(content), d_name(name), d_ttl(ttl), d_type(type)
+    {
+    }
+
+    std::string d_content;
+    DNSName d_name;
+    uint32_t d_ttl;
+    uint16_t d_type;
+  };
+
+  struct OrderedNameTypeTag;
+
+  typedef multi_index_container<
+    SimpleDNSRecord,
+    indexed_by <
+      ordered_non_unique<tag<OrderedNameTypeTag>,
+                         composite_key<
+                           SimpleDNSRecord,
+                           member<SimpleDNSRecord, DNSName, &SimpleDNSRecord::d_name>,
+                           member<SimpleDNSRecord, uint16_t, &SimpleDNSRecord::d_type>
+                           >,
+                         composite_key_compare<CanonDNSNameCompare, std::less<uint16_t> >
+                         >
+      >
+    > RecordStorage;
+
+  struct SimpleDNSZone
+  {
+    SimpleDNSZone(const DNSName& name, uint64_t id): d_records(std::make_shared<RecordStorage>()), d_name(name), d_id(id)
+    {
+    }
+    std::shared_ptr<RecordStorage> d_records;
+    DNSName d_name;
+    uint64_t d_id;
+  };
+
+  struct HashedNameTag {};
+  struct IDTag {};
+
+  typedef multi_index_container<
+    SimpleDNSZone,
+    indexed_by <
+      ordered_unique<tag<IDTag>, member<SimpleDNSZone, uint64_t, &SimpleDNSZone::d_id> >,
+      hashed_unique<tag<HashedNameTag>, member<SimpleDNSZone, DNSName, &SimpleDNSZone::d_name> >
+      >
+    > ZoneStorage;
+
+  struct SimpleMetaData
+  {
+    SimpleMetaData(const DNSName& name, const std::string& kind, const std::vector<std::string>& values): d_name(name), d_kind(kind), d_values(values)
+    {
+    }
+
+    DNSName d_name;
+    std::string d_kind;
+    std::vector<std::string> d_values;
+  };
+
+  struct OrderedNameKindTag {};
+
+  typedef multi_index_container<
+    SimpleMetaData,
+    indexed_by <
+      ordered_unique<tag<OrderedNameKindTag>,
+                     composite_key<
+                       SimpleMetaData,
+                       member<SimpleMetaData, DNSName, &SimpleMetaData::d_name>,
+                       member<SimpleMetaData, std::string, &SimpleMetaData::d_kind>
+                       >,
+                     composite_key_compare<CanonDNSNameCompare, std::less<std::string> >
+                     >
+      >
+    > MetaDataStorage;
+
+  // Initialize our backend ID from the suffix, skipping the '-' that DNSBackend adds there
+  SimpleBackend(const std::string& suffix): d_suffix(suffix), d_backendId(pdns_stou(suffix.substr(1)))
+  {
+  }
+
+  bool findZone(const DNSName& qdomain, int zoneId, std::shared_ptr<RecordStorage>& records, uint64_t& currentZoneId) const
+  {
+    currentZoneId = -1;
+    records.reset();
+
+    if (zoneId != -1) {
+      const auto& idx = boost::multi_index::get<IDTag>(s_zones.at(d_backendId));
+      auto it = idx.find(zoneId);
+      if (it == idx.end()) {
+        return false;
+      }
+      records = it->d_records;
+      currentZoneId = it->d_id;
+    }
+    else {
+      const auto& idx = boost::multi_index::get<HashedNameTag>(s_zones.at(d_backendId));
+      auto it = idx.find(qdomain);
+      if (it == idx.end()) {
+        return false;
+      }
+      records = it->d_records;
+      currentZoneId = it->d_id;
+    }
+
+    return true;
+  }
+
+  void lookup(const QType& qtype, const DNSName& qdomain, int zoneId = -1, DNSPacket *pkt_p = nullptr) override
+  {
+    d_currentScopeMask = 0;
+    findZone(qdomain, zoneId, d_records, d_currentZone);
+
+    if (d_records) {
+      if (qdomain == DNSName("geo.powerdns.com.") && pkt_p != nullptr && pkt_p->getRealRemote() == Netmask("192.0.2.1")) {
+        d_currentScopeMask = 32;
+      }
+
+      auto& idx = d_records->get<OrderedNameTypeTag>();
+      if (qtype == QType::ANY) {
+        auto range = idx.equal_range(qdomain);
+        d_iter = range.first;
+        d_end = range.second;
+      }
+      else {
+        auto range = idx.equal_range(boost::make_tuple(qdomain, qtype.getCode()));
+        d_iter = range.first;
+        d_end = range.second;
+      }
+    }
+  }
+
+  bool get(DNSResourceRecord& drr) override
+  {
+    if (!d_records) {
+      return false;
+    }
+
+    if (d_iter == d_end) {
+      return false;
+    }
+
+    drr.qname = d_iter->d_name;
+    drr.domain_id = d_currentZone;
+    drr.content = d_iter->d_content;
+    drr.qtype = d_iter->d_type;
+    drr.ttl = d_iter->d_ttl;
+
+    // drr.auth = d_iter->auth; might bring pain at some point, let's not cross that bridge until then
+    drr.auth = true;
+    drr.scopeMask = d_currentScopeMask;
+
+    ++d_iter;
+    return true;
+  }
+
+  bool list(const DNSName& target, int zoneId, bool include_disabled = false) override
+  {
+    findZone(target, zoneId, d_records, d_currentZone);
+
+    if (d_records) {
+      d_iter = d_records->begin();
+      d_end = d_records->end();
+      return true;
+    }
+
+    return false;
+  }
+
+  bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override
+  {
+    const auto& idx = boost::multi_index::get<OrderedNameKindTag>(s_metadata.at(d_backendId));
+    auto it = idx.find(boost::make_tuple(name, kind));
+    if (it == idx.end()) {
+      /* funnily enough, we are expected to return true even though we might not know that zone */
+      return true;
+    }
+
+    meta = it->d_values;
+    return true;
+  }
+
+  bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override
+  {
+    auto& idx = boost::multi_index::get<OrderedNameKindTag>(s_metadata.at(d_backendId));
+    auto it = idx.find(boost::make_tuple(name, kind));
+    if (it == idx.end()) {
+      s_metadata.at(d_backendId).insert(SimpleMetaData(name, kind, meta));
+      return true;
+    }
+    idx.replace(it, SimpleMetaData(name, kind, meta));
+    return true;
+  }
+
+  /* this is not thread-safe */
+  static std::unordered_map<uint64_t, ZoneStorage> s_zones;
+  static std::unordered_map<uint64_t, MetaDataStorage> s_metadata;
+
+protected:
+  std::string d_suffix;
+  std::shared_ptr<RecordStorage> d_records{nullptr};
+  RecordStorage::index<OrderedNameTypeTag>::type::const_iterator d_iter;
+  RecordStorage::index<OrderedNameTypeTag>::type::const_iterator d_end;
+  const uint64_t d_backendId;
+  uint64_t d_currentZone{0};
+  uint8_t d_currentScopeMask{0};
+};
+
+class SimpleBackendBestAuth : public SimpleBackend
+{
+public:
+  SimpleBackendBestAuth(const std::string& suffix): SimpleBackend(suffix)
+  {
+  }
+
+  bool getAuth(const DNSName& target, SOAData* sd) override
+  {
+    static const DNSName best("d.0.1.0.0.2.ip6.arpa.");
+
+    ++d_authLookupCount;
+
+    if (target.isPartOf(best)) {
+      /* return the best SOA right away */
+      std::shared_ptr<RecordStorage> records;
+      uint64_t zoneId;
+      if (!findZone(best, -1, records, zoneId)) {
+        return false;
+      }
+
+      auto& idx = records->get<OrderedNameTypeTag>();
+      auto range = idx.equal_range(boost::make_tuple(best, QType::SOA));
+      if (range.first == range.second) {
+        return false;
+      }
+
+      fillSOAData(range.first->d_content, *sd);
+      sd->ttl = range.first->d_ttl;
+      sd->qname = best;
+      sd->domain_id = zoneId;
+      return true;
+    }
+
+    return getSOA(target, *sd);
+  }
+
+  size_t d_authLookupCount{0};
+};
+
+class SimpleBackendNoMeta : public SimpleBackend
+{
+public:
+  SimpleBackendNoMeta(const std::string& suffix): SimpleBackend(suffix)
+  {
+  }
+
+  bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override
+  {
+    return false;
+  }
+
+  bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override
+  {
+    return false;
+  }
+};
+
+std::unordered_map<uint64_t, SimpleBackend::ZoneStorage> SimpleBackend::s_zones;
+std::unordered_map<uint64_t, SimpleBackend::MetaDataStorage> SimpleBackend::s_metadata;
+
+class SimpleBackendFactory : public BackendFactory
+{
+public:
+  SimpleBackendFactory(): BackendFactory("SimpleBackend")
+  {
+  }
+
+  DNSBackend *make(const string& suffix="") override
+  {
+    return new SimpleBackend(suffix);
+  }
+};
+
+class SimpleBackendBestAuthFactory : public BackendFactory
+{
+public:
+  SimpleBackendBestAuthFactory(): BackendFactory("SimpleBackendBestAuth")
+  {
+  }
+
+  DNSBackend *make(const string& suffix="") override
+  {
+    return new SimpleBackendBestAuth(suffix);
+  }
+};
+
+class SimpleBackendNoMetaFactory : public BackendFactory
+{
+public:
+  SimpleBackendNoMetaFactory(): BackendFactory("SimpleBackendNoMeta")
+  {
+  }
+
+  DNSBackend *make(const string& suffix="") override
+  {
+    return new SimpleBackendNoMeta(suffix);
+  }
+};
+
+struct UeberBackendSetupArgFixture {
+  UeberBackendSetupArgFixture() {
+    extern AuthQueryCache QC;
+    ::arg().set("query-cache-ttl")="0";
+    ::arg().set("negquery-cache-ttl")="0";
+    QC.cleanup();
+    BackendMakers().clear();
+    SimpleBackend::s_zones.clear();
+    SimpleBackend::s_metadata.clear();
+  };
+};
+
+static void testWithoutThenWithCache(std::function<void(UeberBackend& ub)> func)
+{
+  extern AuthQueryCache QC;
+
+  {
+    /* disable the cache */
+    ::arg().set("query-cache-ttl")="0";
+    ::arg().set("negquery-cache-ttl")="0";
+    QC.cleanup();
+
+    UeberBackend ub;
+    func(ub);
+  }
+
+  {
+    /* enable the cache */
+    ::arg().set("query-cache-ttl")="20";
+    ::arg().set("negquery-cache-ttl")="60";
+    QC.cleanup();
+
+    UeberBackend ub;
+    /* a first time to fill the cache */
+    func(ub);
+    /* a second time to make sure every call has been tried with the cache filled */
+    func(ub);
+  }
+}
+
+BOOST_FIXTURE_TEST_SUITE(test_ueberbackend_cc, UeberBackendSetupArgFixture)
+
+static std::vector<DNSZoneRecord> getRecords(UeberBackend& ub, const DNSName& name, uint16_t qtype, int zoneId, const DNSPacket* pkt)
+{
+  std::vector<DNSZoneRecord> result;
+
+  ub.lookup(QType(qtype), name, zoneId, const_cast<DNSPacket*>(pkt));
+
+  DNSZoneRecord dzr;
+  while (ub.get(dzr))
+  {
+    result.push_back(std::move(dzr));
+  }
+
+  return result;
+}
+
+static void checkRecordExists(const std::vector<DNSZoneRecord>& records, const DNSName& name, uint16_t type, int zoneId, uint8_t scopeMask, bool auth)
+{
+  BOOST_REQUIRE_GE(records.size(), 1);
+  for (const auto& record : records) {
+    if (record.domain_id == zoneId &&
+        record.dr.d_type == type &&
+        record.dr.d_name == name &&
+        record.auth == auth &&
+        record.scopeMask == scopeMask) {
+      return;
+    }
+  }
+  BOOST_CHECK_MESSAGE(false, "Record " + name.toString() + "/" + QType(type).getName() + " - " + std::to_string(zoneId) + " not found");
+}
+
+BOOST_AUTO_TEST_CASE(test_simple) {
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with wrong zone id
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 65535, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test a DNS packet is correctly passed and that the corresponding scope is passed back
+      DNSPacket pkt(true);
+      ComboAddress remote("192.0.2.1");
+      pkt.setRemote(&remote);
+      auto records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 32, true);
+      // and that we don't get the same result for a different client
+      remote = ComboAddress("192.0.2.2");
+      pkt.setRemote(&remote);
+      records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_separate_zones) {
+  // one zone in backend 1, a second zone in backend 2
+  // no overlap
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.org."), 2);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.org."), QType::SOA, "ns1.powerdns.org. powerdns.org. 3 600 600 3600000 604800", 3600));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.org."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.org."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.org."), QType::AAAA, "2001:db8::42", 60));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::SOA, 2, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::SOA, 2, 0, true);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test AAAA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::AAAA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test NODATA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::ANY, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::SOA, 2, 0, true);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test AAAA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("www.powerdns.org."), QType::AAAA, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("www.powerdns.org."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test NODATA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::PTR, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with wrong zone id
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::ANY, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+
+      records = getRecords(ub, DNSName("not-powerdns.com."), QType::ANY, 65535, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test a DNS packet is correctly passed and that the corresponding scope is passed back
+      DNSPacket pkt(true);
+      ComboAddress remote("192.0.2.1");
+      pkt.setRemote(&remote);
+      auto records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 32, true);
+      // and that we don't get the same result for a different client
+      remote = ComboAddress("192.0.2.2");
+      pkt.setRemote(&remote);
+      records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_overlay) {
+  // one backend holds the SOA, NS and one A
+  // a second backend holds another A and AAAA
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::A, "192.168.0.1", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.com."), 1);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::A, "192.168.0.2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      // /!\ only 3 records are returned since we don't allow spreading the same name over several backends
+      BOOST_REQUIRE_EQUAL(records.size(), 3);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::A, 1, 0, true);
+      //checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, -1, nullptr);
+      // /!\ the AAAA will be found on an exact search, but not on an ANY one
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 1, nullptr);
+      // /!\ only 3 records are returned since we don't allow spreading the same name over several backends
+      BOOST_REQUIRE_EQUAL(records.size(), 3);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::A, 1, 0, true);
+      //checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, 1, nullptr);
+      // /!\ the AAAA will be found on an exact search, but not on an ANY one
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test www - A with zone id set (only in the second backend)
+      auto records = getRecords(ub, DNSName("www.powerdns.com."), QType::A, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("www.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with wrong zone id
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test a DNS packet is correctly passed and that the corresponding scope is passed back
+      DNSPacket pkt(true);
+      ComboAddress remote("192.0.2.1");
+      pkt.setRemote(&remote);
+      auto records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 32, true);
+      // and that we don't get the same result for a different client
+      remote = ComboAddress("192.0.2.2");
+      pkt.setRemote(&remote);
+      records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_overlay_name) {
+  // one backend holds the apex with SOA, NS and one A
+  // a second backend holds others names
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::A, "192.168.0.2", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.com."), 1);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::AAAA, "192.168.0.1", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 5);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::A, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 5);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::A, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test www - A with zone id set (only in the second backend)
+      auto records = getRecords(ub, DNSName("www.powerdns.com."), QType::A, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("www.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with wrong zone id
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test a DNS packet is correctly passed and that the corresponding scope is passed back
+      DNSPacket pkt(true);
+      ComboAddress remote("192.0.2.1");
+      pkt.setRemote(&remote);
+      auto records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 32, true);
+      // and that we don't get the same result for a different client
+      remote = ComboAddress("192.0.2.2");
+      pkt.setRemote(&remote);
+      records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_child_zone) {
+  // Backend 1 holds zone A "com" while backend 2 holds zone B "powerdns.com"
+  // Check that DS queries are correctly handled
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("com."), QType::SOA, "a.gtld-servers.net. nstld.verisign-grs.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::DS, "44030 8 3 7DD75AE1565051F9563CF8DF976AC99CDCA51E3463019C81BD2BB083 82F3854E", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("ns1.powerdns.com."), QType::A, "192.0.2.1", 3600));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.com."), 2);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", 3600));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("ns1.powerdns.com."), QType::A, "192.0.2.1", 3600));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("com."), QType::SOA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 2, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 3);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 2, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 2, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test getAuth() for DS
+      SOAData sd;
+      BOOST_REQUIRE(ub.getAuth(DNSName("powerdns.com."), QType::DS, &sd));
+      BOOST_CHECK_EQUAL(sd.qname.toString(), "com.");
+      BOOST_CHECK_EQUAL(sd.domain_id, 1);
+    }
+
+    {
+      // test getAuth() for A
+      SOAData sd;
+      BOOST_REQUIRE(ub.getAuth(DNSName("powerdns.com."), QType::A, &sd));
+      BOOST_CHECK_EQUAL(sd.qname.toString(), "powerdns.com.");
+      BOOST_CHECK_EQUAL(sd.domain_id, 2);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_best_soa) {
+  // several backends, one returns the best SOA it has right away
+  // while the others do simple lookups
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("d.0.1.0.0.2.ip6.arpa."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("d.0.1.0.0.2.ip6.arpa."), QType::SOA, "ns.apnic.net. read-txt-record-of-zone-first-dns-admin.apnic.net. 3005126844 7200 1800 604800 3600", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."), QType::PTR, "a.reverse.", 3600));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("0.1.0.0.2.ip6.arpa."), 2);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("0.1.0.0.2.ip6.arpa."), QType::SOA, "ns.apnic.net. read-txt-record-of-zone-first-dns-admin.apnic.net. 3005126844 7200 1800 604800 3600", 3600));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().report(new SimpleBackendBestAuthFactory());
+    BackendMakers().launch("SimpleBackendBestAuth:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      auto sbba = dynamic_cast<SimpleBackendBestAuth*>(ub.backends.at(0));
+      BOOST_REQUIRE(sbba != nullptr);
+      sbba->d_authLookupCount = 0;
+
+      // test getAuth()
+      SOAData sd;
+      BOOST_REQUIRE(ub.getAuth(DNSName("2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."), QType::PTR, &sd));
+      BOOST_CHECK_EQUAL(sd.qname.toString(), "d.0.1.0.0.2.ip6.arpa.");
+      BOOST_CHECK_EQUAL(sd.domain_id, 1);
+      // check that only one auth lookup occurred to this backend
+      BOOST_CHECK_EQUAL(sbba->d_authLookupCount, 1);
+    }
+
+    };
+
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_metadata) {
+  // we have metadata stored in the first and second backend.
+  // We can read from the first backend but not from the second, since the first will return "true" even though it has nothing
+  // Updating will insert into the first backend, leaving the first one untouched
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+    SimpleBackend::s_metadata[1].insert(SimpleBackend::SimpleMetaData(DNSName("powerdns.com."), "test-data-a", { "value1", "value2"}));
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.org."), 2);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.org."), QType::SOA, "ns1.powerdns.org. powerdns.org. 3 600 600 3600000 604800", 3600));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.org."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.org."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.org."), QType::AAAA, "2001:db8::42", 60));
+    SimpleBackend::s_zones[2].insert(zoneB);
+    SimpleBackend::s_metadata[2].insert(SimpleBackend::SimpleMetaData(DNSName("powerdns.org."), "test-data-b", { "value1", "value2"}));
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // check the initial values
+      std::vector<std::string> values;
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.com."), "test-data-a", values));
+      BOOST_REQUIRE_EQUAL(values.size(), 2);
+      BOOST_CHECK_EQUAL(values.at(0), "value1");
+      BOOST_CHECK_EQUAL(values.at(1), "value2");
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.com."), "test-data-b", values));
+      BOOST_CHECK_EQUAL(values.size(), 0);
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.org."), "test-data-a", values));
+      BOOST_CHECK_EQUAL(values.size(), 0);
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.org."), "test-data-b", values));
+      BOOST_CHECK_EQUAL(values.size(), 0);
+    }
+
+    {
+      // update the values
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.com."), "test-data-a", { "value3" }));
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.org."), "test-data-a", { "value4" }));
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.org."), "test-data-b", { "value5" }));
+    }
+
+    // check the updated values
+    {
+      std::vector<std::string> values;
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.com."), "test-data-a", values));
+      BOOST_REQUIRE_EQUAL(values.size(), 1);
+      BOOST_CHECK_EQUAL(values.at(0), "value3");
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.org."), "test-data-a", values));
+      BOOST_REQUIRE_EQUAL(values.size(), 1);
+      BOOST_CHECK_EQUAL(values.at(0), "value4");
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.org."), "test-data-b", values));
+      BOOST_REQUIRE_EQUAL(values.size(), 1);
+      BOOST_CHECK_EQUAL(values.at(0), "value5");
+    }
+
+    {
+      // check that it has not been updated in the second backend
+      const auto& it = SimpleBackend::s_metadata[2].find(boost::make_tuple(DNSName("powerdns.org."), "test-data-b"));
+      BOOST_REQUIRE(it != SimpleBackend::s_metadata[2].end());
+      BOOST_REQUIRE_EQUAL(it->d_values.size(), 2);
+      BOOST_CHECK_EQUAL(it->d_values.at(0), "value1");
+      BOOST_CHECK_EQUAL(it->d_values.at(1), "value2");
+    }
+    };
+
+    UeberBackend ub;
+    testFunction(ub);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+
+BOOST_AUTO_TEST_SUITE_END();
index fdc0d0c4bc83a67dbfe5df1afbabfa648528d64e..859c62b564a6b6c6d774e146fac4f8347ab5033b 100644 (file)
@@ -476,7 +476,7 @@ UeberBackend::UeberBackend(const string &pname)
   d_cache_ttl = ::arg().asNum("query-cache-ttl");
   d_negcache_ttl = ::arg().asNum("negquery-cache-ttl");
 
-  d_stale=false;
+  d_stale = false;
 
   backends=BackendMakers().all(pname=="key-only");
 }
index 80f1ee5723530ff746c0768a9059400bed723731..503b89bc26423147598cd2222dd1e2ac69c00703 100644 (file)
 #include <map>
 #include <string>
 #include <algorithm>
-#include <semaphore.h>
 #include <mutex>
 #include <condition_variable>
 
-#include <unistd.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
 #include <boost/utility.hpp>
+
 #include "dnspacket.hh"
 #include "dnsbackend.hh"
-
 #include "namespaces.hh"
 
 /** This is a very magic backend that allows us to load modules dynamically,