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 \
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 \
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 \
#include "dnsseckeeper.hh"
#include "settings/cxxsettings.hh"
#include "json.hh"
-
+#include "rec-system-resolve.hh"
#ifdef NOD_ENABLED
#include "nod.hh"
#endif /* NOD_ENABLED */
});
}
else if (info.isHandler()) {
+ // static PeriodicTask systemResolveTask{"SysResolveCheckTask", 10};
+ // systemResolveTask.runIfDue(now, [] () {
+ // auto& sysResolver = pdns::RecResolve::getInstance();
+ // if (sysResolver.changeDetected()) {
+ // reloadZoneConfiguration(g_yamlSettings);
+ // }
+ // });
if (g_packetCache) {
static PeriodicTask packetCacheTask{"packetCacheTask", 5};
packetCacheTask.runIfDue(now, [now]() {
handleRuntimeDefaults(startupLog);
+ pdns::RecResolve::setInstanceParameters(60, []() { 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")) {
--- /dev/null
+/*
+ * 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 "rec-system-resolve.hh"
+#include "logging.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* res = nullptr;
+ auto ret = getaddrinfo(name.c_str(), nullptr, &hints, &res);
+ // We pick the first address returned for now
+ if (ret == 0) {
+ auto address = ComboAddress{res->ai_addr, res->ai_addrlen};
+ freeaddrinfo(res);
+ return address;
+ }
+ return {};
+}
+} // anonymous namespace
+
+std::function<void()> pdns::RecResolve::s_callback;
+time_t pdns::RecResolve::s_ttl;
+
+void pdns::RecResolve::setInstanceParameters(time_t ttl, const std::function<void()>& callback)
+{
+ pdns::RecResolve::s_ttl = ttl;
+ pdns::RecResolve::s_callback = callback;
+}
+
+pdns::RecResolve& pdns::RecResolve::getInstance()
+{
+ static unique_ptr<RecResolve> res = make_unique<pdns::RecResolve>(s_ttl, s_callback);
+ return *res;
+}
+
+pdns::RecResolve::RecResolve(time_t ttl, const std::function<void()>& callback) :
+ d_ttl(ttl), d_refresher(ttl / 6, callback, *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)
+{
+ 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 refrsh taks shol dnot 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;
+}
+
+bool pdns::RecResolve::changeDetected()
+{
+ bool change = d_refresher.changes.exchange(false);
+ return change;
+}
+
+pdns::RecResolve::Refresher::Refresher(time_t interval, const std::function<void()>& callback, pdns::RecResolve& res) :
+ d_resolver(res), d_callback(callback), d_interval(std::max(static_cast<time_t>(1), interval))
+{
+ start();
+}
+
+pdns::RecResolve::Refresher::~Refresher()
+{
+ finish();
+}
+
+void pdns::RecResolve::Refresher::refreshLoop()
+{
+ setThreadName("rec/sysres");
+
+ while (!stop) {
+ const time_t startTime = time(nullptr);
+ time_t wakeTime = startTime;
+ 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),
+ [&wakeup = wakeup] { return wakeup.load(); });
+ wakeup = false;
+ if (stop) {
+ break;
+ }
+ changes = d_resolver.refresh(time(nullptr));
+ wakeTime = time(nullptr);
+ if (changes) {
+ d_callback();
+ changes = false;
+ }
+ }
+ }
+}
+
+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();
+}
--- /dev/null
+/*
+ * 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 <functional>
+#include <thread>
+
+#include "namespaces.hh"
+#include "iputils.hh"
+#include "lock.hh"
+
+namespace pdns
+{
+class RecResolve
+{
+public:
+ // Should be called before any getInstance() call is done
+ static void setInstanceParameters(time_t ttl, const std::function<void()>& callback);
+ static RecResolve& getInstance();
+
+ RecResolve(time_t ttl = 60, const std::function<void()>& callback = nullptr);
+ ~RecResolve();
+ ComboAddress lookupAndRegister(const std::string& name, time_t now);
+ ComboAddress lookup(const std::string& name);
+ void stopRefresher();
+ void startRefresher();
+ void wipe(const std::string& name = "");
+ bool refresh(time_t now);
+ bool changeDetected();
+
+private:
+ 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;
+
+ class Refresher
+ {
+ public:
+ Refresher(time_t interval, const std::function<void()>& callback, 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();
+
+ std::atomic<bool> changes{false};
+ private:
+ void refreshLoop();
+
+ pdns::RecResolve& d_resolver;
+ std::function<void()> d_callback;
+ 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};
+ };
+
+ Refresher d_refresher;
+
+ static std::function<void()> s_callback;
+ static time_t s_ttl;
+};
+
+}
#include "rec-taskqueue.hh"
#include "rec-tcpout.hh"
#include "rec-main.hh"
+#include "rec-system-resolve.hh"
#include "settings/cxxsettings.hh"
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;
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)};
#include "syncres.hh"
#include "zoneparser-tng.hh"
#include "settings/cxxsettings.hh"
+#include "rec-system-resolve.hh"
extern int g_argc;
extern char** g_argv;
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) {
+ cerr << str.substr(pos) << endl;
+ 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;
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());
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"
dependencies = [
"cxx",
"cxx-build",
+ "hostname-validator",
"ipnet",
"once_cell",
"serde",
serde_yaml = "0.9"
ipnet = "2.8"
once_cell = "1.18.0"
+hostname-validator = "1.1.1"
[build-dependencies]
cxx-build = "1.0"
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 = SocketAddr::from_str(val);
+ if sa.is_err() {
+ let ip = IpAddr::from_str(val);
+ if ip.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);
validate_vec(
&(field.to_owned() + ".forwarders"),
&self.forwarders,
- validate_socket_address,
+ validate_socket_address_or_name,
)
}
--- /dev/null
+#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_resolee)
+{
+ auto sysResolve = pdns::RecResolve();
+
+ 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()
- zone: "example.com"
forwarders:
- 1.2.3.4
- - a.b
+ - '-a.b'
)EOT";
auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);