]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #13921 from omoerbeek/rec-system-resolve
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 26 Mar 2024 06:58:00 +0000 (07:58 +0100)
committerGitHub <noreply@github.com>
Tue, 26 Mar 2024 06:58:00 +0000 (07:58 +0100)
Rec: add feature to allow names (resolved by system resolver) in forwarding config

16 files changed:
pdns/recursordist/Makefile.am
pdns/recursordist/configure.ac
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-system-resolve.cc [new file with mode: 0644]
pdns/recursordist/rec-system-resolve.hh [new file with mode: 0644]
pdns/recursordist/rec_channel_rec.cc
pdns/recursordist/reczones.cc
pdns/recursordist/settings/docs-new-preamble-in.rst
pdns/recursordist/settings/rust/Cargo.lock
pdns/recursordist/settings/rust/Cargo.toml
pdns/recursordist/settings/rust/src/bridge.rs
pdns/recursordist/settings/rust/src/helpers.rs
pdns/recursordist/settings/table.py
pdns/recursordist/test-rec-system-resolve.cc [new file with mode: 0644]
pdns/recursordist/test-settings.cc
regression-tests.recursor-dnssec/test_NamedForward.py [new file with mode: 0644]

index 9bb70569495e2411ad1b7274a6c5e12fda28abbe..762f7a37622665c45e9428b71f859a4cd6c8a435 100644 (file)
@@ -176,6 +176,7 @@ pdns_recursor_SOURCES = \
        rec-protozero.cc rec-protozero.hh \
        rec-responsestats.hh rec-responsestats.cc \
        rec-snmp.hh rec-snmp.cc \
+       rec-system-resolve.hh rec-system-resolve.cc \
        rec-taskqueue.cc rec-taskqueue.hh \
        rec-tcounters.cc rec-tcounters.hh \
        rec-tcp.cc \
@@ -308,6 +309,7 @@ testrunner_SOURCES = \
        rcpgenerator.cc \
        rec-eventtrace.cc rec-eventtrace.hh \
        rec-responsestats.hh rec-responsestats.cc \
+       rec-system-resolve.hh rec-system-resolve.cc \
        rec-taskqueue.cc rec-taskqueue.hh \
        rec-tcounters.cc rec-tcounters.hh \
        rec-zonetocache.cc rec-zonetocache.hh \
@@ -349,6 +351,7 @@ testrunner_SOURCES = \
        test-negcache_cc.cc \
        test-packetcache_hh.cc \
        test-rcpgenerator_cc.cc \
+       test-rec-system-resolve.cc \
        test-rec-taskqueue.cc \
        test-rec-tcounters_cc.cc \
        test-rec-zonetocache.cc \
index 241e0386d19da438306d8f386fe043c23bfee72c..171f4afc7c240a911ea1ad09b5b0c4756aa14b80 100644 (file)
@@ -43,6 +43,9 @@ AC_SUBST([LIBDL], [$lt_cv_dlopen_libs])
 
 PDNS_CHECK_OS
 PDNS_CHECK_NETWORK_LIBS
+AC_SEARCH_LIBS([res_query], [resolv])
+# macOS uses an alternative name internally
+AC_SEARCH_LIBS([res_9_query], [resolv])
 PTHREAD_SET_NAME
 AC_FUNC_STRERROR_R
 
index cb4706a55cb938e15eeaeb3f1a426029d670f442..d4750e7098bbb823fc8cb90acc66804c269f8533 100644 (file)
@@ -41,7 +41,7 @@
 #include "dnsseckeeper.hh"
 #include "settings/cxxsettings.hh"
 #include "json.hh"
-
+#include "rec-system-resolve.hh"
 #ifdef NOD_ENABLED
 #include "nod.hh"
 #endif /* NOD_ENABLED */
@@ -3176,6 +3176,16 @@ int main(int argc, char** argv)
 
     handleRuntimeDefaults(startupLog);
 
