]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Basic functionality works
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 1 Oct 2024 13:58:44 +0000 (15:58 +0200)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 4 Nov 2024 14:20:14 +0000 (15:20 +0100)
Missing: group handling, cleanup of catz no longer in config, tests

14 files changed:
pdns/recursordist/Makefile.am
pdns/recursordist/pdns_recursor.cc
pdns/recursordist/rec-lua-conf.hh
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-xfr.cc
pdns/recursordist/rec-xfr.hh
pdns/recursordist/rec-xfrtracker.cc [new file with mode: 0644]
pdns/recursordist/reczones.cc
pdns/recursordist/rpzloader.cc
pdns/recursordist/settings/cxxsupport.cc
pdns/recursordist/settings/rust-bridge-in.rs
pdns/recursordist/settings/rust/src/bridge.rs
pdns/recursordist/test-settings.cc
pdns/recursordist/ws-recursor.cc

index 6f90adc1d58ee8d066ab68edd83dfb2e31c99e97..482879ea2e521312d3196dfc2549306fb9ff6fb6 100644 (file)
@@ -199,6 +199,7 @@ pdns_recursor_SOURCES = \
        rec-tcp.cc \
        rec-tcpout.cc rec-tcpout.hh \
        rec-xfr.cc rec-xfr.hh \
+       rec-xfrtracker.cc \
        rec-zonetocache.cc rec-zonetocache.hh \
        rec_channel.cc rec_channel.hh rec_metrics.hh \
        rec_channel_rec.cc \
@@ -331,7 +332,7 @@ testrunner_SOURCES = \
        rec-system-resolve.hh rec-system-resolve.cc \
        rec-taskqueue.cc rec-taskqueue.hh \
        rec-tcounters.cc rec-tcounters.hh \
-       rec-xfr.cc rec-xfr.hh \
+       rec-xfrtracker.cc \
        rec-zonetocache.cc rec-zonetocache.hh \
        recpacketcache.cc recpacketcache.hh \
        recursor_cache.cc recursor_cache.hh \
index 251b06c87e08d37fdca4cad4ea0be5b76500bcf2..4ab069e149dcdfb014b3e9ed1e444d273dcc9760 100644 (file)
@@ -2393,7 +2393,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
       SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl,
            g_slogudpin->info(Logr::Notice, "Got NOTIFY", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr), "qname", Logging::Loggable(qname)));
     }
-    if (!notifyZoneTracker(qname)) {
+    if (!ZoneXFR::notifyZoneTracker(qname)) {
       // It wasn't an RPZ
       requestWipeCaches(qname);
     }
index 065da50db67eab89df608609dd98c0dae0948952..de794c2f17c8cfaafa66f390a65b1eac241f710c 100644 (file)
@@ -108,7 +108,7 @@ public:
   SortList sortlist;
   DNSFilterEngine dfe;
   vector<RPZTrackerParams> rpzs;
-  vector<std::pair<ZoneXFRParams, std::shared_ptr<Zone>>> catalogzones;
+  vector<FWCatz> catalogzones;
   TrustAnchorFileInfo trustAnchorFileInfo; // Used to update the Trust Anchors from file periodically
   map<DNSName, dsset_t> dsAnchors;
   map<DNSName, std::string> negAnchors;
index c1fdeffe120ef8f15a8b65a89991ea8a2e0db398..8d146c58098af093d93759cd9ebef6a78ace66d4 100644 (file)
@@ -3425,14 +3425,14 @@ void startLuaConfigDelayedThreads(const LuaConfigItems& luaConfig, uint64_t gene
       exit(1); // NOLINT(concurrency-mt-unsafe)
     }
   }
