From: Remi Gacogne Date: Fri, 7 Jan 2022 16:40:50 +0000 (+0100) Subject: dnsdist: Dynamic discovery and upgrade of backends X-Git-Tag: rec-4.7.0-alpha1~9^2~21 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=09708c45c3bd01c2ffcd31d0799fdd0d45bef135;p=thirdparty%2Fpdns.git dnsdist: Dynamic discovery and upgrade of backends --- diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 755e146e66..6ecdea0ec5 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -35,6 +35,7 @@ #include "dnsdist-carbon.hh" #include "dnsdist-console.hh" #include "dnsdist-dynblocks.hh" +#include "dnsdist-discovery.hh" #include "dnsdist-ecs.hh" #include "dnsdist-healthchecks.hh" #include "dnsdist-lua.hh" @@ -312,8 +313,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) setLuaSideEffect(); newserver_t vars; + DownstreamState::Config config; - ComboAddress serverAddr; std::string serverAddressStr; if (auto addrStr = boost::get(&pvars)) { serverAddressStr = *addrStr; @@ -327,7 +328,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } try { - serverAddr = ComboAddress(serverAddressStr, 53); + config.remote = ComboAddress(serverAddressStr, 53); } catch (const PDNSException& e) { g_outputBuffer = "Error creating new server: " + string(e.reason); @@ -340,14 +341,12 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) return std::shared_ptr(); } - if (IsAnyAddress(serverAddr)) { + if (IsAnyAddress(config.remote)) { g_outputBuffer = "Error creating new server: invalid address for a downstream server."; errlog("Error creating new server: %s is not a valid address for a downstream server", serverAddressStr); return std::shared_ptr(); } - DownstreamState::Config config; - if (vars.count("source")) { /* handle source in the following forms: - v4 address ("192.0.2.1") @@ -601,10 +600,47 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } } + bool autoUpgrade = false; + bool keepAfterUpgrade = false; + uint32_t upgradeInterval = 3600; + uint16_t upgradeDoHKey = 7; + std::string upgradePool; + + if (vars.count("autoUpgrade") && boost::get(vars.at("autoUpgrade"))) { + autoUpgrade = true; + + if (vars.count("autoUpgradeInterval")) { + try { + upgradeInterval = static_cast(std::stoul(boost::get(vars.at("autoUpgradeInterval")))); + } + catch (const std::exception& e) { + warnlog("Error parsing 'autoUpgradeInterval' value: %s", e.what()); + } + } + if (vars.count("autoUpgradeKeep")) { + keepAfterUpgrade = boost::get(vars.at("autoUpgradeKeep")); + } + if (vars.count("autoUpgradePool")) { + upgradePool = boost::get(vars.at("autoUpgradePool")); + } + if (vars.count("autoUpgradeDoHKey")) { + try { + upgradeDoHKey = static_cast(std::stoul(boost::get(vars.at("autoUpgradeDoHKey")))); + } + catch (const std::exception& e) { + warnlog("Error parsing 'autoUpgradeDoHKey' value: %s", e.what()); + } + } + } + // create but don't connect the socket in client or check-config modes auto ret = std::make_shared(std::move(config), std::move(tlsCtx), !(client || configCheck)); if (!(client || configCheck)) { - infolog("Added downstream server %s", serverAddr.toStringWithPort()); + infolog("Added downstream server %s", config.remote.toStringWithPort()); + } + + if (autoUpgrade) { + dnsdist::ServiceDiscovery::addUpgradeableServer(ret, upgradeInterval, upgradePool, upgradeDoHKey, keepAfterUpgrade); } /* this needs to be done _AFTER_ the order has been set, @@ -622,7 +658,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) if (ret->connected) { if (g_launchWork) { - g_launchWork->push_back([&ret]() { + g_launchWork->push_back([ret]() { ret->start(); }); } diff --git a/pdns/dnsdist-protocols.cc b/pdns/dnsdist-protocols.cc index 89b65ecac5..8af8fe8368 100644 --- a/pdns/dnsdist-protocols.cc +++ b/pdns/dnsdist-protocols.cc @@ -67,6 +67,11 @@ bool Protocol::operator==(Protocol::typeenum type) const return d_protocol == type; } +bool Protocol::operator!=(Protocol::typeenum type) const +{ + return d_protocol != type; +} + const std::string& Protocol::toString() const { return names.at(static_cast(d_protocol)); diff --git a/pdns/dnsdist-protocols.hh b/pdns/dnsdist-protocols.hh index a90e6e0c56..ddd74e3688 100644 --- a/pdns/dnsdist-protocols.hh +++ b/pdns/dnsdist-protocols.hh @@ -43,6 +43,7 @@ public: explicit Protocol(const std::string& protocol); bool operator==(typeenum) const; + bool operator!=(typeenum) const; const std::string& toString() const; const std::string& toPrettyString() const; diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 5aa8bc81c7..1e713fd297 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -48,6 +48,7 @@ #include "dnsdist-cache.hh" #include "dnsdist-carbon.hh" #include "dnsdist-console.hh" +#include "dnsdist-discovery.hh" #include "dnsdist-dynblocks.hh" #include "dnsdist-ecs.hh" #include "dnsdist-healthchecks.hh" @@ -2621,6 +2622,8 @@ int main(int argc, char** argv) } } + dnsdist::ServiceDiscovery::run(); + #ifndef DISABLE_CARBON thread carbonthread(carbonDumpThread); carbonthread.detach(); diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index fa5d8aa5b1..2a15da9086 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -138,6 +138,7 @@ dnsdist_SOURCES = \ dnsdist-cache.cc dnsdist-cache.hh \ dnsdist-carbon.cc dnsdist-carbon.hh \ dnsdist-console.cc dnsdist-console.hh \ + dnsdist-discovery.cc dnsdist-discovery.hh \ dnsdist-dnscrypt.cc \ dnsdist-dynblocks.cc dnsdist-dynblocks.hh \ dnsdist-dynbpf.cc dnsdist-dynbpf.hh \ diff --git a/pdns/dnsdistdist/dnsdist-discovery.cc b/pdns/dnsdistdist/dnsdist-discovery.cc new file mode 100644 index 0000000000..d1e06184ab --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-discovery.cc @@ -0,0 +1,504 @@ +/* + * 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 "config.h" +#include "dnsdist-discovery.hh" +#include "dnsdist.hh" +#include "dnsdist-random.hh" +#include "dnsparser.hh" +#include "dolog.hh" +#include "sstuff.hh" + +namespace dnsdist { + +const DNSName ServiceDiscovery::s_discoveryDomain{"_dns.resolver.arpa."}; +const QType ServiceDiscovery::s_discoveryType{QType::SVCB}; +const uint16_t ServiceDiscovery::s_defaultDoHSVCKey{7}; + +bool ServiceDiscovery::addUpgradeableServer(std::shared_ptr& server, uint32_t interval, std::string poolAfterUpgrade, uint16_t dohSVCKey, bool keepAfterUpgrade) +{ + s_upgradeableBackends.lock()->emplace_back(UpgradeableBackend{server, poolAfterUpgrade, 0, interval, dohSVCKey, keepAfterUpgrade}); + return true; +} + +struct DesignatedResolvers +{ + DNSName target; + std::set params; + std::vector hints; +}; + +static bool parseSVCParams(const PacketBuffer& answer, std::map& resolvers) +{ + if (answer.size() <= sizeof(struct dnsheader)) { + throw std::runtime_error("Looking for SVC records in a packet smaller than a DNS header"); + } + + std::map> hints; + const struct dnsheader* dh = reinterpret_cast(answer.data()); + PacketReader pr(pdns_string_view(reinterpret_cast(answer.data()), answer.size())); + uint16_t qdcount = ntohs(dh->qdcount); + uint16_t ancount = ntohs(dh->ancount); + uint16_t nscount = ntohs(dh->nscount); + uint16_t arcount = ntohs(dh->arcount); + + DNSName rrname; + uint16_t rrtype; + uint16_t rrclass; + + size_t idx = 0; + /* consume qd */ + for(; idx < qdcount; idx++) { + rrname = pr.getName(); + rrtype = pr.get16BitInt(); + rrclass = pr.get16BitInt(); + (void) rrtype; + (void) rrclass; + } + + /* parse AN */ + for (idx = 0; idx < ancount; idx++) { + string blob; + struct dnsrecordheader ah; + rrname = pr.getName(); + pr.getDnsrecordheader(ah); + + if (ah.d_type == QType::SVCB) { + auto prio = pr.get16BitInt(); + auto target = pr.getName(); + std::set params; + + if (prio != 0) { + pr.xfrSvcParamKeyVals(params); + } + + resolvers[prio] = { std::move(target), std::move(params), {} }; + } + else { + pr.xfrBlob(blob); + } + } + + /* parse NS */ + for (idx = 0; idx < nscount; idx++) { + string blob; + struct dnsrecordheader ah; + rrname = pr.getName(); + pr.getDnsrecordheader(ah); + + pr.xfrBlob(blob); + } + + /* parse additional for hints */ + for (idx = 0; idx < arcount; idx++) { + string blob; + struct dnsrecordheader ah; + rrname = pr.getName(); + pr.getDnsrecordheader(ah); + + if (ah.d_type == QType::A) { + ComboAddress addr; + pr.xfrCAWithoutPort(4, addr); + hints[rrname].push_back(addr); + } + else if (ah.d_type == QType::AAAA) { + ComboAddress addr; + pr.xfrCAWithoutPort(6, addr); + hints[rrname].push_back(addr); + } + else { + pr.xfrBlob(blob); + } + } + + for (auto& resolver : resolvers) { + auto hint = hints.find(resolver.second.target); + if (hint != hints.end()) { + resolver.second.hints = hint->second; + } + } + + return !resolvers.empty(); +} + +static bool handleSVCResult(const PacketBuffer& answer, const ComboAddress& existingAddr, uint16_t dohSVCKey, ServiceDiscovery::DiscoveredResolverConfig& config) +{ + std::map resolvers; + if (!parseSVCParams(answer, resolvers)) { + return false; + } + + for (const auto& [priority, resolver] : resolvers) { + /* do not compare the ports */ + std::set tentativeAddresses; + ServiceDiscovery::DiscoveredResolverConfig tempConfig; + tempConfig.d_addr.sin4.sin_family = 0; + + for (const auto& param : resolver.params) { + if (param.getKey() == SvcParam::alpn) { + auto alpns = param.getALPN(); + for (const auto& alpn : alpns) { + if (alpn == "dot") { + tempConfig.d_protocol = dnsdist::Protocol::DoT; + if (tempConfig.d_port == 0) { + tempConfig.d_port = 853; + } + } + else if (alpn == "h2") { + tempConfig.d_protocol = dnsdist::Protocol::DoH; + if (tempConfig.d_port == 0) { + tempConfig.d_port = 443; + } + } + } + } + else if (param.getKey() == SvcParam::port) { + tempConfig.d_port = param.getPort(); + } + else if (param.getKey() == SvcParam::ipv4hint || param.getKey() == SvcParam::ipv6hint) { + if (tempConfig.d_addr.sin4.sin_family == 0) { + auto hints = param.getIPHints(); + for (const auto& hint : hints) { + tentativeAddresses.insert(hint); + } + } + } + else if (dohSVCKey != 0 && param.getKey() == dohSVCKey) { + tempConfig.d_dohPath = param.getValue(); + auto expression = tempConfig.d_dohPath.find('{'); + if (expression != std::string::npos) { + /* nuke the {?dns} expression, if any, as we only support POST anyway */ + tempConfig.d_dohPath.resize(expression); + } + } + } + + if (tempConfig.d_protocol == dnsdist::Protocol::DoH){ +#ifndef HAVE_DNS_OVER_HTTPS + continue; +#endif + if (tempConfig.d_dohPath.empty()) { + continue; + } + } + else if (tempConfig.d_protocol == dnsdist::Protocol::DoT) { +#ifndef HAVE_DNS_OVER_TLS + continue; +#endif + } + else { + continue; + } + + /* we have a config that we can use! */ + + for (const auto& hint : resolver.hints) { + tentativeAddresses.insert(hint); + } + + /* we prefer the address we already know, whenever possible */ + if (tentativeAddresses.count(existingAddr) != 0) { + tempConfig.d_addr = existingAddr; + } + else { + tempConfig.d_addr = *tentativeAddresses.begin(); + } + + tempConfig.d_subjectName = resolver.target.toStringNoDot(); + tempConfig.d_addr.sin4.sin_port = tempConfig.d_port; + + config = tempConfig; + return true; + } + + return false; +} + +bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeableBackend, ServiceDiscovery::DiscoveredResolverConfig& config) +{ + const auto& backend = upgradeableBackend.d_ds; + const auto& addr = backend->d_config.remote; + try { + auto id = dnsdist::getRandomDNSID(); + PacketBuffer packet; + GenericDNSPacketWriter pw(packet, s_discoveryDomain, s_discoveryType); + pw.getHeader()->id = id; + pw.getHeader()->rd = 1; + pw.addOpt(4096, 0, 0); + + uint16_t querySize = static_cast(packet.size()); + const uint8_t sizeBytes[] = { static_cast(querySize / 256), static_cast(querySize % 256) }; + packet.insert(packet.begin(), sizeBytes, sizeBytes + 2); + + Socket sock(addr.sin4.sin_family, SOCK_STREAM); + sock.setNonBlocking(); + if (!IsAnyAddress(backend->d_config.sourceAddr)) { + sock.setReuseAddr(); +#ifdef IP_BIND_ADDRESS_NO_PORT + if (backend->d_config.ipBindAddrNoPort) { + SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1); + } +#endif + + if (!backend->d_config.sourceItfName.empty()) { +#ifdef SO_BINDTODEVICE + setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, backend->d_config.sourceItfName.c_str(), backend->d_config.sourceItfName.length()); +#endif + } + sock.bind(backend->d_config.sourceAddr); + } + sock.connect(addr, backend->d_config.tcpConnectTimeout); + + sock.writenWithTimeout(reinterpret_cast(packet.data()), packet.size(), backend->d_config.tcpSendTimeout); + + uint16_t responseSize = 0; + auto got = sock.readWithTimeout(reinterpret_cast(&responseSize), sizeof(responseSize), backend->d_config.tcpRecvTimeout); + if (got < 0 || static_cast(got) != sizeof(responseSize)) { + if (g_verbose) { + warnlog("Error while waiting for the ADD upgrade response from backend %s: %d", addr.toString(), got); + } + return false; + } + + packet.resize(ntohs(responseSize)); + + got = sock.readWithTimeout(reinterpret_cast(packet.data()), packet.size(), backend->d_config.tcpRecvTimeout); + if (got < 0 || static_cast(got) != packet.size()) { + if (g_verbose) { + warnlog("Error while waiting for the ADD upgrade response from backend %s: %d", addr.toString(), got); + } + return false; + } + + if (packet.size() <= sizeof(struct dnsheader)) { + if (g_verbose) { + warnlog("Too short answer of size %d received from the backend %s", packet.size(), addr.toString()); + } + return false; + } + + struct dnsheader d; + memcpy(&d, packet.data(), sizeof(d)); + if (d.id != id) { + if (g_verbose) { + warnlog("Invalid ID (%d / %d) received from the backend %s", d.id, id, addr.toString()); + } + return false; + } + + if (d.rcode != RCode::NoError) { + if (g_verbose) { + warnlog("Response code '%s' received from the backend %s for '%s'", RCode::to_s(d.rcode), addr.toString(), s_discoveryDomain); + } + + return false; + } + + if (ntohs(d.qdcount) != 1) { + if (g_verbose) { + warnlog("Invalid answer (qdcount %d) received from the backend %s", ntohs(d.qdcount), addr.toString()); + } + return false; + } + + uint16_t receivedType; + uint16_t receivedClass; + DNSName receivedName(reinterpret_cast(packet.data()), packet.size(), sizeof(dnsheader), false, &receivedType, &receivedClass); + + if (receivedName != s_discoveryDomain || receivedType != s_discoveryType || receivedClass != QClass::IN) { + if (g_verbose) { + warnlog("Invalid answer, either the qname (%s / %s), qtype (%s / %s) or qclass (%s / %s) does not match, received from the backend %s", receivedName, s_discoveryDomain, QType(receivedType).toString(), s_discoveryType.toString(), QClass(receivedClass).toString(), QClass::IN.toString(), addr.toString()); + } + return false; + } + + return handleSVCResult(packet, addr, upgradeableBackend.d_dohKey, config); + } + catch (const std::exception& e) { + errlog("Error while trying to discover backend upgrade for %s: %s", addr.toStringWithPort(), e.what()); + } + catch (...) { + errlog("Error while trying to discover backend upgrade for %s", addr.toStringWithPort()); + } + + return false; +} + +bool ServiceDiscovery::tryToUpgradeBackend(const UpgradeableBackend& backend) +{ + ServiceDiscovery::DiscoveredResolverConfig discoveredConfig; + + if (!ServiceDiscovery::getDiscoveredConfig(backend, discoveredConfig)) { + return false; + } + + if (discoveredConfig.d_protocol != dnsdist::Protocol::DoT && discoveredConfig.d_protocol != dnsdist::Protocol::DoH) { + return false; + } + + DownstreamState::Config config(backend.d_ds->d_config); + config.remote = discoveredConfig.d_addr; + if (discoveredConfig.d_port != 0) { + config.remote.setPort(discoveredConfig.d_port); + } + else { + if (discoveredConfig.d_protocol == dnsdist::Protocol::DoT) { + config.remote.setPort(853); + } + else if (discoveredConfig.d_protocol == dnsdist::Protocol::DoH) { + config.remote.setPort(443); + } + } + + ComboAddress::addressOnlyEqual comparator; + config.d_dohPath = discoveredConfig.d_dohPath; + if (comparator(config.remote, backend.d_ds->d_config.remote)) { + /* same address, we can used the supplied name for validation */ + config.d_tlsSubjectName = discoveredConfig.d_subjectName; + } + else { + /* different name, and draft-ietf-add-ddr-04 states that: + "In order to be considered a verified Designated Resolver, the TLS + certificate presented by the Designated Resolver MUST contain the IP + address of the designating Unencrypted Resolver in a subjectAltName + extension." + */ + config.d_tlsSubjectName = backend.d_ds->d_config.remote.toString(); + } + + if (!backend.d_poolAfterUpgrade.empty()) { + config.pools.clear(); + config.pools.insert(backend.d_poolAfterUpgrade); + } + + try { + /* create new backend, put it into the right pool(s) */ + TLSContextParameters tlsParams; + auto tlsCtx = getTLSContext(tlsParams); + auto newServer = std::make_shared(std::move(config), std::move(tlsCtx), true); + + infolog("Added automatically upgraded server %s", newServer->getNameWithAddr()); + + auto localPools = g_pools.getCopy(); + if (!newServer->d_config.pools.empty()) { + for (const auto& poolName : newServer->d_config.pools) { + addServerToPool(localPools, poolName, newServer); + } + } + else { + addServerToPool(localPools, "", newServer); + } + g_pools.setState(localPools); + + newServer->start(); + + auto states = g_dstates.getCopy(); + states.push_back(newServer); + /* remove the existing backend if needed */ + if (!backend.keepAfterUpgrade) { + for (auto it = states.begin(); it != states.end(); ++it) { + if (*it == backend.d_ds) { + states.erase(it); + break; + } + } + } + + std::stable_sort(states.begin(), states.end(), [](const decltype(newServer)& a, const decltype(newServer)& b) { + return a->d_config.order < b->d_config.order; + }); + g_dstates.setState(states); + + return true; + } + catch (const std::exception& e) { + warnlog("Error when trying to upgrade a discovered backend: %s", e.what()); + } + + return false; +} + +void ServiceDiscovery::worker() +{ + while (true) { + time_t now = time(nullptr); + + auto upgradeables = *(s_upgradeableBackends.lock()); + std::set> upgradedBackends; + + for (auto backendIt = upgradeables.begin(); backendIt != upgradeables.end(); ) { + try { + auto& backend = *backendIt; + if (backend.d_nextCheck > now) { + ++backendIt; + continue; + } + + auto upgraded = tryToUpgradeBackend(backend); + if (upgraded) { + upgradedBackends.insert(backend.d_ds); + backendIt = upgradeables.erase(backendIt); + } + else { + backend.d_nextCheck = now + backend.d_interval; + ++backendIt; + } + } + catch (const std::exception& e) { + vinfolog("Exception in the Service Discovery thread: %s", e.what()); + } + catch (...) { + vinfolog("Exception in the Service Discovery thread"); + } + } + + + { + auto backends = s_upgradeableBackends.lock(); + for (auto it = backends->begin(); it != backends->end(); ) { + if (upgradedBackends.count(it->d_ds) != 0) { + it = backends->erase(it); + } + else { + ++it; + } + } + } + + /* we could sleep until the next check but a new backend + could be added in the meantime, so let's just check every + minute if we have something to do */ + sleep(60); + } +} + +bool ServiceDiscovery::run() +{ + s_thread = std::thread(&ServiceDiscovery::worker); + s_thread.detach(); + + return true; +} + +LockGuarded> ServiceDiscovery::s_upgradeableBackends; +std::thread ServiceDiscovery::s_thread; +} diff --git a/pdns/dnsdistdist/dnsdist-discovery.hh b/pdns/dnsdistdist/dnsdist-discovery.hh new file mode 100644 index 0000000000..a2591c37f8 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-discovery.hh @@ -0,0 +1,77 @@ +/* + * 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 +#include + +#include "dnsname.hh" +#include "dnsdist-protocols.hh" +#include "iputils.hh" +#include "lock.hh" + +struct DownstreamState; + +namespace dnsdist { + +class ServiceDiscovery +{ +public: + + static bool addUpgradeableServer(std::shared_ptr& server, uint32_t interval, std::string poolAfterUpgrade, uint16_t dohSVCKey, bool keepAfterUpgrade); + + /* starts a background thread if needed */ + static bool run(); + + struct DiscoveredResolverConfig + { + ComboAddress d_addr; + std::string d_subjectName; + std::string d_dohPath; + uint16_t d_port{0}; + dnsdist::Protocol d_protocol; + }; + +private: + static const DNSName s_discoveryDomain; + static const QType s_discoveryType; + static const uint16_t s_defaultDoHSVCKey; + + struct UpgradeableBackend + { + std::shared_ptr d_ds; + std::string d_poolAfterUpgrade; + time_t d_nextCheck; + uint32_t d_interval; + uint16_t d_dohKey; + bool keepAfterUpgrade; + }; + + static bool getDiscoveredConfig(const UpgradeableBackend& backend, DiscoveredResolverConfig& config); + static bool tryToUpgradeBackend(const UpgradeableBackend& backend); + + static void worker(); + + static LockGuarded> s_upgradeableBackends; + static std::thread s_thread; +}; + +} diff --git a/pdns/dnsdistdist/dnsdist-secpoll.cc b/pdns/dnsdistdist/dnsdist-secpoll.cc index 4f5a9994bc..eb38d356c6 100644 --- a/pdns/dnsdistdist/dnsdist-secpoll.cc +++ b/pdns/dnsdistdist/dnsdist-secpoll.cc @@ -90,7 +90,7 @@ static std::string getFirstTXTAnswer(const std::string& answer) static std::string getSecPollStatus(const std::string& queriedName, int timeout=2) { - const DNSName& sentName = DNSName(queriedName); + const DNSName sentName(queriedName); std::vector packet; DNSPacketWriter pw(packet, sentName, QType::TXT); pw.getHeader()->id = dnsdist::getRandomDNSID(); diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index b7c4b89749..62b7b3cf85 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -532,6 +532,9 @@ Servers .. versionchanged:: 1.7.0 Added ``addXForwardedHeaders``, ``caStore``, ``checkTCP``, ``ciphers``, ``ciphers13``, ``dohPath``, ``enableRenegotiation``, ``releaseBuffers``, ``subjectName``, ``tcpOnly``, ``tls`` and ``validateCertificates`` to server_table. + .. versionchanged:: 1.8.0 + Added ``autoUpgrade``, ``autoUpgradeDoHKey``, ``autoUpgradeInterval``, ``autoUpgradeKeep`` and ``autoUpgradePool`` to server_table. + Add a new backend server. Call this function with either a string:: newServer( @@ -589,7 +592,12 @@ Servers dohPath=STRING, -- Enable DNS over HTTPS communication for this backend, using POST queries to the HTTP host supplied as ``subjectName`` and the HTTP path supplied in this parameter. addXForwardedHeaders=BOOL,-- Whether to add X-Forwarded-For, X-Forwarded-Port and X-Forwarded-Proto headers to a DNS over HTTPS backend. releaseBuffers=BOOL, -- Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection. Default to true. - enableRenegotiation=BOOL -- Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS. + enableRenegotiation=BOOL, -- Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS. + autoUpgrade=BOOL, -- Whether to use the 'Discovery of Designated Resolvers' mechanism to automatically upgrade an Do53 backend to DoT or DoH. Default to false. + autoUpgradeInterval=NUM, -- If ``autoUpgrade`` is set, how often to check if an upgrade is available, in seconds. Default is 3600 seconds. + autoUpgradeKeep=BOOL, -- If ``autoUpgrade`` is set, whether to keep the existing Do53 backend around after an upgrade. Default is false which means the Do53 backend will be replaced by the upgraded one. + autoUpgradePool=STRING, -- If ``autoUpgrade`` is set, in which pool to place the newly upgraded backend. Default is empty which means the backend is placed in the default pool. + autoUpgradeDoHKey=NUM -- If ``autoUpgrade`` is set, the value to use for the SVC key corresponding to the DoH path. Default is 7. }) :param str server_string: A simple IP:PORT string.