2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "dnsdist-discovery.hh"
26 #include "dnsdist-random.hh"
27 #include "dnsparser.hh"
30 #include "threadname.hh"
35 const DNSName
ServiceDiscovery::s_discoveryDomain
{"_dns.resolver.arpa."};
36 const QType
ServiceDiscovery::s_discoveryType
{QType::SVCB
};
37 const uint16_t ServiceDiscovery::s_defaultDoHSVCKey
{7};
39 bool ServiceDiscovery::addUpgradeableServer(std::shared_ptr
<DownstreamState
>& server
, uint32_t interval
, std::string poolAfterUpgrade
, uint16_t dohSVCKey
, bool keepAfterUpgrade
)
41 s_upgradeableBackends
.lock()->push_back(std::make_shared
<UpgradeableBackend
>(UpgradeableBackend
{server
, std::move(poolAfterUpgrade
), 0, interval
, dohSVCKey
, keepAfterUpgrade
}));
45 struct DesignatedResolvers
48 std::set
<SvcParam
> params
;
49 std::vector
<ComboAddress
> hints
;
52 static bool parseSVCParams(const PacketBuffer
& answer
, std::map
<uint16_t, DesignatedResolvers
>& resolvers
)
54 std::map
<DNSName
, std::vector
<ComboAddress
>> hints
;
55 const dnsheader_aligned
dh(answer
.data());
56 PacketReader
pr(std::string_view(reinterpret_cast<const char*>(answer
.data()), answer
.size()));
57 uint16_t qdcount
= ntohs(dh
->qdcount
);
58 uint16_t ancount
= ntohs(dh
->ancount
);
59 uint16_t nscount
= ntohs(dh
->nscount
);
60 uint16_t arcount
= ntohs(dh
->arcount
);
68 for (; idx
< qdcount
; idx
++) {
69 rrname
= pr
.getName();
70 rrtype
= pr
.get16BitInt();
71 rrclass
= pr
.get16BitInt();
77 for (idx
= 0; idx
< ancount
; idx
++) {
79 struct dnsrecordheader ah
;
80 rrname
= pr
.getName();
81 pr
.getDnsrecordheader(ah
);
83 if (ah
.d_type
== QType::SVCB
) {
84 auto prio
= pr
.get16BitInt();
85 auto target
= pr
.getName();
86 std::set
<SvcParam
> params
;
89 pr
.xfrSvcParamKeyVals(params
);
92 resolvers
[prio
] = {std::move(target
), std::move(params
), {}};
100 for (idx
= 0; idx
< nscount
; idx
++) {
102 struct dnsrecordheader ah
;
103 rrname
= pr
.getName();
104 pr
.getDnsrecordheader(ah
);
109 /* parse additional for hints */
110 for (idx
= 0; idx
< arcount
; idx
++) {
112 struct dnsrecordheader ah
;
113 rrname
= pr
.getName();
114 pr
.getDnsrecordheader(ah
);
116 if (ah
.d_type
== QType::A
) {
118 pr
.xfrCAWithoutPort(4, addr
);
119 hints
[rrname
].push_back(addr
);
121 else if (ah
.d_type
== QType::AAAA
) {
123 pr
.xfrCAWithoutPort(6, addr
);
124 hints
[rrname
].push_back(addr
);
131 for (auto& resolver
: resolvers
) {
132 auto hint
= hints
.find(resolver
.second
.target
);
133 if (hint
!= hints
.end()) {
134 resolver
.second
.hints
= hint
->second
;
138 return !resolvers
.empty();
141 static bool handleSVCResult(const PacketBuffer
& answer
, const ComboAddress
& existingAddr
, uint16_t dohSVCKey
, ServiceDiscovery::DiscoveredResolverConfig
& config
)
143 std::map
<uint16_t, DesignatedResolvers
> resolvers
;
144 if (!parseSVCParams(answer
, resolvers
)) {
145 vinfolog("No configuration found in response for backend %s", existingAddr
.toStringWithPort());
149 for (const auto& [priority
, resolver
] : resolvers
) {
151 /* do not compare the ports */
152 std::set
<ComboAddress
, ComboAddress::addressOnlyLessThan
> tentativeAddresses
;
153 ServiceDiscovery::DiscoveredResolverConfig tempConfig
;
154 tempConfig
.d_addr
.sin4
.sin_family
= 0;
156 for (const auto& param
: resolver
.params
) {
157 if (param
.getKey() == SvcParam::alpn
) {
158 auto alpns
= param
.getALPN();
159 for (const auto& alpn
: alpns
) {
161 tempConfig
.d_protocol
= dnsdist::Protocol::DoT
;
162 if (tempConfig
.d_port
== 0) {
163 tempConfig
.d_port
= 853;
166 else if (alpn
== "h2") {
167 tempConfig
.d_protocol
= dnsdist::Protocol::DoH
;
168 if (tempConfig
.d_port
== 0) {
169 tempConfig
.d_port
= 443;
174 else if (param
.getKey() == SvcParam::port
) {
175 tempConfig
.d_port
= param
.getPort();
177 else if (param
.getKey() == SvcParam::ipv4hint
|| param
.getKey() == SvcParam::ipv6hint
) {
178 if (tempConfig
.d_addr
.sin4
.sin_family
== 0) {
179 auto hints
= param
.getIPHints();
180 for (const auto& hint
: hints
) {
181 tentativeAddresses
.insert(hint
);
185 else if (dohSVCKey
!= 0 && param
.getKey() == dohSVCKey
) {
186 tempConfig
.d_dohPath
= param
.getValue();
187 auto expression
= tempConfig
.d_dohPath
.find('{');
188 if (expression
!= std::string::npos
) {
189 /* nuke the {?dns} expression, if any, as we only support POST anyway */
190 tempConfig
.d_dohPath
.resize(expression
);
195 if (tempConfig
.d_protocol
== dnsdist::Protocol::DoH
) {
196 #ifndef HAVE_DNS_OVER_HTTPS
199 if (tempConfig
.d_dohPath
.empty()) {
200 vinfolog("Got a DoH upgrade offered for %s but no path, skipping", existingAddr
.toStringWithPort());
204 else if (tempConfig
.d_protocol
== dnsdist::Protocol::DoT
) {
205 #ifndef HAVE_DNS_OVER_TLS
213 /* we have a config that we can use! */
214 for (const auto& hint
: resolver
.hints
) {
215 tentativeAddresses
.insert(hint
);
218 /* we prefer the address we already know, whenever possible */
219 if (tentativeAddresses
.count(existingAddr
) != 0) {
220 tempConfig
.d_addr
= existingAddr
;
223 tempConfig
.d_addr
= *tentativeAddresses
.begin();
226 tempConfig
.d_subjectName
= resolver
.target
.toStringNoDot();
227 tempConfig
.d_addr
.sin4
.sin_port
= tempConfig
.d_port
;
229 config
= std::move(tempConfig
);
236 bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend
& upgradeableBackend
, ServiceDiscovery::DiscoveredResolverConfig
& config
)
238 const auto& backend
= upgradeableBackend
.d_ds
;
239 const auto& addr
= backend
->d_config
.remote
;
241 auto id
= dnsdist::getRandomDNSID();
243 GenericDNSPacketWriter
pw(packet
, s_discoveryDomain
, s_discoveryType
);
244 pw
.getHeader()->id
= id
;
245 pw
.getHeader()->rd
= 1;
246 pw
.addOpt(4096, 0, 0);
249 uint16_t querySize
= static_cast<uint16_t>(packet
.size());
250 const uint8_t sizeBytes
[] = {static_cast<uint8_t>(querySize
/ 256), static_cast<uint8_t>(querySize
% 256)};
251 packet
.insert(packet
.begin(), sizeBytes
, sizeBytes
+ 2);
253 Socket
sock(addr
.sin4
.sin_family
, SOCK_STREAM
);
254 sock
.setNonBlocking();
256 #ifdef SO_BINDTODEVICE
257 if (!backend
->d_config
.sourceItfName
.empty()) {
258 setsockopt(sock
.getHandle(), SOL_SOCKET
, SO_BINDTODEVICE
, backend
->d_config
.sourceItfName
.c_str(), backend
->d_config
.sourceItfName
.length());
262 if (!IsAnyAddress(backend
->d_config
.sourceAddr
)) {
264 #ifdef IP_BIND_ADDRESS_NO_PORT
265 if (backend
->d_config
.ipBindAddrNoPort
) {
266 SSetsockopt(sock
.getHandle(), SOL_IP
, IP_BIND_ADDRESS_NO_PORT
, 1);
269 sock
.bind(backend
->d_config
.sourceAddr
);
271 sock
.connect(addr
, backend
->d_config
.tcpConnectTimeout
);
273 sock
.writenWithTimeout(reinterpret_cast<const char*>(packet
.data()), packet
.size(), backend
->d_config
.tcpSendTimeout
);
275 const struct timeval remainingTime
= {.tv_sec
= backend
->d_config
.tcpRecvTimeout
, .tv_usec
= 0};
276 uint16_t responseSize
= 0;
277 auto got
= readn2WithTimeout(sock
.getHandle(), &responseSize
, sizeof(responseSize
), remainingTime
);
278 if (got
!= sizeof(responseSize
)) {
280 warnlog("Error while waiting for the ADD upgrade response size from backend %s: %d", addr
.toStringWithPort(), got
);
285 packet
.resize(ntohs(responseSize
));
287 got
= readn2WithTimeout(sock
.getHandle(), packet
.data(), packet
.size(), remainingTime
);
288 if (got
!= packet
.size()) {
290 warnlog("Error while waiting for the ADD upgrade response from backend %s: %d", addr
.toStringWithPort(), got
);
295 if (packet
.size() <= sizeof(struct dnsheader
)) {
297 warnlog("Too short answer of size %d received from the backend %s", packet
.size(), addr
.toStringWithPort());
303 memcpy(&d
, packet
.data(), sizeof(d
));
306 warnlog("Invalid ID (%d / %d) received from the backend %s", d
.id
, id
, addr
.toStringWithPort());
311 if (d
.rcode
!= RCode::NoError
) {
313 warnlog("Response code '%s' received from the backend %s for '%s'", RCode::to_s(d
.rcode
), addr
.toStringWithPort(), s_discoveryDomain
);
319 if (ntohs(d
.qdcount
) != 1) {
321 warnlog("Invalid answer (qdcount %d) received from the backend %s", ntohs(d
.qdcount
), addr
.toStringWithPort());
326 uint16_t receivedType
;
327 uint16_t receivedClass
;
328 DNSName
receivedName(reinterpret_cast<const char*>(packet
.data()), packet
.size(), sizeof(dnsheader
), false, &receivedType
, &receivedClass
);
330 if (receivedName
!= s_discoveryDomain
|| receivedType
!= s_discoveryType
|| receivedClass
!= QClass::IN
) {
332 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
.toStringWithPort());
337 return handleSVCResult(packet
, addr
, upgradeableBackend
.d_dohKey
, config
);
339 catch (const std::exception
& e
) {
340 warnlog("Error while trying to discover backend upgrade for %s: %s", addr
.toStringWithPort(), e
.what());
343 warnlog("Error while trying to discover backend upgrade for %s", addr
.toStringWithPort());
349 static bool checkBackendUsability(std::shared_ptr
<DownstreamState
>& ds
)
352 Socket
sock(ds
->d_config
.remote
.sin4
.sin_family
, SOCK_STREAM
);
353 sock
.setNonBlocking();
355 if (!IsAnyAddress(ds
->d_config
.sourceAddr
)) {
357 #ifdef IP_BIND_ADDRESS_NO_PORT
358 if (ds
->d_config
.ipBindAddrNoPort
) {
359 SSetsockopt(sock
.getHandle(), SOL_IP
, IP_BIND_ADDRESS_NO_PORT
, 1);
363 if (!ds
->d_config
.sourceItfName
.empty()) {
364 #ifdef SO_BINDTODEVICE
365 setsockopt(sock
.getHandle(), SOL_SOCKET
, SO_BINDTODEVICE
, ds
->d_config
.sourceItfName
.c_str(), ds
->d_config
.sourceItfName
.length());
368 sock
.bind(ds
->d_config
.sourceAddr
);
371 auto handler
= std::make_unique
<TCPIOHandler
>(ds
->d_config
.d_tlsSubjectName
, ds
->d_config
.d_tlsSubjectIsAddr
, sock
.releaseHandle(), timeval
{ds
->d_config
.checkTimeout
, 0}, ds
->d_tlsCtx
);
372 handler
->connect(ds
->d_config
.tcpFastOpen
, ds
->d_config
.remote
, timeval
{ds
->d_config
.checkTimeout
, 0});
375 catch (const std::exception
& e
) {
376 vinfolog("Exception when trying to use a newly upgraded backend %s (subject %s): %s", ds
->getNameWithAddr(), ds
->d_config
.d_tlsSubjectName
, e
.what());
379 vinfolog("Exception when trying to use a newly upgraded backend %s (subject %s)", ds
->getNameWithAddr(), ds
->d_config
.d_tlsSubjectName
);
385 bool ServiceDiscovery::tryToUpgradeBackend(const UpgradeableBackend
& backend
)
387 ServiceDiscovery::DiscoveredResolverConfig discoveredConfig
;
389 vinfolog("Trying to discover configuration for backend %s", backend
.d_ds
->getNameWithAddr());
390 if (!ServiceDiscovery::getDiscoveredConfig(backend
, discoveredConfig
)) {
394 if (discoveredConfig
.d_protocol
!= dnsdist::Protocol::DoT
&& discoveredConfig
.d_protocol
!= dnsdist::Protocol::DoH
) {
398 DownstreamState::Config
config(backend
.d_ds
->d_config
);
399 config
.remote
= discoveredConfig
.d_addr
;
400 config
.remote
.setPort(discoveredConfig
.d_port
);
402 if (backend
.keepAfterUpgrade
&& config
.availability
== DownstreamState::Availability::Up
) {
403 /* it's OK to keep the forced state if we replace the initial
404 backend, but if we are adding a new backend, it should not
405 inherit that setting, especially since DoX backends are much
406 more likely to fail (certificate errors, ...) */
407 if (config
.d_upgradeToLazyHealthChecks
) {
408 config
.availability
= DownstreamState::Availability::Lazy
;
411 config
.availability
= DownstreamState::Availability::Auto
;
415 ComboAddress::addressOnlyEqual comparator
;
416 config
.d_dohPath
= discoveredConfig
.d_dohPath
;
417 if (!discoveredConfig
.d_subjectName
.empty() && comparator(config
.remote
, backend
.d_ds
->d_config
.remote
)) {
418 /* same address, we can used the supplied name for validation */
419 config
.d_tlsSubjectName
= discoveredConfig
.d_subjectName
;
422 /* different name, and draft-ietf-add-ddr-04 states that:
423 "In order to be considered a verified Designated Resolver, the TLS
424 certificate presented by the Designated Resolver MUST contain the IP
425 address of the designating Unencrypted Resolver in a subjectAltName
428 config
.d_tlsSubjectName
= backend
.d_ds
->d_config
.remote
.toString();
429 config
.d_tlsSubjectIsAddr
= true;
432 if (!backend
.d_poolAfterUpgrade
.empty()) {
433 config
.pools
.clear();
434 config
.pools
.insert(backend
.d_poolAfterUpgrade
);
438 /* create new backend, put it into the right pool(s) */
439 auto tlsCtx
= getTLSContext(config
.d_tlsParams
);
440 auto newServer
= std::make_shared
<DownstreamState
>(std::move(config
), std::move(tlsCtx
), true);
442 /* check that we can connect to the backend (including certificate validation */
443 if (!checkBackendUsability(newServer
)) {
444 vinfolog("Failed to use the automatically upgraded server %s, skipping for now", newServer
->getNameWithAddr());
448 infolog("Added automatically upgraded server %s", newServer
->getNameWithAddr());
450 auto localPools
= g_pools
.getCopy();
451 if (!newServer
->d_config
.pools
.empty()) {
452 for (const auto& poolName
: newServer
->d_config
.pools
) {
453 addServerToPool(localPools
, poolName
, newServer
);
457 addServerToPool(localPools
, "", newServer
);
462 auto states
= g_dstates
.getCopy();
463 states
.push_back(newServer
);
464 /* remove the existing backend if needed */
465 if (!backend
.keepAfterUpgrade
) {
466 for (auto it
= states
.begin(); it
!= states
.end(); ++it
) {
467 if (*it
== backend
.d_ds
) {
473 for (const string
& poolName
: backend
.d_ds
->d_config
.pools
) {
474 removeServerFromPool(localPools
, poolName
, backend
.d_ds
);
476 /* the server might also be in the default pool */
477 removeServerFromPool(localPools
, "", backend
.d_ds
);
480 std::stable_sort(states
.begin(), states
.end(), [](const decltype(newServer
)& a
, const decltype(newServer
)& b
) {
481 return a
->d_config
.order
< b
->d_config
.order
;
484 g_pools
.setState(localPools
);
485 g_dstates
.setState(states
);
486 if (!backend
.keepAfterUpgrade
) {
487 backend
.d_ds
->stop();
492 catch (const std::exception
& e
) {
493 warnlog("Error when trying to upgrade a discovered backend: %s", e
.what());
499 void ServiceDiscovery::worker()
501 setThreadName("dnsdist/discove");
503 time_t now
= time(nullptr);
505 auto upgradeables
= *(s_upgradeableBackends
.lock());
506 std::set
<std::shared_ptr
<DownstreamState
>> upgradedBackends
;
508 for (auto backendIt
= upgradeables
.begin(); backendIt
!= upgradeables
.end();) {
509 auto& backend
= *backendIt
;
511 if (backend
->d_nextCheck
> now
) {
516 auto upgraded
= tryToUpgradeBackend(*backend
);
518 upgradedBackends
.insert(backend
->d_ds
);
519 backendIt
= upgradeables
.erase(backendIt
);
523 catch (const std::exception
& e
) {
524 vinfolog("Exception in the Service Discovery thread: %s", e
.what());
527 vinfolog("Exception in the Service Discovery thread");
530 backend
->d_nextCheck
= now
+ backend
->d_interval
;
535 auto backends
= s_upgradeableBackends
.lock();
536 for (auto it
= backends
->begin(); it
!= backends
->end();) {
537 if (upgradedBackends
.count((*it
)->d_ds
) != 0) {
538 it
= backends
->erase(it
);
546 /* we could sleep until the next check but a new backend
547 could be added in the meantime, so let's just check every
548 minute if we have something to do */
553 bool ServiceDiscovery::run()
555 s_thread
= std::thread(&ServiceDiscovery::worker
);
561 LockGuarded
<std::vector
<std::shared_ptr
<ServiceDiscovery::UpgradeableBackend
>>> ServiceDiscovery::s_upgradeableBackends
;
562 std::thread
ServiceDiscovery::s_thread
;