+    if (auto ttl = ::arg().asNum("system-resolver-ttl"); ttl != 0) {
+      time_t interval = ttl;
+      if (::arg().asNum("system-resolver-interval") != 0) {
+        interval = ::arg().asNum("system-resolver-interval");
+      }
+      bool selfResolveCheck = ::arg().mustDo("system-resolver-self-resolve-check");
+      // Cannot use SyncRes::s_serverID, it is not set yet
+      pdns::RecResolve::setInstanceParameters(arg()["server-id"], ttl, interval, selfResolveCheck, []() { reloadZoneConfiguration(g_yamlSettings); });
+    }
+
     g_recCache = std::make_unique<MemRecursorCache>(::arg().asNum("record-cache-shards"));
     g_negCache = std::make_unique<NegCache>(::arg().asNum("record-cache-shards") / 8);
     if (!::arg().mustDo("disable-packetcache")) {
diff --git a/pdns/recursordist/rec-system-resolve.cc b/pdns/recursordist/rec-system-resolve.cc
new file mode 100644 (file)
index 0000000..c41a016
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+#include "dnsparser.hh"
+#include "dnsrecords.hh"
+#include "rec-system-resolve.hh"
+#include "logging.hh"
+#include "noinitvector.hh"
+#include "threadname.hh"
+
+namespace
+{
+ComboAddress resolve(const std::string& name)
+{
+  struct addrinfo hints = {};
+  hints.ai_flags = AI_ADDRCONFIG;
+  hints.ai_family = 0;
+
+  struct addrinfo* res0 = nullptr;
+  auto ret = getaddrinfo(name.c_str(), nullptr, &hints, &res0);
+  // We pick the first address after sorting for now, no handling of multiple addresses or AF selection.
+  vector<ComboAddress> vec;
+  if (ret != 0) {
+    return {};
+  }
+  auto* res = res0;
+  while (res != nullptr) {
+    try {
+      auto address = ComboAddress{res->ai_addr, res->ai_addrlen};
+      vec.emplace_back(address);
+    }
+    catch (...) {
+    }
+    res = res->ai_next;
+  }
+  freeaddrinfo(res0);
+  if (!vec.empty()) {
+    std::sort(vec.begin(), vec.end());
+    return vec.at(0);
+  }
+  return {};
+}
+
+PacketBuffer resolve(const string& name, QClass cls, QType type)
+{
+  PacketBuffer answer(512);
+  auto ret = res_query(name.c_str(), cls, type, answer.data(), static_cast<int>(answer.size()));
+  if (ret == -1) {
+    answer.resize(0);
+  }
+  else {
+    answer.resize(ret);
+  }
+  return answer;
+}
+
+// do a id.server/CH/TXT query
+std::string serverID()
+{
+  auto buffer = resolve("id.server", QClass::CHAOS, QType::TXT);
+  if (buffer.empty()) {
+    return {};
+  }
+
+  MOADNSParser parser(false, static_cast<const char*>(static_cast<void*>(buffer.data())), buffer.size());
+  if (parser.d_header.rcode != RCode::NoError || parser.d_answers.size() != 1) {
+    return {};
+  }
+  const auto& dnsrecord = parser.d_answers.at(0).first;
+  if (dnsrecord.d_type == QType::TXT) {
+    if (auto txt = getRR<TXTRecordContent>(dnsrecord); txt != nullptr) {
+      const auto& text = txt->d_text;
+      if (text.size() >= 2 && text.at(0) == '"' && text.at(text.size() - 1) == '"') {
+        // remove quotes around text
+        return txt->d_text.substr(1, txt->d_text.size() - 2);
+      }
+      return txt->d_text;
+    }
+  }
+  return {};
+}
+} // anonymous namespace
+
+// RecResolve class members.
+std::string pdns::RecResolve::s_serverID;
+time_t pdns::RecResolve::s_ttl{0};
+time_t pdns::RecResolve::s_interval{0};
+std::function<void()> pdns::RecResolve::s_callback;
+bool pdns::RecResolve::s_selfResolveCheck{false};
+
+void pdns::RecResolve::setInstanceParameters(std::string serverID, time_t ttl, time_t interval, bool selfResolveCheck, const std::function<void()>& callback)
+{
+  pdns::RecResolve::s_serverID = std::move(serverID);
+  pdns::RecResolve::s_ttl = ttl;
+  pdns::RecResolve::s_interval = interval;
+  pdns::RecResolve::s_selfResolveCheck = selfResolveCheck;
+  pdns::RecResolve::s_callback = callback;
+}
+
+pdns::RecResolve& pdns::RecResolve::getInstance()
+{
+  static unique_ptr<RecResolve> res = make_unique<pdns::RecResolve>(s_ttl, s_interval, s_selfResolveCheck, s_callback);
+  return *res;
+}
+
+pdns::RecResolve::RecResolve(time_t ttl, time_t interval, bool selfResolveCheck, const std::function<void()>& callback) :
+  d_ttl(ttl), d_refresher(interval, callback, selfResolveCheck, *this)
+{
+}
+
+pdns::RecResolve::~RecResolve() = default;
+
+void pdns::RecResolve::stopRefresher()
+{
+  d_refresher.finish();
+}
+
+void pdns::RecResolve::startRefresher()
+{
+  d_refresher.start();
+}
+
+ComboAddress pdns::RecResolve::lookupAndRegister(const std::string& name, time_t now)
+{
+  if (s_ttl == 0) {
+    throw PDNSException("config tried to resolve `" + name + "' but system resolver feature not enabled");
+  }
+  auto data = d_data.lock();
+  if (auto iter = data->d_map.find(name); iter != data->d_map.end()) {
+    if (iter->second.d_ttd < now) {
+      return iter->second.d_address;
+    }
+    // If it's stale, re-resolve below
+  }
+  // We keep the lock while resolving, even though this might take a while...
+  auto address = resolve(name);
+
+  time_t ttd = now + d_ttl;
+  auto iter = data->d_map.emplace(name, AddressData{address, ttd}).first;
+  return iter->second.d_address;
+}
+
+ComboAddress pdns::RecResolve::lookup(const std::string& name)
+{
+  auto data = d_data.lock();
+  if (auto iter = data->d_map.find(name); iter != data->d_map.end()) {
+    // always return it, even if it's stale
+    return iter->second.d_address;
+  }
+  throw PDNSException("system resolve of unregistered name: " + name);
+}
+
+void pdns::RecResolve::wipe(const string& name)
+{
+  auto data = d_data.lock();
+  if (name.empty()) {
+    data->d_map.clear();
+  }
+  else {
+    data->d_map.erase(name);
+  }
+}
+
+bool pdns::RecResolve::refresh(time_t now)
+{
+  // The refresh task should not take the lock for a long time, so we're working on a copy
+  ResolveData copy;
+  {
+    auto data = d_data.lock();
+    copy = *data;
+  }
+  std::map<std::string, AddressData> newData;
+
+  auto log = g_slog->withName("system-resolver");
+
+  bool updated = false;
+  for (const auto& entry : copy.d_map) {
+    if (entry.second.d_ttd <= now) {
+      auto newAddress = resolve(entry.first);
+      time_t ttd = now;
+      if (newAddress != ComboAddress()) {
+        // positive resolve, good for ttl
+        ttd += d_ttl;
+      }
+      else {
+        log->error(Logr::Error, "Name did not resolve", "name", Logging::Loggable(entry.first));
+      }
+      if (newAddress != entry.second.d_address) {
+        log->info(Logr::Debug, "Name resolved to new address",
+                  "name", Logging::Loggable(entry.first),
+                  "address", Logging::Loggable(newAddress.toString()));
+        // An address changed
+        updated = true;
+      }
+      newData.emplace(entry.first, AddressData{newAddress, ttd});
+    }
+  }
+
+  if (!newData.empty()) {
+    auto data = d_data.lock();
+    for (const auto& entry : newData) {
+      data->d_map.insert_or_assign(entry.first, entry.second);
+    }
+  }
+  if (updated) {
+    log->info(Logr::Info, "Changes in names detected");
+  }
+  return updated;
+}
+
+pdns::RecResolve::Refresher::Refresher(time_t interval, const std::function<void()>& callback, bool selfResolveCheck, pdns::RecResolve& res) :
+  d_resolver(res), d_callback(callback), d_interval(std::max(static_cast<time_t>(1), interval)), d_selfResolveCheck(selfResolveCheck)
+{
+  start();
+}
+
+pdns::RecResolve::Refresher::~Refresher()
+{
+  finish();
+}
+
+void pdns::RecResolve::Refresher::refreshLoop()
+{
+  setThreadName("rec/sysres");
+  time_t lastSelfCheck = 0;
+
+  while (!stop) {
+    const time_t startTime = time(nullptr);
+    time_t wakeTime = startTime;
+    // The expresion wakeTime - startTime is equal to the total amount of time slept
+    while (wakeTime - startTime < d_interval) {
+      std::unique_lock lock(mutex);
+      time_t remaining = d_interval - (wakeTime - startTime);
+      if (remaining <= 0) {
+        break;
+      }
+      condVar.wait_for(lock, std::chrono::seconds(remaining),
+                       [&doWakeup = wakeup] { return doWakeup.load(); });
+      wakeup = false;
+      if (stop) {
+        break;
+      }
+      if (d_selfResolveCheck && lastSelfCheck < time(nullptr) - 3600) {
+        lastSelfCheck = time(nullptr);
+        auto resolvedServerID = serverID();
+        if (resolvedServerID == s_serverID) {
+          auto log = g_slog->withName("system-resolver");
+          log->info(Logr::Error, "id.server/CH/TXT resolves to my own server identity", "id.server", Logging::Loggable(resolvedServerID));
+        }
+      }
+      bool changes = d_resolver.refresh(time(nullptr));
+      wakeTime = time(nullptr);
+      if (changes) {
+        d_callback();
+      }
+    }
+  }
+}
+
+void pdns::RecResolve::Refresher::finish()
+{
+  stop = true;
+  wakeup = true;
+  condVar.notify_one();
+  d_thread.join();
+}
+
+void pdns::RecResolve::Refresher::start()
+{
+  stop = false;
+  wakeup = false;
+  d_thread = std::thread([this]() { refreshLoop(); });
+}
+
+void pdns::RecResolve::Refresher::trigger()
+{
+  stop = true;
+  wakeup = true;
+  condVar.notify_one();
+}
diff --git a/pdns/recursordist/rec-system-resolve.hh b/pdns/recursordist/rec-system-resolve.hh
new file mode 100644 (file)
index 0000000..b3e199a
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "config.h"
+
+#include <condition_variable>
+#include <functional>
+#include <thread>
+
+#include "namespaces.hh"
+#include "iputils.hh"
+#include "lock.hh"
+
+/************************************************************************************************
+The pdns::RecResolve class implements a facility to use the system configured resolver. At the moment
+of writing, this can only be used to configure forwarding by name instead of IP.
+ ************************************************************************************************/
+
+/************************************************************************************************
+DESIGN CONSIDERATIONS
+
+- all names looked up with lookupAndRegister() will be entered into a table.
+
+- the names in the table will ber periodically checked by a refresh thread. Set the period (before
+  starting to use the system resolver) by calling pdns::RecResolve::setInstanceParameters().
+
+- if *a* name resolves to a different result than stored, we will call the callback. Currently this is
+   used to call the equivalent of rec_control reload-zones
+
+- A manual rec_control reload-zones will *also* flush the existing table before doing the reload, so
+  we force a re-resolve all names. See
+  rec_channel_rec.cc:reloadZoneConfigurationWithSysResolveReset()
+
+**************************************************************************************************/
+
+/************************************************************************************************
+PRACTICAL CONSIDERATIONS/IMPLEMENTATION LIMITS
+
+- Currently the facility is *only* used by the forwarding code
+
+- We resolve with AI_ADDRCONFIG, the address families enabled will depend on the network config
+  of the machine
+
+- We pick the first address that getaddrinfo() produced. Currently no handling of multiple addresses
+  and/or multiple address families.
+
+- There is a check to detect *some* cases of self-resolve. This is done by resolving
+  id.server/CH/TXT and comparing the result to the system-id set. Both false positives and false
+  negatives can occur.
+
+**************************************************************************************************/
+namespace pdns
+{
+class RecResolve
+{
+public:
+  // Should be called before any getInstance() call is done
+  static void setInstanceParameters(std::string serverID, time_t ttl, time_t interval, bool selfResolveCheck, const std::function<void()>& callback);
+  // Get "the" instance of the system resolver.
+  static RecResolve& getInstance();
+
+  RecResolve(time_t ttl, time_t interval, bool selfResolveCheck, const std::function<void()>& callback = nullptr);
+  ~RecResolve();
+  // Lookup a name and register it in the names to be checked if not already there
+  ComboAddress lookupAndRegister(const std::string& name, time_t now);
+  // Lookup a name which must be already registered
+  ComboAddress lookup(const std::string& name);
+
+  // When an instance is created, it will run a refresh thread, stop it with this method
+  void stopRefresher();
+  // And restart it again
+  void startRefresher();
+  // Wipe one or all names
+  void wipe(const std::string& name = "");
+
+private:
+  bool refresh(time_t now);
+  struct AddressData
+  {
+    ComboAddress d_address;
+    time_t d_ttd{0};
+  };
+  struct ResolveData
+  {
+    std::map<std::string, AddressData> d_map;
+  };
+  LockGuarded<ResolveData> d_data;
+  const time_t d_ttl;
+
+  // This private class implements the refresher thread
+  class Refresher
+  {
+  public:
+    Refresher(time_t interval, const std::function<void()>& callback, bool selfResolveCheck, pdns::RecResolve& res);
+    Refresher(const Refresher&) = delete;
+    Refresher(Refresher&&) = delete;
+    Refresher& operator=(const Refresher&) = delete;
+    Refresher& operator=(Refresher&&) = delete;
+    ~Refresher();
+
+    void start();
+    void finish();
+    void trigger();
+
+  private:
+    void refreshLoop();
+
+    pdns::RecResolve& d_resolver;
+    std::function<void()> d_callback;
+    const time_t d_interval;
+    std::thread d_thread;
+    std::mutex mutex;
+    std::condition_variable condVar;
+    std::atomic<bool> wakeup{false};
+    std::atomic<bool> stop{false};
+    const bool d_selfResolveCheck;
+  };
+
+  Refresher d_refresher;
+
+  static std::string s_serverID;
+  static std::function<void()> s_callback;
+  static time_t s_ttl;
+  static time_t s_interval;
+  static bool s_selfResolveCheck;
+};
+
+}
index 3ad2f9f5f6493ec8843b8e995124b58afe4bc960..8a3af52076cfa16fc7554af273bbc511bdfa1bb1 100644 (file)
@@ -38,6 +38,7 @@
 #include "rec-taskqueue.hh"
 #include "rec-tcpout.hh"
 #include "rec-main.hh"
+#include "rec-system-resolve.hh"
 
 #include "settings/cxxsettings.hh"
 
@@ -2174,6 +2175,16 @@ static RecursorControlChannel::Answer reloadACLs()
   return {0, "ok\n"};
 }
 
