]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Threads started and doing xfr
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 30 Sep 2024 13:31:01 +0000 (15:31 +0200)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 4 Nov 2024 14:20:13 +0000 (15:20 +0100)
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-main.hh
pdns/recursordist/rec-xfr.cc
pdns/recursordist/rec-xfr.hh
pdns/recursordist/rec_channel_rec.cc
pdns/recursordist/settings/cxxsupport.cc
pdns/recursordist/settings/generate.py
pdns/recursordist/settings/rust-bridge-in.rs
pdns/recursordist/settings/rust/src/bridge.rs
pdns/recursordist/settings/table.py
pdns/recursordist/test-settings.cc

index c374db5a6bc02db480c247ad800ca4913269450f..c1fdeffe120ef8f15a8b65a89991ea8a2e0db398 100644 (file)
@@ -2321,7 +2321,7 @@ static int serviceMain(Logr::log_t log)
 
   {
     auto lci = g_luaconfs.getCopy();
-    startLuaConfigDelayedThreads(lci.rpzs, lci.generation);
+    startLuaConfigDelayedThreads(lci, lci.generation);
   }
 
   RecThreadInfo::makeThreadPipes(log);
@@ -3402,9 +3402,9 @@ struct WipeCacheResult wipeCaches(const DNSName& canon, bool subtree, uint16_t q
   return res;
 }
 
-void startLuaConfigDelayedThreads(const vector<RPZTrackerParams>& rpzs, uint64_t generation)
+void startLuaConfigDelayedThreads(const LuaConfigItems& luaConfig, uint64_t generation)
 {
-  for (const auto& rpzPrimary : rpzs) {
+  for (const auto& rpzPrimary : luaConfig.rpzs) {
     if (rpzPrimary.zoneXFRParams.primaries.empty()) {
       continue;
     }
@@ -3425,6 +3425,27 @@ void startLuaConfigDelayedThreads(const vector<RPZTrackerParams>& rpzs, uint64_t
       exit(1); // NOLINT(concurrency-mt-unsafe)
     }
   }
+  for (const auto& [fcz, zone] : luaConfig.catalogzones) {
+    if (fcz.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);
+      theThread.detach();
+    }
+    catch (const std::exception& e) {
+      SLOG(g_log << Logger::Error << "Problem starting ZoneIXFRTracker thread: " << e.what() << endl,
+           g_slog->withName("zone")->error(Logr::Error, e.what(), "Exception starting ZoneXFRTracker thread", "exception", Logging::Loggable("std::exception")));
+      exit(1); // NOLINT(concurrency-mt-unsafe)
+    }
+    catch (const PDNSException& e) {
+      SLOG(g_log << Logger::Error << "Problem starting ZoneIXFRTracker thread: " << e.reason << endl,
+           g_slog->withName("zone")->error(Logr::Error, e.reason, "Exception starting ZoneXFRTracker thread", "exception", Logging::Loggable("PDNSException")));
+      exit(1); // NOLINT(concurrency-mt-unsafe)
+    }
+  }
 }
 
 static void* pleaseInitPolCounts(const string& name)
@@ -3527,6 +3548,25 @@ static void activateRPZs(LuaConfigItems& lci)
   }
 }
 
+static void activateForwardingCatalogZones(LuaConfigItems& lci)
+{
+  size_t idx = 0;
+  for (auto& fcz : lci.catalogzones) {
+
+    auto& params = fcz.first;
+    params.zoneIdx = idx++;
+    auto zone = std::make_shared<Zone>();
+    if (params.zoneSizeHint != 0) {
+      //zone->reserve(params.zoneSizeHint);
+    }
+
+    DNSName domain(params.name);
+    zone->name = domain;
+    fcz.second = zone;
+  }
+}
+
+
 void activateLuaConfig(LuaConfigItems& lci)
 {
   if (!lci.trustAnchorFileInfo.fname.empty()) {
@@ -3540,5 +3580,6 @@ void activateLuaConfig(LuaConfigItems& lci)
     warnIfDNSSECDisabled("Warning: adding Negative Trust Anchor for DNSSEC, but dnssec is set to 'off'!");
   }
   activateRPZs(lci);
+  activateForwardingCatalogZones(lci);
   g_luaconfs.setState(lci);
 }
