]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdistdist/dnsdist-discovery.cc
Merge pull request #13387 from omoerbeek/rec-b-root-servers
[thirdparty/pdns.git] / pdns / dnsdistdist / dnsdist-discovery.cc
CommitLineData
09708c45
RG
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
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.
8 *
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.
12 *
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.
17 *
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.
21 */
22
23#include "config.h"
24#include "dnsdist-discovery.hh"
25#include "dnsdist.hh"
26#include "dnsdist-random.hh"
27#include "dnsparser.hh"
28#include "dolog.hh"
29#include "sstuff.hh"
7dd60afe 30#include "threadname.hh"
09708c45 31
f468a7fe
RG
32namespace dnsdist
33{
09708c45
RG
34
35const DNSName ServiceDiscovery::s_discoveryDomain{"_dns.resolver.arpa."};
36const QType ServiceDiscovery::s_discoveryType{QType::SVCB};
37const uint16_t ServiceDiscovery::s_defaultDoHSVCKey{7};
38
39bool ServiceDiscovery::addUpgradeableServer(std::shared_ptr<DownstreamState>& server, uint32_t interval, std::string poolAfterUpgrade, uint16_t dohSVCKey, bool keepAfterUpgrade)
40{
c92e6020 41 s_upgradeableBackends.lock()->push_back(std::make_shared<UpgradeableBackend>(UpgradeableBackend{server, std::move(poolAfterUpgrade), 0, interval, dohSVCKey, keepAfterUpgrade}));
09708c45
RG
42 return true;
43}
44
45struct DesignatedResolvers
46{
47 DNSName target;
48 std::set<SvcParam> params;
49 std::vector<ComboAddress> hints;
50};
51
52static bool parseSVCParams(const PacketBuffer& answer, std::map<uint16_t, DesignatedResolvers>& resolvers)
53{
09708c45 54 std::map<DNSName, std::vector<ComboAddress>> hints;
90686725 55 const dnsheader_aligned dh(answer.data());
fa5a722b 56 PacketReader pr(std::string_view(reinterpret_cast<const char*>(answer.data()), answer.size()));
09708c45
RG
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);
61
62 DNSName rrname;
63 uint16_t rrtype;
64 uint16_t rrclass;
65
66 size_t idx = 0;
67 /* consume qd */
f468a7fe 68 for (; idx < qdcount; idx++) {
09708c45
RG
69 rrname = pr.getName();
70 rrtype = pr.get16BitInt();
71 rrclass = pr.get16BitInt();
f468a7fe
RG
72 (void)rrtype;
73 (void)rrclass;
09708c45
RG
74 }
75
76 /* parse AN */
77 for (idx = 0; idx < ancount; idx++) {
78 string blob;
79 struct dnsrecordheader ah;
80 rrname = pr.getName();
81 pr.getDnsrecordheader(ah);
82
83 if (ah.d_type == QType::SVCB) {
84 auto prio = pr.get16BitInt();
85 auto target = pr.getName();
86 std::set<SvcParam> params;
87
88 if (prio != 0) {
89 pr.xfrSvcParamKeyVals(params);
90 }
91
f468a7fe 92 resolvers[prio] = {std::move(target), std::move(params), {}};
09708c45
RG
93 }
94 else {
95 pr.xfrBlob(blob);
96 }
97 }
98
99 /* parse NS */
100 for (idx = 0; idx < nscount; idx++) {
101 string blob;
102 struct dnsrecordheader ah;
103 rrname = pr.getName();
104 pr.getDnsrecordheader(ah);
105
106 pr.xfrBlob(blob);
107 }
108
109 /* parse additional for hints */
110 for (idx = 0; idx < arcount; idx++) {
111 string blob;
112 struct dnsrecordheader ah;
113 rrname = pr.getName();
114 pr.getDnsrecordheader(ah);
115
116 if (ah.d_type == QType::A) {
117 ComboAddress addr;
118 pr.xfrCAWithoutPort(4, addr);
119 hints[rrname].push_back(addr);
120 }
121 else if (ah.d_type == QType::AAAA) {
122 ComboAddress addr;
123 pr.xfrCAWithoutPort(6, addr);
124 hints[rrname].push_back(addr);
125 }
126 else {
127 pr.xfrBlob(blob);
128 }
129 }
130
131 for (auto& resolver : resolvers) {
132 auto hint = hints.find(resolver.second.target);
133 if (hint != hints.end()) {
134 resolver.second.hints = hint->second;
135 }
136 }
137
138 return !resolvers.empty();
139}
140
141static bool handleSVCResult(const PacketBuffer& answer, const ComboAddress& existingAddr, uint16_t dohSVCKey, ServiceDiscovery::DiscoveredResolverConfig& config)
142{
143 std::map<uint16_t, DesignatedResolvers> resolvers;
144 if (!parseSVCParams(answer, resolvers)) {
8d2fe9be 145 vinfolog("No configuration found in response for backend %s", existingAddr.toStringWithPort());
09708c45
RG
146 return false;
147 }
148
149 for (const auto& [priority, resolver] : resolvers) {
9c69b51d 150 (void)priority;
09708c45
RG
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;
155
156 for (const auto& param : resolver.params) {
157 if (param.getKey() == SvcParam::alpn) {
158 auto alpns = param.getALPN();
159 for (const auto& alpn : alpns) {
160 if (alpn == "dot") {
161 tempConfig.d_protocol = dnsdist::Protocol::DoT;
162 if (tempConfig.d_port == 0) {
163 tempConfig.d_port = 853;
164 }
165 }
166 else if (alpn == "h2") {
167 tempConfig.d_protocol = dnsdist::Protocol::DoH;
168 if (tempConfig.d_port == 0) {
169 tempConfig.d_port = 443;
170 }
171 }
172 }
173 }
174 else if (param.getKey() == SvcParam::port) {
175 tempConfig.d_port = param.getPort();
176 }
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);
182 }
183 }
184 }
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);
191 }
192 }
193 }
194
f468a7fe 195 if (tempConfig.d_protocol == dnsdist::Protocol::DoH) {
09708c45
RG
196#ifndef HAVE_DNS_OVER_HTTPS
197 continue;
198#endif
199 if (tempConfig.d_dohPath.empty()) {
8d2fe9be 200 vinfolog("Got a DoH upgrade offered for %s but no path, skipping", existingAddr.toStringWithPort());
09708c45
RG
201 continue;
202 }
203 }
204 else if (tempConfig.d_protocol == dnsdist::Protocol::DoT) {
205#ifndef HAVE_DNS_OVER_TLS
206 continue;
207#endif
208 }
209 else {
210 continue;
211 }
212
213 /* we have a config that we can use! */
09708c45
RG
214 for (const auto& hint : resolver.hints) {
215 tentativeAddresses.insert(hint);
216 }
217
218 /* we prefer the address we already know, whenever possible */
219 if (tentativeAddresses.count(existingAddr) != 0) {
220 tempConfig.d_addr = existingAddr;
221 }
222 else {
223 tempConfig.d_addr = *tentativeAddresses.begin();
224 }
225
226 tempConfig.d_subjectName = resolver.target.toStringNoDot();
227 tempConfig.d_addr.sin4.sin_port = tempConfig.d_port;
228
229 config = tempConfig;
230 return true;
231 }
232
233 return false;
234}
235
236bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeableBackend, ServiceDiscovery::DiscoveredResolverConfig& config)
237{
238 const auto& backend = upgradeableBackend.d_ds;
239 const auto& addr = backend->d_config.remote;
240 try {
241 auto id = dnsdist::getRandomDNSID();
242 PacketBuffer packet;
243 GenericDNSPacketWriter pw(packet, s_discoveryDomain, s_discoveryType);
244 pw.getHeader()->id = id;
245 pw.getHeader()->rd = 1;
246 pw.addOpt(4096, 0, 0);
bd4439a9 247 pw.commit();
09708c45
RG
248
249 uint16_t querySize = static_cast<uint16_t>(packet.size());
f468a7fe 250 const uint8_t sizeBytes[] = {static_cast<uint8_t>(querySize / 256), static_cast<uint8_t>(querySize % 256)};
09708c45
RG
251 packet.insert(packet.begin(), sizeBytes, sizeBytes + 2);
252
253 Socket sock(addr.sin4.sin_family, SOCK_STREAM);
254 sock.setNonBlocking();
33363b1c
RG
255
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());
259 }
260#endif
261
09708c45
RG
262 if (!IsAnyAddress(backend->d_config.sourceAddr)) {
263 sock.setReuseAddr();
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);
267 }
268#endif
09708c45
RG
269 sock.bind(backend->d_config.sourceAddr);
270 }
271 sock.connect(addr, backend->d_config.tcpConnectTimeout);
272
273 sock.writenWithTimeout(reinterpret_cast<const char*>(packet.data()), packet.size(), backend->d_config.tcpSendTimeout);
274
514e10c7 275 const struct timeval remainingTime = {.tv_sec = backend->d_config.tcpRecvTimeout, .tv_usec = 0};
09708c45 276 uint16_t responseSize = 0;
1769de1d 277 auto got = readn2WithTimeout(sock.getHandle(), &responseSize, sizeof(responseSize), remainingTime);
6f0a2aec 278 if (got != sizeof(responseSize)) {
09708c45 279 if (g_verbose) {
bd4439a9 280 warnlog("Error while waiting for the ADD upgrade response size from backend %s: %d", addr.toStringWithPort(), got);
09708c45
RG
281 }
282 return false;
283 }
284
285 packet.resize(ntohs(responseSize));
286
1769de1d 287 got = readn2WithTimeout(sock.getHandle(), packet.data(), packet.size(), remainingTime);
6f0a2aec 288 if (got != packet.size()) {
09708c45 289 if (g_verbose) {
bd4439a9 290 warnlog("Error while waiting for the ADD upgrade response from backend %s: %d", addr.toStringWithPort(), got);
09708c45
RG
291 }
292 return false;
293 }
294
295 if (packet.size() <= sizeof(struct dnsheader)) {
296 if (g_verbose) {
bd4439a9 297 warnlog("Too short answer of size %d received from the backend %s", packet.size(), addr.toStringWithPort());
09708c45
RG
298 }
299 return false;
300 }
301
302 struct dnsheader d;
303 memcpy(&d, packet.data(), sizeof(d));
304 if (d.id != id) {
305 if (g_verbose) {
bd4439a9 306 warnlog("Invalid ID (%d / %d) received from the backend %s", d.id, id, addr.toStringWithPort());
09708c45
RG
307 }
308 return false;
309 }
310
311 if (d.rcode != RCode::NoError) {
312 if (g_verbose) {
bd4439a9 313 warnlog("Response code '%s' received from the backend %s for '%s'", RCode::to_s(d.rcode), addr.toStringWithPort(), s_discoveryDomain);
09708c45
RG
314 }
315
316 return false;
317 }
318
319 if (ntohs(d.qdcount) != 1) {
320 if (g_verbose) {
bd4439a9 321 warnlog("Invalid answer (qdcount %d) received from the backend %s", ntohs(d.qdcount), addr.toStringWithPort());
09708c45
RG
322 }
323 return false;
324 }
325
326 uint16_t receivedType;
327 uint16_t receivedClass;
328 DNSName receivedName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
329
330 if (receivedName != s_discoveryDomain || receivedType != s_discoveryType || receivedClass != QClass::IN) {
331 if (g_verbose) {
bd4439a9 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());
09708c45
RG
333 }
334 return false;
335 }
336
337 return handleSVCResult(packet, addr, upgradeableBackend.d_dohKey, config);
338 }
339 catch (const std::exception& e) {
b48bacc1 340 warnlog("Error while trying to discover backend upgrade for %s: %s", addr.toStringWithPort(), e.what());
09708c45
RG
341 }
342 catch (...) {
b48bacc1 343 warnlog("Error while trying to discover backend upgrade for %s", addr.toStringWithPort());
09708c45
RG
344 }
345
346 return false;
347}
348
fdc8abc6
RG
349static bool checkBackendUsability(std::shared_ptr<DownstreamState>& ds)
350{
351 try {
352 Socket sock(ds->d_config.remote.sin4.sin_family, SOCK_STREAM);
353 sock.setNonBlocking();
354
355 if (!IsAnyAddress(ds->d_config.sourceAddr)) {
356 sock.setReuseAddr();
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);
360 }
361#endif
362
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());
366#endif
367 }
368 sock.bind(ds->d_config.sourceAddr);
369 }
370
1152079d 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);
fdc8abc6
RG
372 handler->connect(ds->d_config.tcpFastOpen, ds->d_config.remote, timeval{ds->d_config.checkTimeout, 0});
373 return true;
374 }
375 catch (const std::exception& e) {
bd4439a9 376 vinfolog("Exception when trying to use a newly upgraded backend %s (subject %s): %s", ds->getNameWithAddr(), ds->d_config.d_tlsSubjectName, e.what());
fdc8abc6
RG
377 }
378 catch (...) {
bd4439a9 379 vinfolog("Exception when trying to use a newly upgraded backend %s (subject %s)", ds->getNameWithAddr(), ds->d_config.d_tlsSubjectName);
fdc8abc6
RG
380 }
381
382 return false;
383}
384
09708c45
RG
385bool ServiceDiscovery::tryToUpgradeBackend(const UpgradeableBackend& backend)
386{
387 ServiceDiscovery::DiscoveredResolverConfig discoveredConfig;
388
8d2fe9be 389 vinfolog("Trying to discover configuration for backend %s", backend.d_ds->getNameWithAddr());
09708c45
RG
390 if (!ServiceDiscovery::getDiscoveredConfig(backend, discoveredConfig)) {
391 return false;
392 }
393
394 if (discoveredConfig.d_protocol != dnsdist::Protocol::DoT && discoveredConfig.d_protocol != dnsdist::Protocol::DoH) {
395 return false;
396 }
397
398 DownstreamState::Config config(backend.d_ds->d_config);
399 config.remote = discoveredConfig.d_addr;
a57367a4 400 config.remote.setPort(discoveredConfig.d_port);
09708c45 401
5485edd1
RG
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;
409 }
410 else {
411 config.availability = DownstreamState::Availability::Auto;
412 }
413 }
414
09708c45
RG
415 ComboAddress::addressOnlyEqual comparator;
416 config.d_dohPath = discoveredConfig.d_dohPath;
fdc8abc6 417 if (!discoveredConfig.d_subjectName.empty() && comparator(config.remote, backend.d_ds->d_config.remote)) {
09708c45
RG
418 /* same address, we can used the supplied name for validation */
419 config.d_tlsSubjectName = discoveredConfig.d_subjectName;
420 }
421 else {
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
426 extension."
427 */
428 config.d_tlsSubjectName = backend.d_ds->d_config.remote.toString();
74b08b2a 429 config.d_tlsSubjectIsAddr = true;
09708c45
RG
430 }
431
432 if (!backend.d_poolAfterUpgrade.empty()) {
433 config.pools.clear();
434 config.pools.insert(backend.d_poolAfterUpgrade);
435 }
436
437 try {
438 /* create new backend, put it into the right pool(s) */
8d2fe9be 439 auto tlsCtx = getTLSContext(config.d_tlsParams);
09708c45
RG
440 auto newServer = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), true);
441
fdc8abc6
RG
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());
445 return false;
446 }
447
09708c45
RG
448 infolog("Added automatically upgraded server %s", newServer->getNameWithAddr());
449
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);
454 }
455 }
456 else {
457 addServerToPool(localPools, "", newServer);
458 }
09708c45
RG
459
460 newServer->start();
461
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) {
468 states.erase(it);
469 break;
470 }
471 }
69127e14
RG
472
473 for (const string& poolName : backend.d_ds->d_config.pools) {
474 removeServerFromPool(localPools, poolName, backend.d_ds);
475 }
476 /* the server might also be in the default pool */
477 removeServerFromPool(localPools, "", backend.d_ds);
09708c45
RG
478 }
479
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;
482 });
69127e14
RG
483
484 g_pools.setState(localPools);
09708c45 485 g_dstates.setState(states);
efaf2362
RG
486 if (!backend.keepAfterUpgrade) {
487 backend.d_ds->stop();
488 }
09708c45
RG
489
490 return true;
491 }
492 catch (const std::exception& e) {
493 warnlog("Error when trying to upgrade a discovered backend: %s", e.what());
494 }
495
496 return false;
497}
498
499void ServiceDiscovery::worker()
500{
7dd60afe 501 setThreadName("dnsdist/discove");
09708c45
RG
502 while (true) {
503 time_t now = time(nullptr);
504
505 auto upgradeables = *(s_upgradeableBackends.lock());
506 std::set<std::shared_ptr<DownstreamState>> upgradedBackends;
507
f468a7fe 508 for (auto backendIt = upgradeables.begin(); backendIt != upgradeables.end();) {
fdc8abc6 509 auto& backend = *backendIt;
09708c45 510 try {
b48bacc1 511 if (backend->d_nextCheck > now) {
09708c45
RG
512 ++backendIt;
513 continue;
514 }
515
b48bacc1 516 auto upgraded = tryToUpgradeBackend(*backend);
09708c45 517 if (upgraded) {
b48bacc1 518 upgradedBackends.insert(backend->d_ds);
09708c45 519 backendIt = upgradeables.erase(backendIt);
fdc8abc6 520 continue;
09708c45
RG
521 }
522 }
523 catch (const std::exception& e) {
524 vinfolog("Exception in the Service Discovery thread: %s", e.what());
525 }
526 catch (...) {
527 vinfolog("Exception in the Service Discovery thread");
528 }
fdc8abc6 529
b48bacc1 530 backend->d_nextCheck = now + backend->d_interval;
fdc8abc6 531 ++backendIt;
09708c45
RG
532 }
533
09708c45
RG
534 {
535 auto backends = s_upgradeableBackends.lock();
f468a7fe 536 for (auto it = backends->begin(); it != backends->end();) {
b48bacc1 537 if (upgradedBackends.count((*it)->d_ds) != 0) {
09708c45
RG
538 it = backends->erase(it);
539 }
540 else {
541 ++it;
542 }
543 }
544 }
545
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 */
549 sleep(60);
550 }
551}
552
553bool ServiceDiscovery::run()
554{
555 s_thread = std::thread(&ServiceDiscovery::worker);
556 s_thread.detach();
557
558 return true;
559}
560
b48bacc1 561LockGuarded<std::vector<std::shared_ptr<ServiceDiscovery::UpgradeableBackend>>> ServiceDiscovery::s_upgradeableBackends;
09708c45
RG
562std::thread ServiceDiscovery::s_thread;
563}