*/
#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));
continue;
}
- //RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
+ zone->add(dnsRecord, logger);
nrecords++;
}
axfrNow = time(nullptr);
}
}
+ 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(); });
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;
}
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;
}
}
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) {
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);
}
}
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,
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"));
}
// 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;
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));
}
// 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);
}
+