index 208206457e6dcf1fc48e8126e0fb4f5e0d403639..2654f8f5d521633c89c64e40d95fac4326b3fd3f 100644 (file)
@@ -621,7 +621,7 @@ void handleNewTCPQuestion(int fileDesc, FDMultiplexer::funcparam_t&);
 void makeUDPServerSockets(deferredAdd_t& deferredAdds, Logr::log_t);
 string doTraceRegex(FDWrapper file, vector<string>::const_iterator begin, vector<string>::const_iterator end);
 extern bool g_luaSettingsInYAML;
-void startLuaConfigDelayedThreads(const vector<RPZTrackerParams>& rpzs, uint64_t generation);
+void startLuaConfigDelayedThreads(const LuaConfigItems& luaConfig, uint64_t generation);
 void activateLuaConfig(LuaConfigItems& lci);
 unsigned int authWaitTimeMSec(const std::unique_ptr<MT_t>& mtasker);
 
index fc59c9a1f7393728619b3e807ef8b6e05c87294f..9f6e1ac2ad942f9e07bebda41b7d2ba8c86aed3b 100644 (file)
@@ -26,6 +26,7 @@
 #include "threadname.hh"
 #include "rec-lua-conf.hh"
 #include "query-local-address.hh"
+#include "axfr-retriever.hh"
 #include "ixfr.hh"
 
 // As there can be multiple threads doing updates (due to config reloads), we use a multimap.
@@ -70,6 +71,108 @@ void clearZoneTracker(const DNSName& zoneName)
   }
 }
 