-  for (const auto& [fcz, zone] : luaConfig.catalogzones) {
-    if (fcz.primaries.empty()) {
+  for (const auto& fcz : luaConfig.catalogzones) {
+    if (fcz.d_params.primaries.empty()) {
       continue;
     }
     try {
       // ZoneXFRTracker uses call by value for its args. That is essential, since we want copies so
       // that ZoneXFRTracker gets values with the proper lifetime.
-      std::thread theThread(zoneXFRTracker, fcz, generation);
+      std::thread theThread(ZoneXFR::zoneXFRTracker, fcz.d_params, generation);
       theThread.detach();
     }
     catch (const std::exception& e) {
@@ -3553,16 +3553,16 @@ static void activateForwardingCatalogZones(LuaConfigItems& lci)
   size_t idx = 0;
   for (auto& fcz : lci.catalogzones) {
 
-    auto& params = fcz.first;
+    auto& params = fcz.d_params;
     params.zoneIdx = idx++;
-    auto zone = std::make_shared<Zone>();
+    auto zone = std::make_shared<CatalogZone>();
     if (params.zoneSizeHint != 0) {
-      //zone->reserve(params.zoneSizeHint);
+      zone->reserve(params.zoneSizeHint);
     }
 
     DNSName domain(params.name);
-    zone->name = domain;
-    fcz.second = zone;
+    zone->setName(domain);
+    fcz.d_catz = zone;
   }
 }
 
@@ -3583,3 +3583,4 @@ void activateLuaConfig(LuaConfigItems& lci)
   activateForwardingCatalogZones(lci);
   g_luaconfs.setState(lci);
 }
+
index 9f6e1ac2ad942f9e07bebda41b7d2ba8c86aed3b..e8a5e6518bea2737125fca31ce994b15b6837460 100644 (file)
  */
 
 #include "rec-xfr.hh"
-#include "lock.hh"
+#include "arguments.hh"
 #include "logging.hh"
 #include "threadname.hh"
 #include "rec-lua-conf.hh"
 #include "query-local-address.hh"
 #include "axfr-retriever.hh"
 #include "ixfr.hh"
+#include "dnsrecords.hh"
+#include "settings/cxxsettings.hh"
+#include "rec-main.hh"
+#include "syncres.hh"
 
-// As there can be multiple threads doing updates (due to config reloads), we use a multimap.
-// The value contains the actual thread id that owns the struct.
+static const DNSName cZones("zones");
+static const DNSName cVersion("version");
 
-static LockGuarded<std::multimap<DNSName, ZoneWaiter&>> condVars;
+// TODO: cleanup files if not in catalogzones
 
-// Notify all threads tracking the zone name
-bool notifyZoneTracker(const DNSName& name)
+void CatalogZone::add(const DNSRecord& record, Logr::log_t logger)
 {
-  auto lock = condVars.lock();
-  auto [start, end] = lock->equal_range(name);
-  if (start == end) {
-    // Did not find any thread tracking that name
-    return false;
+  if (record.d_name.empty()) {
+    logger->info(Logr::Warning, "Record is not part of zone, skipping", "name", Logging::Loggable(record.d_name));
+    return;
   }
-  while (start != end) {
-    start->second.stop = true;
-    start->second.condVar.notify_one();
-    ++start;
+  if (record.d_class != QClass::IN) {
+    logger->info(Logr::Warning, "Record class is not IN, skipping", "name", Logging::Loggable(record.d_name));
+    return;
   }
-  return true;
+
+  if (record.d_name.getLastLabel() != cZones && record.d_name != cVersion) {
+    logger->info(Logr::Warning, "Record is not a catalog zone entry, skipping", "name", Logging::Loggable(record.d_name));
+    return;
+  }
+  const auto& key = record.d_name;
+  logger->info(Logr::Debug, "Adding cat zone entry", "name", Logging::Loggable(key), "qtype", Logging::Loggable(record.d_type));
+  d_records.emplace(std::make_pair(key, record.d_type), record);
+}
+
+void CatalogZone::remove(const DNSRecord& record, Logr::log_t logger)
+{
+  if (record.d_name.empty()) {
+    logger->info(Logr::Warning, "Record is not part of zone, skipping", "name", Logging::Loggable(record.d_name));
+    return;
+  }
+  if (record.d_class != QClass::IN) {
+    logger->info(Logr::Warning, "Record class is not IN, skipping", "name", Logging::Loggable(record.d_name));
+    return;
+  }
+
+  if (record.d_name.getLastLabel() != cZones && record.d_name != cVersion) {
+    logger->info(Logr::Warning, "Record is not a catalog zone entry, skipping", "name", Logging::Loggable(record.d_name));
+    return;
+  }
+  const auto& key = record.d_name;
+  logger->info(Logr::Debug, "Removing cat zone entry", "name", Logging::Loggable(key), "qtype", Logging::Loggable(record.d_type));
+  d_records.erase(std::make_pair(key, record.d_type));
+}
+
+void CatalogZone::registerForwarders(const FWCatz& params, Logr::log_t logger)
+{
+  auto defsIter = params.d_defaults.find("");
+  pdns::rust::settings::rec::FCZDefault defaults  = defsIter->second;
+  // defaults.name = "default";
+  // defaults.forwarders = {"1.2.3.4", "4.5.6.7"};
+  // defaults.recurse = false;
+  // defaults.notify_allowed = false;
+  
+  const string zonesFile = ::arg()["api-config-dir"] + "/catzone." + d_name.toString();
+  ::rust::Vec<::pdns::rust::settings::rec::ForwardZone> forwards;
+  for (const auto& record : d_records) {
+    if (record.first.second != QType::PTR) {
+      continue;
+    }
+    if (const auto ptr = getRR<PTRRecordContent>(record.second)) {
+      auto target = ptr->getContent();
+      pdns::rust::settings::rec::ForwardZone forward;
+      forward.zone = target.toString();
+      forward.recurse = defaults.recurse;
+      forward.notify_allowed = defaults.notify_allowed;
+      for (const auto& value : defaults.forwarders) {
+        forward.forwarders.emplace_back(value);
+      }
+      forward.validate("");
+      forwards.emplace_back(std::move(forward));
+    }
+  }
+  pdns::rust::settings::rec::api_delete_zones(zonesFile);
+  pdns::rust::settings::rec::api_add_forward_zones(zonesFile, forwards);
+  reloadZoneConfiguration(true);
 }
 
-void insertZoneTracker(const DNSName& zoneName, ZoneWaiter& waiter)
+bool CatalogZone::versionCheck() const
 {
-  auto lock = condVars.lock();
-  lock->emplace(zoneName, waiter);
+  auto records = d_records.equal_range(std::make_pair(cVersion, QType::TXT));
+  bool invalid = false;
+  size_t count = 0;
+  for (auto record = records.first; record != records.second; ++record) {
+    const auto txt = getRR<TXTRecordContent>(record->second);
+    if (txt == nullptr) {
+      invalid = true;
+      continue;
+    }
+    auto str = txt->d_text;
+    if (str != "\"2\"") {
+      invalid = true;
+      continue;
+    }
+    ++count;
+  }
+  return !invalid && count == 1;
 }
 
-void clearZoneTracker(const DNSName& zoneName)
+bool CatalogZone::dupsCheck() const
 {
-  // Zap our (and only our) ZoneWaiter struct out of the multimap
-  auto lock = condVars.lock();
-  auto [start, end] = lock->equal_range(zoneName);
-  while (start != end) {
-    if (start->second.id == std::this_thread::get_id()) {
-      lock->erase(start);
+  std::unordered_set<DNSName> values;
+  bool invalid = false;
+  for (const auto& [key, record] : d_records) {
+    if (key.second != QType::PTR) {
+      continue;
+    }
+    const auto ptr = getRR<PTRRecordContent>(record);
+    if (ptr == nullptr) {
+      invalid = true;
+      continue;
+    }
+    if (!values.insert(ptr->getContent()).second) {
+      invalid = true;
       break;
     }
-    ++start;
   }
+  return !invalid;
 }
 
-static shared_ptr<const SOARecordContent> loadZoneFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, const TSIGTriplet& tsigTriplet, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
+static shared_ptr<const SOARecordContent> loadZoneFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, shared_ptr<CatalogZone>& zone, const TSIGTriplet& tsigTriplet, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
 {
 
   auto logger = plogger->withValues("primary", Logging::Loggable(primary));
@@ -106,7 +188,7 @@ static shared_ptr<const SOARecordContent> loadZoneFromServer(Logr::log_t plogger
         continue;
       }
 
-      //RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
+      zone->add(dnsRecord, logger);
       nrecords++;
     }
     axfrNow = time(nullptr);
@@ -119,44 +201,54 @@ static shared_ptr<const SOARecordContent> loadZoneFromServer(Logr::log_t plogger
     }
   }
 
+  if (!zone->versionCheck()) {
+    zone->clear();
+    throw PDNSException("no valid version record in catalog zone");
+  }
+  if (!zone->dupsCheck()) {
+    zone->clear();
+    throw PDNSException("duplicate PTR values in catalog zone");
+  }
   logger->info(Logr::Info, "Zone load completed", "nrecords", Logging::Loggable(nrecords), "soa", Logging::Loggable(soaRecordContent->getZoneRepresentation()));
   return soaRecordContent;
 }
 
-static void preloadZoneFIle(ZoneXFRParams& params, const DNSName& zoneName, std::shared_ptr<Zone>& oldZone, uint32_t& refresh, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger)
+void ZoneXFR::preloadZoneFile(const DNSName& zoneName, std::shared_ptr<CatalogZone>& oldZone, uint32_t& refresh, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger)
 {
-  while (!params.soaRecordContent) {
+  while (!d_params.soaRecordContent) {
     /* if we received an empty sr, the zone was not really preloaded */
 
     /* full copy, as promised */
-    std::shared_ptr<Zone> newZone = std::make_shared<Zone>(*oldZone);
-    for (const auto& primary : params.primaries) {
+    auto newZone = std::make_shared<CatalogZone>(*oldZone);
+    for (const auto& primary : d_params.primaries) {
       try {
-        params.soaRecordContent = loadZoneFromServer(logger, primary, zoneName, params.tsigtriplet, params.maxReceivedMBytes, params.localAddress, params.xfrTimeout);
-        newZone->serial = params.soaRecordContent->d_st.serial;
-        newZone->refresh = params.soaRecordContent->d_st.refresh;
-        refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : newZone->refresh, 1U);
-        //setRPZZoneNewState(polName, params.zoneXFRParams.soaRecordContent->d_st.serial, newZone->size(), false, true);
-
-        g_luaconfs.modify([zoneIdx = params.zoneIdx, &newZone](LuaConfigItems& lci) {
-          lci.catalogzones.at(zoneIdx).second = newZone;
+        d_params.soaRecordContent = loadZoneFromServer(logger, primary, zoneName, newZone,  d_params.tsigtriplet, d_params.maxReceivedMBytes, d_params.localAddress, d_params.xfrTimeout);
+        newZone->setSerial(d_params.soaRecordContent->d_st.serial);
+        newZone->setRefresh(d_params.soaRecordContent->d_st.refresh);
+        refresh = std::max(d_params.refreshFromConf != 0 ? d_params.refreshFromConf : newZone->getRefresh(), 1U);
+        newZone->newStats(d_params.soaRecordContent->d_st.serial, true);
+
+        g_luaconfs.modify([zoneIdx = d_params.zoneIdx, &newZone](LuaConfigItems& lci) {
+          lci.catalogzones.at(zoneIdx).d_catz = newZone;
         });
 
+        auto lci = g_luaconfs.getLocal();
+        newZone->registerForwarders(lci->catalogzones.at(d_params.zoneIdx), logger);
         /* no need to try another primary */
         break;
       }
       catch (const std::exception& e) {
         logger->error(Logr::Warning, e.what(), "Unable to load zone, will retry", "from", Logging::Loggable(primary), "exception", Logging::Loggable("std::exception"), "refresh", Logging::Loggable(refresh));
-        // Stats
+        oldZone->incFailedStats();
       }
       catch (const PDNSException& e) {
         logger->error(Logr::Warning, e.reason, "Unable to load zone, will retry", "from", Logging::Loggable(primary), "exception", Logging::Loggable("PDNSException"), "refresh", Logging::Loggable(refresh));
-        // Stats
+        oldZone->incFailedStats();
       }
     }
     // Release newZone before (long) sleep to reduce memory usage
     newZone = nullptr;
-    if (!params.soaRecordContent) {
+    if (!d_params.soaRecordContent) {
       std::unique_lock lock(waiter.mutex);
       waiter.condVar.wait_for(lock, std::chrono::seconds(refresh),
                                  [&stop = waiter.stop] { return stop.load(); });
@@ -171,14 +263,15 @@ static void preloadZoneFIle(ZoneXFRParams& params, const DNSName& zoneName, std:
       return;
     }
   }
+
 }
 
-static bool zoneTrackerIteration(ZoneXFRParams& params, const DNSName& zoneName, std::shared_ptr<Zone>& oldZone, uint32_t& refresh, bool& skipRefreshDelay, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger)
+bool ZoneXFR::zoneTrackerIteration(const DNSName& zoneName, std::shared_ptr<CatalogZone>& oldZone, uint32_t& refresh, bool& skipRefreshDelay, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger)
 {
   // Don't hold on to oldZone, it well be re-assigned after sleep in the try block
   oldZone = nullptr;
   DNSRecord soaRecord;
-  soaRecord.setContent(params.soaRecordContent);
+  soaRecord.setContent(d_params.soaRecordContent);
 
   if (skipRefreshDelay) {
     skipRefreshDelay = false;
@@ -210,25 +303,25 @@ static bool zoneTrackerIteration(ZoneXFRParams& params, const DNSName& zoneName,
   }
 
   vector<pair<vector<DNSRecord>, vector<DNSRecord>>> deltas;
-  for (const auto& primary : params.primaries) {
+  for (const auto& primary : d_params.primaries) {
     auto soa = getRR<SOARecordContent>(soaRecord);
     auto serial = soa ? soa->d_st.serial : 0;
     logger->info(Logr::Info, "Getting IXFR deltas", "address", Logging::Loggable(primary), "ourserial", Logging::Loggable(serial));
 
-    ComboAddress local(params.localAddress);
+    ComboAddress local(d_params.localAddress);
     if (local == ComboAddress()) {
       local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
     }
 
     try {
-      deltas = getIXFRDeltas(primary, zoneName, soaRecord, params.xfrTimeout, true, params.tsigtriplet, &local, params.maxReceivedMBytes);
+      deltas = getIXFRDeltas(primary, zoneName, soaRecord, d_params.xfrTimeout, true, d_params.tsigtriplet, &local, d_params.maxReceivedMBytes);
 
       /* no need to try another primary */
       break;
     }
     catch (const std::runtime_error& e) {
       logger->error(Logr::Warning, e.what(), "Exception during retrieval of delta", "exception", Logging::Loggable("std::runtime_error"));
-      // XXX stats
+      oldZone->incFailedStats();
       continue;
     }
   }
@@ -244,25 +337,27 @@ static bool zoneTrackerIteration(ZoneXFRParams& params, const DNSName& zoneName,
       logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing zone update thread");
       return false;
     }
-    oldZone = luaconfsLocal->catalogzones.at(params.zoneIdx).second;
-    if (!oldZone || oldZone->name != zoneName) {
+    oldZone = luaconfsLocal->catalogzones.at(d_params.zoneIdx).d_catz;
+    if (!oldZone || oldZone->getName() != zoneName) {
       logger->info(Logr::Info, "This policy is no more, stopping the existing zone update thread");
       return false;
     }
     /* we need to make a _full copy_ of the zone we are going to work on */
-    std::shared_ptr<Zone> newZone = std::make_shared<Zone>(*oldZone);
+    auto newZone = std::make_shared<CatalogZone>(*oldZone);
     /* initialize the current serial to the last one */
-    std::shared_ptr<const SOARecordContent> currentSR = params.soaRecordContent;
+    std::shared_ptr<const SOARecordContent> currentSR = d_params.soaRecordContent;
 
     int totremove = 0;
     int totadd = 0;
+    bool fullUpdate = false;
 
     for (const auto& delta : deltas) {
       const auto& remove = delta.first;
       const auto& add = delta.second;
       if (remove.empty()) {
         logger->info(Logr::Warning, "IXFR update is a whole new zone");
-        newZone->d_records.clear();
+        newZone->clear();
+        fullUpdate = true;
       }
       for (const auto& resourceRecord : remove) { // should always contain the SOA
         if (resourceRecord.d_type == QType::NS) {
@@ -283,7 +378,7 @@ static bool zoneTrackerIteration(ZoneXFRParams& params, const DNSName& zoneName,
         else {
           totremove++;
           logger->info(Logr::Debug, "Remove from zone", "name", Logging::Loggable(resourceRecord.d_name));
-        //RPZRecordToPolicy(resourceRecord, newZone, false, params.defpol, params.defpolOverrideLocal, params.maxTTL, logger);
+          newZone->remove(resourceRecord, logger);
         }
       }
 
@@ -300,19 +395,25 @@ static bool zoneTrackerIteration(ZoneXFRParams& params, const DNSName& zoneName,
         else {
           totadd++;
           logger->info(Logr::Debug, "Addition to zone", "name", Logging::Loggable(resourceRecord.d_name));
-        //RPZRecordToPolicy(resourceRecord, newZone, true, params.defpol, params.defpolOverrideLocal, params.maxTTL, logger);
+          newZone->add(resourceRecord, logger);
         }
       }
     }
+  if (!newZone->versionCheck()) {
+    throw PDNSException("no valid version record in catalog zone");
+  }
+  if (!newZone->dupsCheck()) {
+    throw PDNSException("duplicate PTR values in catalog zone");
+  }
 
     /* only update sr now that all changes have been converted */
     if (currentSR) {
-      params.soaRecordContent = std::move(currentSR);
+      d_params.soaRecordContent = std::move(currentSR);
     }
-    logger->info(Logr::Info, "Zone mutations", "removals", Logging::Loggable(totremove), "additions", Logging::Loggable(totadd), "newserial", Logging::Loggable(params.soaRecordContent->d_st.serial));
-    newZone->serial = params.soaRecordContent->d_st.serial;
-    newZone->refresh = params.soaRecordContent->d_st.refresh;
-    //setRPZZoneNewState(polName, params.zoneXFRParams.soaRecordContent->d_st.serial, newZone->size(), false, fullUpdate);
+    logger->info(Logr::Info, "Zone mutations", "removals", Logging::Loggable(totremove), "additions", Logging::Loggable(totadd), "newserial", Logging::Loggable(d_params.soaRecordContent->d_st.serial));
+    newZone->setSerial(d_params.soaRecordContent->d_st.serial);
+    newZone->setRefresh(d_params.soaRecordContent->d_st.refresh);
+    newZone->newStats(d_params.soaRecordContent->d_st.serial, fullUpdate);
 
     /* we need to replace the existing zone with the new one,
        but we don't want to touch anything else, especially other zones,
@@ -322,11 +423,13 @@ static bool zoneTrackerIteration(ZoneXFRParams& params, const DNSName& zoneName,
       logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing zone update thread");
       return false;
     }
-    g_luaconfs.modify([zoneIdx = params.zoneIdx, &newZone](LuaConfigItems& lci) {
-      lci.catalogzones.at(zoneIdx).second = newZone;
+    g_luaconfs.modify([zoneIdx = d_params.zoneIdx, &newZone](LuaConfigItems& lci) {
+      lci.catalogzones.at(zoneIdx).d_catz = newZone;
     });
 
-    refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : newZone->refresh, 1U);
+    auto lci = g_luaconfs.getLocal();
+    newZone->registerForwarders(lci->catalogzones.at(d_params.zoneIdx), logger);
+    refresh = std::max(d_params.refreshFromConf != 0 ? d_params.refreshFromConf : newZone->getRefresh(), 1U);
   }
   catch (const std::exception& e) {
     logger->error(Logr::Error, e.what(), "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("std::exception"));
@@ -338,7 +441,7 @@ static bool zoneTrackerIteration(ZoneXFRParams& params, const DNSName& zoneName,
 }
 
 // coverity[pass_by_value] params is intended to be a copy, as this is the main function of a thread
-void zoneXFRTracker(ZoneXFRParams params, uint64_t configGeneration) // NOLINT(performance-unnecessary-value-param)
+void ZoneXFR::zoneXFRTracker(ZoneXFRParams params, uint64_t configGeneration) // NOLINT(performance-unnecessary-value-param)
 {
   setThreadName("rec/catixfr");
   bool isPreloaded = params.soaRecordContent != nullptr;
@@ -346,9 +449,9 @@ void zoneXFRTracker(ZoneXFRParams params, uint64_t configGeneration) // NOLINT(p
   ZoneWaiter waiter(std::this_thread::get_id());
 
   /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
-  std::shared_ptr<Zone> oldZone;
+  std::shared_ptr<CatalogZone> oldZone;
   if (params.zoneIdx < g_luaconfs.getLocal()->catalogzones.size()) {
-    oldZone = g_luaconfs.getLocal()->catalogzones.at(params.zoneIdx).second;
+    oldZone = g_luaconfs.getLocal()->catalogzones.at(params.zoneIdx).d_catz;
   }
   if (!oldZone) {
     logger->error(Logr::Error, "Unable to retrieve catalog zone from configuration", "index", Logging::Loggable(params.zoneIdx));
@@ -356,19 +459,21 @@ void zoneXFRTracker(ZoneXFRParams params, uint64_t configGeneration) // NOLINT(p
   }
 
   // If oldZone failed to load its getRefresh() returns 0, protect against that
-  uint32_t refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : oldZone->refresh, 10U);
-  DNSName zoneName = oldZone->name;
+  uint32_t refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : oldZone->getRefresh(), 10U);
+  DNSName zoneName = oldZone->getName();
 
   // Now that we know the name, set it in the logger
   logger = logger->withValues("zone", Logging::Loggable(zoneName));
 
   insertZoneTracker(zoneName, waiter);
 
-  preloadZoneFIle(params, zoneName, oldZone, refresh, configGeneration, waiter, logger);
+  ZoneXFR xfrObject(params);
+  xfrObject.preloadZoneFile(zoneName, oldZone, refresh, configGeneration, waiter, logger);
   bool skipRefreshDelay = isPreloaded;
-  while (zoneTrackerIteration(params, zoneName, oldZone, refresh, skipRefreshDelay, configGeneration, waiter, logger)) {
+  while (xfrObject.zoneTrackerIteration(zoneName, oldZone, refresh, skipRefreshDelay, configGeneration, waiter, logger)) {
     // empty
   }
 
   clearZoneTracker(zoneName);
 }
+
index 5a735f926aa0d2537a0df9a5ab81d22e58ce5378..3e77806497e2d60930ad86eef834fc6575d08c1c 100644 (file)
 #include <vector>
 
 #include "iputils.hh"
+#include "lock.hh"
+#include "logr.hh"
+#include "dnsrecords.hh"
+#include "rust/lib.rs.h"
 
 class DNSName;
 class SOARecordContent;
+struct FWCatz;
 
 // Please make sure that the struct below only contains value types since they are used as parameters in a thread ct
 struct ZoneXFRParams
@@ -47,27 +52,97 @@ struct ZoneXFRParams
   uint16_t xfrTimeout{20};
 };
 
-// A struct that holds the condition var and related stuff to allow notifies to be sent to the tread owning
-// the struct.
-struct ZoneWaiter
+class CatalogZone
 {
-  ZoneWaiter(std::thread::id arg) :
-    id(arg) {}
-  std::thread::id id;
-  std::mutex mutex;
-  std::condition_variable condVar;
-  std::atomic<bool> stop{false};
+public:
+  void setRefresh(uint32_t refresh)
+  {
+    d_refresh = refresh;
+  }
+  [[nodiscard]] auto getRefresh() const
+  {
+    return d_refresh;
+  }
+  void setSerial(uint32_t serial)
+  {
+    d_serial = serial;
+  }
+  void setName(const DNSName& name)
+  {
+    d_name = name;
+  }
+  [[nodiscard]] auto getName() const
+  {
+    return d_name;
+  }
+  void reserve([[maybe_unused]] size_t size)
+  {
+    //d_records.reserve(size);
+  }
+  void clear()
+  {
+    d_records.clear();
+  }
+  void newStats([[maybe_unused]] uint32_t serial, [[maybe_unused]]bool fullTransfer)
+  {
+    // XXX stats
+  }
+  void incFailedStats()
+  {
+    // XXX stats
+  }
+  void add(const DNSRecord& record, Logr::log_t  logger);
+  void remove(const DNSRecord& record, Logr::log_t logger);
+  void registerForwarders(const FWCatz& params, Logr::log_t logger);
+  [[nodiscard]] bool versionCheck() const;
+  [[nodiscard]] bool dupsCheck() const;
+
+private:
+  std::multimap<std::pair<DNSName, QType>, DNSRecord> d_records;
+  DNSName d_name;
+  uint32_t d_refresh{0};
+  uint32_t d_serial{0};
 };
 
-struct Zone
+struct FWCatz
 {
-  std::vector<DNSRecord> d_records;
-  DNSName name;
-  uint32_t refresh{0};
-  uint32_t serial{0};
+  ZoneXFRParams d_params;
+  std::map<std::string, pdns::rust::settings::rec::FCZDefault> d_defaults;
+  std::shared_ptr<CatalogZone> d_catz;
+};
+
+struct ZoneXFR
+{
+public:
+  // A struct that holds the condition var and related stuff to allow notifies to be sent to the tread owning
+  // the struct.
+  struct ZoneWaiter
+  {
+    ZoneWaiter(std::thread::id arg) :
+      id(arg) {}
+    std::thread::id id;
+    std::mutex mutex;
+    std::condition_variable condVar;
+    std::atomic<bool> stop{false};
+  };
+
+  static bool notifyZoneTracker(const DNSName& name);
+  static void insertZoneTracker(const DNSName& zoneName, ZoneWaiter& waiter);
+  static void clearZoneTracker(const DNSName& zoneName);
+  static void zoneXFRTracker(ZoneXFRParams params, uint64_t configGeneration);
+
+  ZoneXFR(ZoneXFRParams  params) :
+    d_params(std::move(params))
+  {}
+
+private:
+  void preloadZoneFile(const DNSName& zoneName, std::shared_ptr<CatalogZone>& oldZone, uint32_t& refresh, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger);
+  bool zoneTrackerIteration(const DNSName& zoneName, std::shared_ptr<CatalogZone>& oldZone, uint32_t& refresh, bool& skipRefreshDelay, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger);
+
+  ZoneXFRParams d_params;
+
+  // As there can be multiple threads doing updates (due to config reloads), we use a multimap.
+  // The value contains the actual thread id that owns the struct.
+  static LockGuarded<std::multimap<DNSName, ZoneXFR::ZoneWaiter&>> condVars;
 };
 
-bool notifyZoneTracker(const DNSName& name);
-void insertZoneTracker(const DNSName& zoneName, ZoneWaiter& waiter);
-void clearZoneTracker(const DNSName& zoneName);
-void zoneXFRTracker(ZoneXFRParams params, uint64_t configGeneration);
diff --git a/pdns/recursordist/rec-xfrtracker.cc b/pdns/recursordist/rec-xfrtracker.cc
new file mode 100644 (file)
index 0000000..4941c71
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#include "rec-xfr.hh"
+#include "dnsname.hh"
+
+LockGuarded<std::multimap<DNSName, ZoneXFR::ZoneWaiter&>> ZoneXFR::condVars;
+
+// Notify all threads tracking the zone name
+bool ZoneXFR::notifyZoneTracker(const DNSName& name)
+{
+  auto lock = condVars.lock();
+  auto [start, end] = lock->equal_range(name);
+  if (start == end) {
+    // Did not find any thread tracking that name
+    return false;
+  }
+  while (start != end) {
+    start->second.stop = true;
+    start->second.condVar.notify_one();
+    ++start;
+  }
+  return true;
+}
+
+void ZoneXFR::insertZoneTracker(const DNSName& zoneName, ZoneWaiter& waiter)
+{
+  auto lock = condVars.lock();
+  lock->emplace(zoneName, waiter);
+}
+
+void ZoneXFR::clearZoneTracker(const DNSName& zoneName)
+{
+  // Zap our (and only our) ZoneWaiter struct out of the multimap
+  auto lock = condVars.lock();
+  auto [start, end] = lock->equal_range(zoneName);
+  while (start != end) {
+    if (start->second.id == std::this_thread::get_id()) {
+      lock->erase(start);
+      break;
+    }
+    ++start;
+  }
+}
index 1fa8361fbfe1ba01d2b661e439f73688bef49b7e..7831b1afd6957c1847f7c7f44e47a028717d53b4 100644 (file)
@@ -313,12 +313,12 @@ static void processForwardZones(shared_ptr<SyncRes::domainmap_t>& newMap, Logr::
   }
 }
 
-static void processApiZonesFile(shared_ptr<SyncRes::domainmap_t>& newMap, shared_ptr<notifyset_t>& newSet, Logr::log_t log)
+static void processApiZonesFile(const string& file, shared_ptr<SyncRes::domainmap_t>& newMap, shared_ptr<notifyset_t>& newSet, Logr::log_t log)
 {
   if (::arg()["api-config-dir"].empty()) {
     return;
   }
-  const auto filename = ::arg()["api-config-dir"] + "/apizones";
+  const auto filename = ::arg()["api-config-dir"] + "/" + file;
   struct stat statStruct
   {
   };
@@ -602,7 +602,11 @@ std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>>
   processForwardZones(newMap, log);
   processForwardZonesFile(newMap, newSet, log);
   if (yaml) {
-    processApiZonesFile(newMap, newSet, log);
+    auto lci = g_luaconfs.getLocal();
+    processApiZonesFile("apizones", newMap, newSet, log);
+    for (const auto& catz : lci->catalogzones) {
+      processApiZonesFile("catzone." + catz.d_catz->getName().toString(), newMap, newSet, log);
+    }
   }
   processExportEtcHosts(newMap, log);
   processServeRFC1918(newMap, log);
index ad46ebaebf5abe1bb8e6e389a2677006d48e2c2b..46fcffb0c0e635d68cc5190c97c474a271ee5454 100644 (file)
@@ -435,7 +435,7 @@ static bool dumpZoneToDisk(Logr::log_t logger, const std::shared_ptr<DNSFilterEn
 }
 
 
-static void preloadRPZFIle(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, uint64_t configGeneration, ZoneWaiter& rpzwaiter, Logr::log_t logger)
+static void preloadRPZFIle(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, uint64_t configGeneration, ZoneXFR::ZoneWaiter& rpzwaiter, Logr::log_t logger)
 {
   while (!params.zoneXFRParams.soaRecordContent) {
     /* if we received an empty sr, the zone was not really preloaded */
@@ -491,7 +491,7 @@ static void preloadRPZFIle(RPZTrackerParams& params, const DNSName& zoneName, st
   }
 }
 
-static bool RPZTrackerIteration(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, bool& skipRefreshDelay, uint64_t configGeneration, ZoneWaiter& rpzwaiter, Logr::log_t logger)
+static bool RPZTrackerIteration(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, bool& skipRefreshDelay, uint64_t configGeneration, ZoneXFR::ZoneWaiter& rpzwaiter, Logr::log_t logger)
 {
   // Don't hold on to oldZone, it well be re-assigned after sleep in the try block
   oldZone = nullptr;
@@ -678,7 +678,7 @@ void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration)
   setThreadName("rec/rpzixfr");
   bool isPreloaded = params.zoneXFRParams.soaRecordContent != nullptr;
   auto logger = g_slog->withName("rpz");
-  ZoneWaiter waiter(std::this_thread::get_id());
+  ZoneXFR::ZoneWaiter waiter(std::this_thread::get_id());
 
   /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
   std::shared_ptr<DNSFilterEngine::Zone> oldZone = g_luaconfs.getLocal()->dfe.getZone(params.zoneXFRParams.zoneIdx);
@@ -696,7 +696,7 @@ void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration)
   // Now that we know the name, set it in the logger
   logger = logger->withValues("zone", Logging::Loggable(zoneName));
 
-  insertZoneTracker(zoneName, waiter);
+  ZoneXFR::insertZoneTracker(zoneName, waiter);
 
   preloadRPZFIle(params, zoneName, oldZone, refresh, polName, configGeneration, waiter, logger);
 
@@ -706,5 +706,5 @@ void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration)
     // empty
   }
 
-  clearZoneTracker(zoneName);
+  ZoneXFR::clearZoneTracker(zoneName);
 }
index 3d045f4370fd33561dc02497c79a1a7d650dd1c6..d465fd0bc195d05cdb57c5ff6d15d6875c3756b2 100644 (file)
@@ -1304,27 +1304,29 @@ void fromRustToLuaConfig(const rust::Vec<pdns::rust::settings::rec::ProxyMapping
   }
 }
 
-void fromRustToLuaConfig(const rust::Vec<pdns::rust::settings::rec::ForwardingCatalogZone>& catzones, std::vector<pair<ZoneXFRParams, std::shared_ptr<Zone>>>& lua)
+void fromRustToLuaConfig(const rust::Vec<pdns::rust::settings::rec::ForwardingCatalogZone>& catzones, std::vector<FWCatz>& lua)
 {
   for (const auto& catz : catzones) {
-    cerr << "catz: " << catz.name << endl;
-    ZoneXFRParams params;
-    auto zone = std::make_shared<Zone>();
+    FWCatz fwcatz;
+    for (const auto& def : catz.groups) {
+      fwcatz.d_defaults.emplace(def.name, def);
+    }
+    fwcatz.d_catz = std::make_shared<CatalogZone>();
 
     for (const auto& address : catz.xfr.addresses) {
       ComboAddress combo = ComboAddress(std::string(address), 53);
-      params.primaries.emplace_back(combo.toStringWithPort());
+      fwcatz.d_params.primaries.emplace_back(combo.toStringWithPort());
     }
-    params.name = std::string(catz.name);
-    params.zoneSizeHint = catz.xfr.zoneSizeHint;
-    assign(params.tsigtriplet, catz.xfr.tsig);
-    params.refreshFromConf = catz.xfr.refresh;
-    params.maxReceivedMBytes = catz.xfr.maxReceivedMBytes;
+    fwcatz.d_params.name = std::string(catz.name);
+    fwcatz.d_params.zoneSizeHint = catz.xfr.zoneSizeHint;
+    assign(fwcatz.d_params.tsigtriplet, catz.xfr.tsig);
+    fwcatz.d_params.refreshFromConf = catz.xfr.refresh;
+    fwcatz.d_params.maxReceivedMBytes = catz.xfr.maxReceivedMBytes;
     if (!catz.xfr.localAddress.empty()) {
-      params.localAddress = ComboAddress(std::string(catz.xfr.localAddress));
+      fwcatz.d_params.localAddress = ComboAddress(std::string(catz.xfr.localAddress));
     }
-    params.xfrTimeout = catz.xfr.axfrTimeout;
-    lua.emplace_back(params, zone);
+    fwcatz.d_params.xfrTimeout = catz.xfr.axfrTimeout;
+    lua.emplace_back(std::move(fwcatz));
   }
 }
 }
index 5211d996d6b6fdf0ec05775050a611e683b052d0..2fd4f746221215aa96b41d94e24732166c85f9dd 100644 (file)
@@ -287,7 +287,7 @@ pub struct FCZDefault {
     #[serde(default, skip_serializing_if = "crate::is_default")]
     recurse: bool,
     #[serde(default, skip_serializing_if = "crate::is_default")]
-    allow_notify: bool,
+    notify_allowed: bool,
  }
 
 #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
@@ -296,7 +296,7 @@ pub struct ForwardingCatalogZone {
     #[serde(default, skip_serializing_if = "crate::is_default")]
     name: String,
     #[serde(default, skip_serializing_if = "crate::is_default")]
-    allow_notify: bool,
+    notify_allowed: bool,
     #[serde(default, skip_serializing_if = "crate::is_default")]
     xfr: XFR,
     #[serde(default, skip_serializing_if = "crate::is_default")]
@@ -388,6 +388,7 @@ extern "Rust" {
     fn validate_trustanchors(field: &str, vec: &Vec<TrustAnchor>) -> Result<()>;
     fn validate_negativetrustanchors(field: &str, vec: &Vec<NegativeTrustAnchor>) -> Result<()>;
     fn api_delete_zone(file: &str, zone: &str) -> Result<()>;
+    fn api_delete_zones(file: &str) -> Result<()>;
 }
 
 unsafe extern "C++" {
index 0abaf931b27df211575129346d86d9228b63eea0..d39d787696c3cd8eb03b49bf86da3fe5f7344a5f 100644 (file)
@@ -665,7 +665,7 @@ impl ForwardingCatalogZone {
         let seq = serde_yaml::Sequence::new();
         let mut map = serde_yaml::Mapping::new();
         inserts(&mut map, "name", &self.name);
-        insertb(&mut map, "allow_notify", self.allow_notify);
+        insertb(&mut map, "notify_allowed", self.notify_allowed);
         insertseq(&mut map, "groups", &seq);
         serde_yaml::Value::Mapping(map)
     }
@@ -1037,6 +1037,14 @@ pub fn api_delete_zone(path: &str, zone: &str) -> Result<(), std::io::Error> {
     api_write_zones(path, &zones)
 }
 
+// This function is called from C++, it needs to acquire the lock
+pub fn api_delete_zones(path: &str) -> Result<(), std::io::Error> {
+    let _lock = LOCK.lock().unwrap();
+    let mut zones = api_read_zones_locked(path, true)?;
+    zones.forward_zones.clear();
+    api_write_zones(path, &zones)
+}
+
 pub fn def_pb_export_qtypes() -> Vec<String> {
     vec![
         String::from("A"),
index 831d6393c3c3ed908f2ef0c172f8c3aec76d53af..ac103d977b472e90b6d07ea586763b35bfa49685 100644 (file)
@@ -898,7 +898,7 @@ BOOST_AUTO_TEST_CASE(test_yaml_forwardingcatalogzones)
       - name: mygroup
         forwarders: [4.5.6.7]
         recurse: true
-        allow_notify: true
+        notify_allowed: true
 )EOT";
 
   auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
@@ -912,7 +912,7 @@ BOOST_AUTO_TEST_CASE(test_yaml_forwardingcatalogzones)
   BOOST_CHECK_EQUAL(settings.recursor.forwarding_catalog_zones[0].groups[1].forwarders.size(), 1U);
   BOOST_CHECK_EQUAL(std::string(settings.recursor.forwarding_catalog_zones[0].groups[1].forwarders[0]), "4.5.6.7");
   BOOST_CHECK_EQUAL(settings.recursor.forwarding_catalog_zones[0].groups[1].recurse, true);
-  BOOST_CHECK_EQUAL(settings.recursor.forwarding_catalog_zones[0].groups[1].allow_notify, true);
+  BOOST_CHECK_EQUAL(settings.recursor.forwarding_catalog_zones[0].groups[1].notify_allowed, true);
 }
 
 BOOST_AUTO_TEST_CASE(test_yaml_to_luaconfigand_back)
index d6d8ed89c6e26aaf99aad41fc206bb1c2229a6a9..36569d29c25e5c7199b51e91b1f30dcd81c6ce94 100644 (file)
@@ -235,7 +235,7 @@ static void doCreateZone(const Json& document)
   bool notifyAllowed = boolFromJson(document, "notify_allowed", false);
   string confbasename = "zone-" + apiZoneNameToId(zone);
 
-  const string yamlAPiZonesFile = ::arg()["api-config-dir"] + "/apizones";
+  const string yamlAPIZonesFile = ::arg()["api-config-dir"] + "/apizones";
 
   if (kind == "NATIVE") {
     if (rdFlag) {
@@ -270,7 +270,7 @@ static void doCreateZone(const Json& document)
       pdns::rust::settings::rec::AuthZone authzone;
       authzone.zone = zonename;
       authzone.file = zonefilename;
-      pdns::rust::settings::rec::api_add_auth_zone(yamlAPiZonesFile, std::move(authzone));
+      pdns::rust::settings::rec::api_add_auth_zone(yamlAPIZonesFile, std::move(authzone));
     }
     else {
       apiWriteConfigFile(confbasename, "auth-zones+=" + zonename + "=" + zonefilename);
@@ -285,7 +285,7 @@ static void doCreateZone(const Json& document)
       for (const auto& value : document["servers"].array_items()) {
         forward.forwarders.emplace_back(value.string_value());
       }
-      pdns::rust::settings::rec::api_add_forward_zone(yamlAPiZonesFile, std::move(forward));
+      pdns::rust::settings::rec::api_add_forward_zone(yamlAPIZonesFile, std::move(forward));
     }
     else {
       string serverlist;