From: Otto Moerbeek Date: Tue, 1 Oct 2024 13:58:44 +0000 (+0200) Subject: Basic functionality works X-Git-Tag: rec-5.2.0-alpha1~7^2~17 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1ee3f5784f40a253f33e54d9b01b379b33523828;p=thirdparty%2Fpdns.git Basic functionality works Missing: group handling, cleanup of catz no longer in config, tests --- diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 6f90adc1d5..482879ea2e 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -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 \ diff --git a/pdns/recursordist/pdns_recursor.cc b/pdns/recursordist/pdns_recursor.cc index 251b06c87e..4ab069e149 100644 --- a/pdns/recursordist/pdns_recursor.cc +++ b/pdns/recursordist/pdns_recursor.cc @@ -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); } diff --git a/pdns/recursordist/rec-lua-conf.hh b/pdns/recursordist/rec-lua-conf.hh index 065da50db6..de794c2f17 100644 --- a/pdns/recursordist/rec-lua-conf.hh +++ b/pdns/recursordist/rec-lua-conf.hh @@ -108,7 +108,7 @@ public: SortList sortlist; DNSFilterEngine dfe; vector rpzs; - vector>> catalogzones; + vector catalogzones; TrustAnchorFileInfo trustAnchorFileInfo; // Used to update the Trust Anchors from file periodically map dsAnchors; map negAnchors; diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index c1fdeffe12..8d146c5809 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -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(); + auto zone = std::make_shared(); 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); } + diff --git a/pdns/recursordist/rec-xfr.cc b/pdns/recursordist/rec-xfr.cc index 9f6e1ac2ad..e8a5e6518b 100644 --- a/pdns/recursordist/rec-xfr.cc +++ b/pdns/recursordist/rec-xfr.cc @@ -21,57 +21,139 @@ */ #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> 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(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(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 values; + bool invalid = false; + for (const auto& [key, record] : d_records) { + if (key.second != QType::PTR) { + continue; + } + const auto ptr = getRR(record); + if (ptr == nullptr) { + invalid = true; + continue; + } + if (!values.insert(ptr->getContent()).second) { + invalid = true; break; } - ++start; } + return !invalid; } -static shared_ptr 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 loadZoneFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, shared_ptr& 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 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 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& oldZone, uint32_t& refresh, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger) +void ZoneXFR::preloadZoneFile(const DNSName& zoneName, std::shared_ptr& 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 newZone = std::make_shared(*oldZone); - for (const auto& primary : params.primaries) { + auto newZone = std::make_shared(*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& oldZone, uint32_t& refresh, bool& skipRefreshDelay, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger) +bool ZoneXFR::zoneTrackerIteration(const DNSName& zoneName, std::shared_ptr& 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, vector>> deltas; - for (const auto& primary : params.primaries) { + for (const auto& primary : d_params.primaries) { auto soa = getRR(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 newZone = std::make_shared(*oldZone); + auto newZone = std::make_shared(*oldZone); /* initialize the current serial to the last one */ - std::shared_ptr currentSR = params.soaRecordContent; + std::shared_ptr 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 oldZone; + std::shared_ptr 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); } + diff --git a/pdns/recursordist/rec-xfr.hh b/pdns/recursordist/rec-xfr.hh index 5a735f926a..3e77806497 100644 --- a/pdns/recursordist/rec-xfr.hh +++ b/pdns/recursordist/rec-xfr.hh @@ -28,9 +28,14 @@ #include #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 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, DNSRecord> d_records; + DNSName d_name; + uint32_t d_refresh{0}; + uint32_t d_serial{0}; }; -struct Zone +struct FWCatz { - std::vector d_records; - DNSName name; - uint32_t refresh{0}; - uint32_t serial{0}; + ZoneXFRParams d_params; + std::map d_defaults; + std::shared_ptr 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 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& oldZone, uint32_t& refresh, uint64_t configGeneration, ZoneWaiter& waiter, Logr::log_t logger); + bool zoneTrackerIteration(const DNSName& zoneName, std::shared_ptr& 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> 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 index 0000000000..4941c71bda --- /dev/null +++ b/pdns/recursordist/rec-xfrtracker.cc @@ -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> 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; + } +} diff --git a/pdns/recursordist/reczones.cc b/pdns/recursordist/reczones.cc index 1fa8361fbf..7831b1afd6 100644 --- a/pdns/recursordist/reczones.cc +++ b/pdns/recursordist/reczones.cc @@ -313,12 +313,12 @@ static void processForwardZones(shared_ptr& newMap, Logr:: } } -static void processApiZonesFile(shared_ptr& newMap, shared_ptr& newSet, Logr::log_t log) +static void processApiZonesFile(const string& file, shared_ptr& newMap, shared_ptr& 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> 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); diff --git a/pdns/recursordist/rpzloader.cc b/pdns/recursordist/rpzloader.cc index ad46ebaebf..46fcffb0c0 100644 --- a/pdns/recursordist/rpzloader.cc +++ b/pdns/recursordist/rpzloader.cc @@ -435,7 +435,7 @@ static bool dumpZoneToDisk(Logr::log_t logger, const std::shared_ptr& 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& 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& 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& 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 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); } diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index 3d045f4370..d465fd0bc1 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -1304,27 +1304,29 @@ void fromRustToLuaConfig(const rust::Vec& catzones, std::vector>>& lua) +void fromRustToLuaConfig(const rust::Vec& catzones, std::vector& lua) { for (const auto& catz : catzones) { - cerr << "catz: " << catz.name << endl; - ZoneXFRParams params; - auto zone = std::make_shared(); + FWCatz fwcatz; + for (const auto& def : catz.groups) { + fwcatz.d_defaults.emplace(def.name, def); + } + fwcatz.d_catz = std::make_shared(); 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)); } } } diff --git a/pdns/recursordist/settings/rust-bridge-in.rs b/pdns/recursordist/settings/rust-bridge-in.rs index 5211d996d6..2fd4f74622 100644 --- a/pdns/recursordist/settings/rust-bridge-in.rs +++ b/pdns/recursordist/settings/rust-bridge-in.rs @@ -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) -> Result<()>; fn validate_negativetrustanchors(field: &str, vec: &Vec) -> Result<()>; fn api_delete_zone(file: &str, zone: &str) -> Result<()>; + fn api_delete_zones(file: &str) -> Result<()>; } unsafe extern "C++" { diff --git a/pdns/recursordist/settings/rust/src/bridge.rs b/pdns/recursordist/settings/rust/src/bridge.rs index 0abaf931b2..d39d787696 100644 --- a/pdns/recursordist/settings/rust/src/bridge.rs +++ b/pdns/recursordist/settings/rust/src/bridge.rs @@ -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 { vec![ String::from("A"), diff --git a/pdns/recursordist/test-settings.cc b/pdns/recursordist/test-settings.cc index 831d6393c3..ac103d977b 100644 --- a/pdns/recursordist/test-settings.cc +++ b/pdns/recursordist/test-settings.cc @@ -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) diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index d6d8ed89c6..36569d29c2 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -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;