+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)
+{
+
+  auto logger = plogger->withValues("primary", Logging::Loggable(primary));
+  logger->info(Logr::Info, "Loading zone from nameserver");
+  if (!tsigTriplet.name.empty()) {
+    logger->info(Logr::Info, "Using TSIG key for authentication", "tsig_key_name", Logging::Loggable(tsigTriplet.name), "tsig_key_algorithm", Logging::Loggable(tsigTriplet.algo));
+  }
+
+  ComboAddress local(localAddress);
+  if (local == ComboAddress()) {
+    local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
+  }
+
+  AXFRRetriever axfr(primary, zoneName, tsigTriplet, &local, maxReceivedBytes, axfrTimeout);
+  unsigned int nrecords = 0;
+  Resolver::res_t nop;
+  vector<DNSRecord> chunk;
+  time_t last = 0;
+  time_t axfrStart = time(nullptr);
+  time_t axfrNow = time(nullptr);
+  shared_ptr<const SOARecordContent> soaRecordContent;
+  // coverity[store_truncates_time_t]
+  while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow)) != 0) {
+    for (auto& dnsRecord : chunk) {
+      if (dnsRecord.d_type == QType::NS || dnsRecord.d_type == QType::TSIG) {
+        continue;
+      }
+
+      dnsRecord.d_name.makeUsRelative(zoneName);
+      if (dnsRecord.d_type == QType::SOA) {
+        soaRecordContent = getRR<SOARecordContent>(dnsRecord);
+        continue;
+      }
+
+      //RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
+      nrecords++;
+    }
+    axfrNow = time(nullptr);
+    if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
+      throw PDNSException("Total AXFR time exceeded!");
+    }
+    if (last != time(nullptr)) {
+      logger->info(Logr::Info, "Zone load in progress", "nrecords", Logging::Loggable(nrecords));
+      last = time(nullptr);
+    }
+  }
+
+  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)
+{
+  while (!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) {
+      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;
+        });
+
+        /* 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
+      }
+      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
+      }
+    }
+    // Release newZone before (long) sleep to reduce memory usage
+    newZone = nullptr;
+    if (!params.soaRecordContent) {
+      std::unique_lock lock(waiter.mutex);
+      waiter.condVar.wait_for(lock, std::chrono::seconds(refresh),
+                                 [&stop = waiter.stop] { return stop.load(); });
+    }
+    waiter.stop = false;
+    auto luaconfsLocal = g_luaconfs.getLocal();
+
+    if (luaconfsLocal->generation != configGeneration) {
+      /* the configuration has been reloaded, meaning that a new thread
+         has been started to handle that zone and we are now obsolete.
+      */
+      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)
 {
   // Don't hold on to oldZone, it well be re-assigned after sleep in the try block
@@ -261,6 +364,8 @@ void zoneXFRTracker(ZoneXFRParams params, uint64_t configGeneration) // NOLINT(p
 
   insertZoneTracker(zoneName, waiter);
 
+  preloadZoneFIle(params, zoneName, oldZone, refresh, configGeneration, waiter, logger);
+  bool skipRefreshDelay = isPreloaded;
   while (zoneTrackerIteration(params, zoneName, oldZone, refresh, skipRefreshDelay, configGeneration, waiter, logger)) {
     // empty
   }
index 2200ca877a6a6229894c6acb1e5ae26f2cbf192b..5a735f926aa0d2537a0df9a5ab81d22e58ce5378 100644 (file)
@@ -70,3 +70,4 @@ struct Zone
 bool notifyZoneTracker(const DNSName& name);
 void insertZoneTracker(const DNSName& zoneName, ZoneWaiter& waiter);
 void clearZoneTracker(const DNSName& zoneName);
+void zoneXFRTracker(ZoneXFRParams params, uint64_t configGeneration);
index f6876587237ac54e2961289d12a6045ee4bdefe9..e8a9aa84596281e5c5234d077986707baac9e7ac 100644 (file)
@@ -1934,7 +1934,7 @@ RecursorControlChannel::Answer luaconfig(bool broadcast)
       activateLuaConfig(lci);
       lci = g_luaconfs.getCopy();
       if (broadcast) {
-        startLuaConfigDelayedThreads(lci.rpzs, lci.generation);
+        startLuaConfigDelayedThreads(lci, lci.generation);
         broadcastFunction([=] { return pleaseSupplantProxyMapping(proxyMapping); });
       }
       else {
@@ -1972,7 +1972,7 @@ RecursorControlChannel::Answer luaconfig(bool broadcast)
     activateLuaConfig(lci);
     lci = g_luaconfs.getCopy();
     if (broadcast) {
-      startLuaConfigDelayedThreads(lci.rpzs, lci.generation);
+      startLuaConfigDelayedThreads(lci, lci.generation);
       broadcastFunction([pmap = std::move(proxyMapping)] { return pleaseSupplantProxyMapping(pmap); });
     }
     else {
index 4cfe53d1a2c52ca6f32a7f0b8886e921f1ca6c85..3d045f4370fd33561dc02497c79a1a7d650dd1c6 100644 (file)
@@ -541,7 +541,7 @@ static void processLine(const std::string& arg, FieldMap& map, bool mainFile)
   ::rust::String section;
   ::rust::String fieldname;
   ::rust::String type_name;
-  pdns::rust::settings::rec::Value rustvalue = {false, 0, 0.0, "", {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}};
+  pdns::rust::settings::rec::Value rustvalue = {false, 0, 0.0, "", {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}};
   if (pdns::settings::rec::oldKVToBridgeStruct(var, val, section, fieldname, type_name, rustvalue)) {
     auto overriding = !mainFile && !incremental && !simpleRustType(type_name);
     auto [existing, inserted] = map.emplace(std::pair{std::pair{section, fieldname}, pdns::rust::settings::rec::OldStyle{section, fieldname, var, std::move(type_name), rustvalue, overriding}});
@@ -671,6 +671,7 @@ std::string pdns::settings::rec::defaultsToYaml()
   def("recordcache", "zonetocaches", "Vec<ZoneToCache>");
   def("recursor", "allowed_additional_qtypes", "Vec<AllowedAdditionalQType>");
   def("incoming", "proxymappings", "Vec<ProxyMapping>");
+  def("recursor", "forwarding_catalog_zones", "Vec<ForwardingCatalogZone>");
   // End of should be generated XXX
 
   // Convert the map to a vector, as CXX does not have any dictionary like support.
@@ -713,7 +714,12 @@ std::string pdns::settings::rec::defaultsToYaml()
         oldname = std::string(iter->second.old_name);
       }
       res += "##### ";
-      res += arg().getHelp(oldname);
+      auto help = arg().getHelp(oldname);
+      if (help.empty()) {
+        replace(oldname.begin(), oldname.end(), '_', '-');
+        help = arg().getHelp(oldname);
+      }
+      res += help;
       res += '\n';
     }
     if (sectionChange) {
@@ -1297,6 +1303,30 @@ void fromRustToLuaConfig(const rust::Vec<pdns::rust::settings::rec::ProxyMapping
     proxyMapping.insert_or_assign(subnet, {address, smn});
   }
 }
+
+void fromRustToLuaConfig(const rust::Vec<pdns::rust::settings::rec::ForwardingCatalogZone>& catzones, std::vector<pair<ZoneXFRParams, std::shared_ptr<Zone>>>& lua)
+{
+  for (const auto& catz : catzones) {
+    cerr << "catz: " << catz.name << endl;
+    ZoneXFRParams params;
+    auto zone = std::make_shared<Zone>();
+
+    for (const auto& address : catz.xfr.addresses) {
+      ComboAddress combo = ComboAddress(std::string(address), 53);
+      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;
+    if (!catz.xfr.localAddress.empty()) {
+      params.localAddress = ComboAddress(std::string(catz.xfr.localAddress));
+    }
+    params.xfrTimeout = catz.xfr.axfrTimeout;
+    lua.emplace_back(params, zone);
+  }
+}
 }
 
 void pdns::settings::rec::fromBridgeStructToLuaConfig(const pdns::rust::settings::rec::Recursorsettings& settings, LuaConfigItems& luaConfig, ProxyMapping& proxyMapping)
@@ -1320,6 +1350,7 @@ void pdns::settings::rec::fromBridgeStructToLuaConfig(const pdns::rust::settings
   fromRustToLuaConfig(settings.recursor.sortlists, luaConfig.sortlist);
   fromRustToLuaConfig(settings.recordcache.zonetocaches, luaConfig.ztcConfigs);
   fromRustToLuaConfig(settings.recursor.allowed_additional_qtypes, luaConfig.allowAdditionalQTypes);
+  fromRustToLuaConfig(settings.recursor.forwarding_catalog_zones, luaConfig.catalogzones);
   fromRustToLuaConfig(settings.incoming.proxymappings, proxyMapping);
 }
 
index 549bfe38f4ffc14658d1d90b5d8deb2303445ebb..052866b90ea8cf566cb27f5c5be3976ab1fb0985 100644 (file)
@@ -109,6 +109,7 @@ class LType(Enum):
     ListSubnets = auto()
     ListTrustAnchors = auto()
     ListZoneToCaches = auto()
+    ListForwardingCatalogZones = auto()
     String = auto()
     Uint64 = auto()
 
@@ -116,7 +117,7 @@ listOfStringTypes = (LType.ListSocketAddresses,  LType.ListStrings, LType.ListSu
 listOfStructuredTypes = (LType.ListAuthZones, LType.ListForwardZones, LType.ListTrustAnchors, LType.ListNegativeTrustAnchors,
                          LType.ListProtobufServers, LType.ListDNSTapFrameStreamServers, LType.ListDNSTapNODFrameStreamServers,
                          LType.ListSortLists, LType.ListRPZs, LType.ListZoneToCaches, LType.ListAllowedAdditionalQTypes,
-                         LType.ListProxyMappings)
+                         LType.ListProxyMappings, LType.ListForwardingCatalogZones)
 
 def get_olddoc_typename(typ):
     """Given a type from table.py, return the old-style type name"""
@@ -180,6 +181,8 @@ def get_newdoc_typename(typ):
         return 'Sequence of `AllowedAdditionalQType`_'
     if typ == LType.ListProxyMappings:
         return 'Sequence of `ProxyMapping`_'
+    if typ == LType.ListForwardingCatalogZones:
+        return 'Sequence of `ForwardingCatalogZone`_'
     return 'Unknown' + str(typ)
 
 def get_default_olddoc_value(typ, val):
index 427da15c74a3f81bd268e90a70a4a6ca1a1c789a..5211d996d6b6fdf0ec05775050a611e683b052d0 100644 (file)
@@ -258,6 +258,51 @@ pub struct ApiZones {
     forward_zones: Vec<ForwardZone>,
 }
 
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct XFR {
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    addresses: Vec<String>,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    zoneSizeHint: u32,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    tsig: TSIGTriplet,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    refresh: u32,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    maxReceivedMBytes: u32,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    localAddress: String,
+    #[serde(default = "crate::U32::<20>::value", skip_serializing_if = "crate::U32::<20>::is_equal")]
+    axfrTimeout: u32,
+}
+
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct FCZDefault {
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    name: String,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    forwarders: Vec<String>,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    recurse: bool,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    allow_notify: bool,
+ }
+
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct ForwardingCatalogZone {
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    name: String,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    allow_notify: bool,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    xfr: XFR,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    groups: Vec<FCZDefault>,
+}
+
 // Two structs used to generated YAML based on a vector of name to value mappings
 // Cannot use Enum as CXX has only very basic Enum support
 struct Value {
@@ -278,6 +323,7 @@ struct Value {
     vec_zonetocache_val: Vec<ZoneToCache>,
     vec_allowedadditionalqtype_val: Vec<AllowedAdditionalQType>,
     vec_proxymapping_val: Vec<ProxyMapping>,
+    vec_forwardingcatalogzone_val: Vec<ForwardingCatalogZone>,
 }
 
 struct OldStyle {
index 4ac678e05cd9c3cc96850e17b4dc7d91a9194ae0..0abaf931b27df211575129346d86d9228b63eea0 100644 (file)
@@ -67,6 +67,27 @@ impl Default for ApiZones {
     }
 }
 
+impl Default for XFR {
+    fn default() -> Self {
+        let deserialized: XFR = serde_yaml::from_str("").unwrap();
+        deserialized
+    }
+}
+
+impl Default for FCZDefault {
+    fn default() -> Self {
+        let deserialized: FCZDefault = serde_yaml::from_str("").unwrap();
+        deserialized
+    }
+}
+
+impl Default for ForwardingCatalogZone {
+    fn default() -> Self {
+        let deserialized: ForwardingCatalogZone = serde_yaml::from_str("").unwrap();
+        deserialized
+    }
+}
+
 pub fn validate_socket_address(field: &str, val: &String) -> Result<(), ValidationError> {
     let sa = SocketAddr::from_str(val);
     if sa.is_err() {
@@ -634,6 +655,22 @@ impl TSIGTriplet {
     }
 }
 
+impl ForwardingCatalogZone {
+    pub fn validate(&self, _field: &str) -> Result<(), ValidationError> {
+        Ok(())
+    }
+
+    fn to_yaml_map(&self) -> serde_yaml::Value {
+        // XXX INCOMPLETE
+        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);
+        insertseq(&mut map, "groups", &seq);
+        serde_yaml::Value::Mapping(map)
+    }
+}
+
 #[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
 pub fn validate_auth_zones(field: &str, vec: &Vec<AuthZone>) -> Result<(), ValidationError> {
     validate_vec(field, vec, |field, element| element.validate(field))
@@ -855,6 +892,13 @@ pub fn map_to_yaml_string(vec: &Vec<OldStyle>) -> Result<String, serde_yaml::Err
                         }
                         serde_yaml::Value::Sequence(seq)
                     }
+                    "Vec<ForwardingCatalogZone>" => {
+                        let mut seq = serde_yaml::Sequence::new();
+                        for element in &entry.value.vec_forwardingcatalogzone_val {
+                            seq.push(element.to_yaml_map());
+                        }
+                        serde_yaml::Value::Sequence(seq)
+                    }
                     other => serde_yaml::Value::String(
                         "map_to_yaml_string: Unknown type: ".to_owned() + other,
                     ),
@@ -1100,3 +1144,4 @@ pub fn validate_recordcache(
 pub fn validate_snmp(_snmp: &recsettings::Snmp) -> Result<(), ValidationError> {
     Ok(())
 }
+
index 0ec0ec5ac6adb8d695a32f069463ca20b9da875e..d1603ad0e00b9ac82308c00be81da83cf2cd5762 100644 (file)
@@ -3535,4 +3535,16 @@ Load this Lua script on startup and shutdown and run the Lua function ``on_recur
         'skip-old' : 'No equivalent old-style setting',
         'versionadded': '5.2.0',
     },
+    {
+        'name' : 'forwarding_catalog_zones',
+        'section' : 'recursor',
+        'type' : LType.ListForwardingCatalogZones,
+        'default' : '',
+        'help' : 'Sequence of ForwardingCatalogZone',
+        'doc' : '''
+Sequence of ForwardingCatalogZone
+        ''',
+        'skip-old' : 'No equivalent old style setting',
+        'versionadded': '5.2.0',
+    },
 ]
index b6c0caa5561740946d7adf0400aeb6747aeaee03..831d6393c3c3ed908f2ef0c172f8c3aec76d53af 100644 (file)
@@ -886,6 +886,35 @@ BOOST_AUTO_TEST_CASE(test_yaml_proxymapping)
   BOOST_CHECK_EQUAL(settings.incoming.proxymappings[1].domains.size(), 3U);
 }
 