+static std::string reloadZoneConfigurationWithSysResolveReset()
+{
+  auto& sysResolver = pdns::RecResolve::getInstance();
+  sysResolver.stopRefresher();
+  sysResolver.wipe();
+  auto ret = reloadZoneConfiguration(g_yamlSettings);
+  sysResolver.startRefresher();
+  return ret;
+}
+
 RecursorControlChannel::Answer RecursorControlParser::getAnswer(int socket, const string& question, RecursorControlParser::func_t** command)
 {
   *command = nop;
@@ -2317,7 +2328,7 @@ RecursorControlChannel::Answer RecursorControlParser::getAnswer(int socket, cons
       g_log << Logger::Error << "Unable to reload zones and forwards when chroot()'ed, requested via control channel" << endl;
       return {1, "Unable to reload zones and forwards when chroot()'ed, please restart\n"};
     }
-    return {0, reloadZoneConfiguration(g_yamlSettings)};
+    return {0, reloadZoneConfigurationWithSysResolveReset()};
   }
   if (cmd == "set-ecs-minimum-ttl") {
     return {0, setMinimumECSTTL(begin, end)};
index ced34549a8e68b69a7b321efd6b35f3e25ed2009..f8d50bdfb9cb5aecc1c81616f81bb4d08d2592b5 100644 (file)
@@ -33,6 +33,7 @@
 #include "syncres.hh"
 #include "zoneparser-tng.hh"
 #include "settings/cxxsettings.hh"
+#include "rec-system-resolve.hh"
 
 extern int g_argc;
 extern char** g_argv;
@@ -62,6 +63,29 @@ bool primeHints(time_t now)
   return ret;
 }
 
+static ComboAddress fromNameOrIP(const string& str, uint16_t defPort, Logr::log_t log)
+{
+  try {
+    ComboAddress addr = parseIPAndPort(str, defPort);
+    return addr;
+  }
+  catch (const PDNSException&) {
+    uint16_t port = defPort;
+    string::size_type pos = str.rfind(':');
+    if (pos != string::npos) {
+      port = pdns::checked_stoi<uint16_t>(str.substr(pos + 1));
+    }
+    auto& res = pdns::RecResolve::getInstance();
+    ComboAddress address = res.lookupAndRegister(str.substr(0, pos), time(nullptr));
+    if (address != ComboAddress()) {
+      address.setPort(port);
+      return address;
+    }
+    log->error(Logr::Error, "Could not resolve name", "name", Logging::Loggable(str));
+    throw PDNSException("Could not resolve " + str);
+  }
+}
+
 static void convertServersForAD(const std::string& zone, const std::string& input, SyncRes::AuthDomain& authDomain, const char* sepa, Logr::log_t log, bool verbose = true)
 {
   vector<string> servers;
@@ -70,7 +94,7 @@ static void convertServersForAD(const std::string& zone, const std::string& inpu
 
   vector<string> addresses;
   for (auto& server : servers) {
-    ComboAddress addr = parseIPAndPort(server, 53);
+    ComboAddress addr = fromNameOrIP(server, 53, log);
     authDomain.d_servers.push_back(addr);
     if (verbose) {
       addresses.push_back(addr.toStringWithPort());
index 1beaed3ea81e1c317e3808aa903a35d98abe0e6a..a99eb572420e1958e214421f50d5359fd4c7ce9f 100644 (file)
@@ -168,6 +168,11 @@ An example of a ``forward_zones`` entry, which consists of a sequence of forward
     recurse: true
     notify_allowed: true
 
+Starting with version 5.1.0, names can be used if
+:ref:`setting-yaml-recursor.system_resolver_ttl` is set.
+The names will be resolved using the system resolver and an automatic refresh of the forwarding zones will happen if a name starts resolving to a new address.
+The refresh is done by performing the equivalent of ``rec_control reload-zones``.
+
 
 Auth Zone
 ^^^^^^^^^
index d1d48a14650c20b5839c90f3ed82beb51eb551c4..6c098259647e280e1407615560b9f023c21739f8 100644 (file)
@@ -77,6 +77,12 @@ version = "0.14.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
 
+[[package]]
+name = "hostname-validator"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2"
+
 [[package]]
 name = "indexmap"
 version = "2.1.0"
@@ -189,6 +195,7 @@ version = "0.1.0"
 dependencies = [
  "cxx",
  "cxx-build",
+ "hostname-validator",
  "ipnet",
  "once_cell",
  "serde",
index 89cef97885b99a19b9044f310fe68fa681d7c5da..c14e85d6e03fc9d6273887c8eb4fb9437f105c6c 100644 (file)
@@ -13,6 +13,7 @@ serde = { version = "1.0", features = ["derive"] }
 serde_yaml = "0.9"
 ipnet = "2.8"
 once_cell = "1.18.0"
+hostname-validator = "1.1.1" # This is temporary. PR 13819 has the infra to call C++ from, so we can arrange for DNSName::ishostname() to be called instead of importing another crate after that one is merged.
 
 [build-dependencies]
 cxx-build = "1.0"
index 181e23424247d1112d6d6106fdde6a11dc84a551..6de3318c2e44bf43575b6d688d8d30e0a672af8c 100644 (file)
@@ -67,6 +67,30 @@ pub fn validate_socket_address(field: &str, val: &String) -> Result<(), Validati
     Ok(())
 }
 
+fn is_port_number(str: &str) -> bool {
+    str.parse::<u16>().is_ok()
+}
+
+pub fn validate_socket_address_or_name(field: &str, val: &String) -> Result<(), ValidationError> {
+    let sa = validate_socket_address(field, val);
+    if sa.is_err() {
+        if !hostname_validator::is_valid(val) {
+            let parts: Vec<&str> = val.split(':').collect();
+            if parts.len() != 2
+                || !hostname_validator::is_valid(parts[0])
+                || !is_port_number(parts[1])
+            {
+                let msg = format!(
+                    "{}: value `{}' is not an IP, IP:port, name or name:port combination",
+                    field, val
+                );
+                return Err(ValidationError { msg });
+            }
+        }
+    }
+    Ok(())
+}
+
 fn validate_name(field: &str, val: &String) -> Result<(), ValidationError> {
     if val.is_empty() {
         let msg = format!("{}: value may not be empty", field);
@@ -139,9 +163,7 @@ pub fn parse_yaml_string_to_api_zones(str: &str) -> Result<ApiZones, serde_yaml:
     serde_yaml::from_str(str)
 }
 
-pub fn parse_yaml_string_to_allow_notify_for(
-    str: &str,
-) -> Result<Vec<String>, serde_yaml::Error> {
+pub fn parse_yaml_string_to_allow_notify_for(str: &str) -> Result<Vec<String>, serde_yaml::Error> {
     serde_yaml::from_str(str)
 }
 
@@ -159,7 +181,7 @@ impl ForwardZone {
         validate_vec(
             &(field.to_owned() + ".forwarders"),
             &self.forwarders,
-            validate_socket_address,
+            validate_socket_address_or_name,
         )
     }
 
@@ -216,10 +238,7 @@ pub fn validate_auth_zones(field: &str, vec: &Vec<AuthZone>) -> Result<(), Valid
 }
 
 #[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
-pub fn validate_forward_zones(
-    field: &str,
-    vec: &Vec<ForwardZone>,
-) -> Result<(), ValidationError> {
+pub fn validate_forward_zones(field: &str, vec: &Vec<ForwardZone>) -> Result<(), ValidationError> {
     validate_vec(field, vec, |field, element| element.validate(field))
 }
 
@@ -351,7 +370,9 @@ pub fn map_to_yaml_string(vec: &Vec<OldStyle>) -> Result<String, serde_yaml::Err
                         }
                         serde_yaml::Value::Sequence(seq)
                     }
-                    other => serde_yaml::Value::String("map_to_yaml_string: Unknown type: ".to_owned() + other),
+                    other => serde_yaml::Value::String(
+                        "map_to_yaml_string: Unknown type: ".to_owned() + other,
+                    ),
                 };
                 if entry.overriding {
                     let tagged_value = Box::new(serde_yaml::value::TaggedValue {
@@ -467,7 +488,10 @@ pub fn api_add_forward_zone(path: &str, forwardzone: ForwardZone) -> Result<(),
 }
 
 // This function is called from C++, it needs to acquire the lock
-pub fn api_add_forward_zones(path: &str, forwardzones: &mut Vec<ForwardZone>) -> Result<(), std::io::Error> {
+pub fn api_add_forward_zones(
+    path: &str,
+    forwardzones: &mut Vec<ForwardZone>,
+) -> Result<(), std::io::Error> {
     let _lock = LOCK.lock().unwrap();
     let mut zones = api_read_zones_locked(path, true)?;
     zones.forward_zones.append(forwardzones);
index 795d8a585500cf6054a4cb8397f5d7d2ad5b2a4d..a49786f6162600a278faed6187dca69a947edd89 100644 (file)
@@ -20,8 +20,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-use std::{error::Error, fmt};
 use crate::ValidationError;
+use std::{error::Error, fmt};
 
 /* Helper code for validation  */
 impl Error for ValidationError {}
@@ -64,10 +64,9 @@ pub fn is_default<T: Default + PartialEq>(t: &T) -> bool {
 
 pub const OVERRIDE_TAG: &str = "!override";
 
-pub fn is_overriding(m: &serde_yaml::Mapping, key: &str) -> bool{
+pub fn is_overriding(m: &serde_yaml::Mapping, key: &str) -> bool {
     if let Some(serde_yaml::Value::Tagged(vvv)) = m.get(key) {
         return vvv.tag == OVERRIDE_TAG;
     }
     false
 }
-
index e8c97295fdb8f4b1fb285b2526781014d09cc807..f518b7989bef5ad71e5cde2c16628ebb7e50d9b6 100644 (file)
@@ -2540,7 +2540,7 @@ Use 0 to disable.
         'section' : 'recursor',
         'type' : LType.ListStrings,
         'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128',
-        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*',
+        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\\*, ecs-v6-response-bits-\\*',
         'help' : 'List of statistics that are disabled when retrieving the complete list of statistics via the API',
         'doc' : '''
 A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
@@ -2569,7 +2569,7 @@ These statistics can still be retrieved individually by specifically asking for
         'section' : 'recursor',
         'type' : LType.ListStrings,
         'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
-        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*',
+        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\\*, ecs-v6-response-bits-\\*, cumul-answers-\\*, cumul-auth4answers-\\*, cumul-auth6answers-\\*',
         'help' : 'List of statistics that are prevented from being exported via Carbon',
         'doc' : '''
 A list of comma-separated statistic names, that are prevented from being exported via carbon for performance reasons.
@@ -2596,7 +2596,7 @@ A sequence of statistic names, that are prevented from being exported via carbon
         'section' : 'recursor',
         'type' : LType.ListStrings,
         'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
-        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*',
+        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\\*, ecs-v6-response-bits-\\*, cumul-answers-\\*, cumul-auth4answers-\\*, cumul-auth6answers-\\*',
         'help' : 'List of statistics that are prevented from being exported via rec_control get-all',
         'doc' : '''
 A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
@@ -2636,7 +2636,7 @@ Can be read out using ``rec_control top-remotes``.
         'section' : 'recursor',
         'type' : LType.ListStrings,
         'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
-        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*',
+        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\\*, ecs-v6-response-bits-\\*',
         'help' : 'List of statistics that are prevented from being exported via SNMP',
         'doc' : '''
 A list of comma-separated statistic names, that are prevented from being exported via SNMP, for performance reasons.
@@ -3113,4 +3113,54 @@ This can be used to not count known failing (test) name validations in the ordin
  ''',
     'versionadded': '4.5.0'
     },
+    {
+        'name' : 'system_resolver_ttl',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Set TTL of system resolver feature, 0 (default) is disabled',
+        'doc' : '''
+Sets TTL in seconds of the system resolver feature.
+If not equal to zero names can be used for forwarding targets.
+The names will be resolved by the system resolver configured in the OS.
+
+The TTL is used as a time to live to see if the names used in forwarding resolve to a different address than before.
+If the TTL is expired, a re-resolve will be done by the next iteration of the check function;
+if a change is detected, the recursor performs an equivalent of ``rec_control reload-zones``.
+
+Make sure the recursor itself is not used by the system resolver! Default is 0 (not enabled).
+A suggested value is 60.
+''',
+    'versionadded': '5.1.0'
+    },
+    {
+        'name' : 'system_resolver_interval',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Set interval (in seconds) of the re-resolve checks of system resolver subsystem.',
+        'doc' : '''
+Sets the check interval (in seconds) of the system resolver feature.
+All names known by the system resolver subsystem are periodically checked for changing values.
+
+If the TTL of a name has expired, it is checked by re-resolving it.
+if a change is detected, the recursor performs an equivalent of ``rec_control reload-zones``.
+
+This settings sets the interval between the checks.
+If set to zero (the default), the value :ref:`setting-system-resolver-ttl` is used.
+''',
+    'versionadded': '5.1.0'
+    },
+    {
+        'name' : 'system_resolver_self_resolve_check',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Check for potential self-resolve, default enabled.',
+        'doc' : '''
+Warn on potential self-resolve.
+If this check draws the wrong conclusion, you can disable it.
+''',
+    'versionadded': '5.1.0'
+    },
 ]
diff --git a/pdns/recursordist/test-rec-system-resolve.cc b/pdns/recursordist/test-rec-system-resolve.cc
new file mode 100644 (file)
index 0000000..c903c63
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#include <boost/test/unit_test.hpp>
+
+#include "rec-system-resolve.hh"
+
+BOOST_AUTO_TEST_SUITE(rec_system_resolve)
+
+BOOST_AUTO_TEST_CASE(test_basic_resolve)
+{
+
+  pdns::RecResolve::setInstanceParameters("foo", 60, 10, false, nullptr);
+  auto& sysResolve = pdns::RecResolve::getInstance();
+
+  auto address = sysResolve.lookupAndRegister("localhost", time(nullptr));
+  BOOST_CHECK(address.toString() == "127.0.0.1" || address.toString() == "::1");
+  address = sysResolve.lookup("localhost");
+  BOOST_CHECK(address.toString() == "127.0.0.1" || address.toString() == "::1");
+  sysResolve.wipe("localhost");
+  BOOST_CHECK_THROW(sysResolve.lookup("localhost"), PDNSException);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index 91cc367853363a178de42ff0270fc2db42dda71a..0e1eb5b002df47dfac27b79154998b63d8ec1869 100644 (file)
@@ -112,7 +112,7 @@ recursor:
     - zone: "example.com"
       forwarders:
         - 1.2.3.4
-        - a.b
+        - '-a.b'
 )EOT";
 
   auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
diff --git a/regression-tests.recursor-dnssec/test_NamedForward.py b/regression-tests.recursor-dnssec/test_NamedForward.py
new file mode 100644 (file)
index 0000000..018990b
--- /dev/null
@@ -0,0 +1,80 @@
+import dns
+import os
+import unittest
+import subprocess
+import time
+from recursortests import RecursorTest
+
+class testNamedForward(RecursorTest):
+    """
+    This is forwarding test using a name as target
+    """
+
+    _confdir = 'NamedForward'
+    _config_template = """
+dnssec=validate
+forward-zones-recurse=.=dns.quad9.net
+system-resolver-ttl=10
+    """
+
+    @classmethod
+    def setUpClass(cls):
+
+        # we don't need all the auth stuff
+        cls.setUpSockets()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+    def testA(self):
+        expected = dns.rrset.from_text('dns.google.', 0, dns.rdataclass.IN, 'A', '8.8.8.8', '8.8.4.4')
+        query = dns.message.make_query('dns.google', 'A', want_dnssec=True)
+        query.flags |= dns.flags.AD
+
+        res = self.sendUDPQuery(query)
+
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expected)
+        self.assertMatchingRRSIGInAnswer(res, expected)
+
+@unittest.skipUnless('ENABLE_SUDO_TESTS' in os.environ, "sudo is not available")
+class testNamedForwardWithChange(RecursorTest):
+    """
+    This is forwarding test using a name as target and a changing resolve
+    """
+
+    _confdir = 'NamedForwardWithChange'
+    _config_template = """
+dnssec=off
+forward-zones-recurse=.=namedforwardtest
+devonly-regression-test-mode
+system-resolver-ttl=1
+    """
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        subprocess.run(['sudo', 'sed', '-i', '-e', '/namedforwardtest/d', '/etc/hosts'])
+        subprocess.run(['sudo', 'sh', '-c', 'echo ' + cls._PREFIX + '.10 namedforwardtest >> /etc/hosts'])
+        super(testNamedForwardWithChange, cls).generateRecursorConfig(confdir)
+
+    def testExampleNSQuery(self):
+        query = dns.message.make_query('example', 'NS', want_dnssec=False)
+
+        expectedNS = dns.rrset.from_text('example.', 0, 'IN', 'NS', 'ns1.example.', 'ns2.example.')
+
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expectedNS)
+
+        subprocess.run(['sudo', 'sed', '-i', '-e', '/namedforwardtest/d', '/etc/hosts'])
+        subprocess.run(['sudo', 'sh', '-c', 'echo ' + self._PREFIX + '.12 namedforwardtest >> /etc/hosts'])
+
+        # the change should get picked up by the systemn resolver update thread and the reload flushes the caches
+        time.sleep(2)
+        res = self.sendUDPQuery(query)
+        subprocess.run(['sudo', 'sed', '-i', '-e', '/namedforwardtest/d', '/etc/hosts'])
+        self.assertRcodeEqual(res, dns.rcode.SERVFAIL)