+BOOST_AUTO_TEST_CASE(test_yaml_forwardingcatalogzones)
+{
+  const std::string yaml = R"EOT(recursor:
+  forwarding_catalog_zones:
+  - name: 'forward.invalid'
+    xfr:
+      addresses: [192.168.178.3:53]
+    groups:
+      - forwarders: [1.2.3.4]
+      - name: mygroup
+        forwarders: [4.5.6.7]
+        recurse: true
+        allow_notify: true
+)EOT";
+
+  auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  settings.validate();
+  BOOST_CHECK_EQUAL(std::string(settings.recursor.forwarding_catalog_zones[0].name), "forward.invalid");
+  BOOST_CHECK_EQUAL(std::string(settings.recursor.forwarding_catalog_zones[0].xfr.addresses[0]), "192.168.178.3:53");
+  BOOST_CHECK_EQUAL(settings.recursor.forwarding_catalog_zones[0].groups[0].forwarders.size(), 1U);
+  BOOST_CHECK_EQUAL(std::string(settings.recursor.forwarding_catalog_zones[0].groups[0].forwarders[0]), "1.2.3.4");
+  BOOST_CHECK_EQUAL(std::string(settings.recursor.forwarding_catalog_zones[0].groups[0].name), "");
+
+  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_AUTO_TEST_CASE(test_yaml_to_luaconfigand_back)
 {
   const std::string yaml = R"EOT(dnssec: