]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdistdist/dnsdist-discovery.cc
dnsdist: Require dnspython >= 2.2.0 in our regression tests for SVCB
[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"
30
f468a7fe
RG
31namespace dnsdist
32{
09708c45
RG
33
34const DNSName ServiceDiscovery::s_discoveryDomain{"_dns.resolver.arpa."};
35const QType ServiceDiscovery::s_discoveryType{QType::SVCB};
36const uint16_t ServiceDiscovery::s_defaultDoHSVCKey{7};
37
38bool ServiceDiscovery::addUpgradeableServer(std::shared_ptr<DownstreamState>& server, uint32_t interval, std::string poolAfterUpgrade, uint16_t dohSVCKey, bool keepAfterUpgrade)
39{
fdc8abc6 40 s_upgradeableBackends.lock()->push_back(UpgradeableBackend{server, poolAfterUpgrade, 0, interval, dohSVCKey, keepAfterUpgrade});
09708c45
RG
41 return true;
42}
43
44struct DesignatedResolvers
45{
46 DNSName target;
47 std::set<SvcParam> params;
48 std::vector<ComboAddress> hints;
49};
50
51static bool parseSVCParams(const PacketBuffer& answer, std::map<uint16_t, DesignatedResolvers>& resolvers)
52{
53 if (answer.size() <= sizeof(struct dnsheader)) {
54 throw std::runtime_error("Looking for SVC records in a packet smaller than a DNS header");
55 }
56
57 std::map<DNSName, std::vector<ComboAddress>> hints;
58 const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(answer.data());
59 PacketReader pr(pdns_string_view(reinterpret_cast<const char*>(answer.data()), answer.size()));
60 uint16_t qdcount = ntohs(dh->qdcount);
61 uint16_t ancount = ntohs(dh->ancount);
62 uint16_t nscount = ntohs(dh->nscount);
63 uint16_t arcount = ntohs(dh->arcount);
64
65 DNSName rrname;
66 uint16_t rrtype;
67 uint16_t rrclass;
68
69 size_t idx = 0;
70 /* consume qd */
f468a7fe 71 for (; idx < qdcount; idx++) {
09708c45
RG
72 rrname = pr.getName();
73 rrtype = pr.get16BitInt();
74 rrclass = pr.get16BitInt();
f468a7fe
RG
75 (void)rrtype;
76 (void)rrclass;
09708c45
RG
77 }
78
79 /* parse AN */
80 for (idx = 0; idx < ancount; idx++) {
81 string blob;
82 struct dnsrecordheader ah;
83 rrname = pr.getName();
84 pr.getDnsrecordheader(ah);
85
86 if (ah.d_type == QType::SVCB) {
87 auto prio = pr.get16BitInt();
88 auto target = pr.getName();
89 std::set<SvcParam> params;
90
91 if (prio != 0) {
92 pr.xfrSvcParamKeyVals(params);
93 }
94
f468a7fe 95 resolvers[prio] = {std::move(target), std::move(params), {}};
09708c45
RG
96 }
97 else {
98 pr.xfrBlob(blob);
99 }
100 }
101
102 /* parse NS */
103 for (idx = 0; idx < nscount; idx++) {
104 string blob;
105 struct dnsrecordheader ah;
106 rrname = pr.getName();
107 pr.getDnsrecordheader(ah);
108
109 pr.xfrBlob(blob);
110 }
111
112 /* parse additional for hints */
113 for (idx = 0; idx < arcount; idx++) {
114 string blob;
115 struct dnsrecordheader ah;
116 rrname = pr.getName();
117 pr.getDnsrecordheader(ah);
118
119 if (ah.d_type == QType::A) {
120 ComboAddress addr;
121 pr.xfrCAWithoutPort(4, addr);
122 hints[rrname].push_back(addr);
123 }
124 else if (ah.d_type == QType::AAAA) {
125 ComboAddress addr;
126 pr.xfrCAWithoutPort(6, addr);
127 hints[rrname].push_back(addr);
128 }
129 else {
130 pr.xfrBlob(blob);
131 }
132 }
133
134 for (auto& resolver : resolvers) {
135 auto hint = hints.find(resolver.second.target);
136 if (hint != hints.end()) {
137 resolver.second.hints = hint->second;
138 }
139 }
140
141 return !resolvers.empty();
142}
143
144static bool handleSVCResult(const PacketBuffer& answer, const ComboAddress& existingAddr, uint16_t dohSVCKey, ServiceDiscovery::DiscoveredResolverConfig& config)
145{
146 std::map<uint16_t, DesignatedResolvers> resolvers;
147 if (!parseSVCParams(answer, resolvers)) {
8d2fe9be 148 vinfolog("No configuration found in response for backend %s", existingAddr.toStringWithPort());
09708c45
RG
149 return false;
150 }
151
152 for (const auto& [priority, resolver] : resolvers) {
153 /* do not compare the ports */
154 std::set<ComboAddress, ComboAddress::addressOnlyLessThan> tentativeAddresses;
155 ServiceDiscovery::DiscoveredResolverConfig tempConfig;
156 tempConfig.d_addr.sin4.sin_family = 0;
157
158 for (const auto& param : resolver.params) {
159 if (param.getKey() == SvcParam::alpn) {
160 auto alpns = param.getALPN();
161 for (const auto& alpn : alpns) {
162 if (alpn == "dot") {
163 tempConfig.d_protocol = dnsdist::Protocol::DoT;
164 if (tempConfig.d_port == 0) {
165 tempConfig.d_port = 853;
166 }
167 }
168 else if (alpn == "h2") {
169 tempConfig.d_protocol = dnsdist::Protocol::DoH;
170 if (tempConfig.d_port == 0) {
171 tempConfig.d_port = 443;
172 }
173 }
174 }
175 }
176 else if (param.getKey() == SvcParam::port) {
177 tempConfig.d_port = param.getPort();
178 }
179 else if (param.getKey() == SvcParam::ipv4hint || param.getKey() == SvcParam::ipv6hint) {
180 if (tempConfig.d_addr.sin4.sin_family == 0) {
181 auto hints = param.getIPHints();
182 for (const auto& hint : hints) {
183 tentativeAddresses.insert(hint);
184 }
185 }
186 }
187 else if (dohSVCKey != 0 && param.getKey() == dohSVCKey) {
188 tempConfig.d_dohPath = param.getValue();
189 auto expression = tempConfig.d_dohPath.find('{');
190 if (expression != std::string::npos) {
191 /* nuke the {?dns} expression, if any, as we only support POST anyway */
192 tempConfig.d_dohPath.resize(expression);
193 }
194 }
195 }
196
f468a7fe 197 if (tempConfig.d_protocol == dnsdist::Protocol::DoH) {
09708c45
RG
198#ifndef HAVE_DNS_OVER_HTTPS
199 continue;
200#endif
201 if (tempConfig.d_dohPath.empty()) {
8d2fe9be 202 vinfolog("Got a DoH upgrade offered for %s but no path, skipping", existingAddr.toStringWithPort());
09708c45
RG
203 continue;
204 }
205 }
206 else if (tempConfig.d_protocol == dnsdist::Protocol::DoT) {
207#ifndef HAVE_DNS_OVER_TLS
208 continue;
209#endif
210 }
211 else {
212 continue;
213 }
214
215 /* we have a config that we can use! */
09708c45
RG
216 for (const auto& hint : resolver.hints) {
217 tentativeAddresses.insert(hint);
218 }
219
220 /* we prefer the address we already know, whenever possible */
221 if (tentativeAddresses.count(existingAddr) != 0) {
222 tempConfig.d_addr = existingAddr;
223 }
224 else {
225 tempConfig.d_addr = *tentativeAddresses.begin();
226 }
227
228 tempConfig.d_subjectName = resolver.target.toStringNoDot();
229 tempConfig.d_addr.sin4.sin_port = tempConfig.d_port;
230
231 config = tempConfig;
232 return true;
233 }
234
235 return false;
236}
237
238bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeableBackend, ServiceDiscovery::DiscoveredResolverConfig& config)
239{
240 const auto& backend = upgradeableBackend.d_ds;
241 const auto& addr = backend->d_config.remote;
242 try {
243 auto id = dnsdist::getRandomDNSID();
244 PacketBuffer packet;
245 GenericDNSPacketWriter pw(packet, s_discoveryDomain, s_discoveryType);
246 pw.getHeader()->id = id;
247 pw.getHeader()->rd = 1;
248 pw.addOpt(4096, 0, 0);
bd4439a9 249 pw.commit();
09708c45
RG
250
251 uint16_t querySize = static_cast<uint16_t>(packet.size());
f468a7fe 252 const uint8_t sizeBytes[] = {static_cast<uint8_t>(querySize / 256), static_cast<uint8_t>(querySize % 256)};
09708c45
RG
253 packet.insert(packet.begin(), sizeBytes, sizeBytes + 2);
254
255 Socket sock(addr.sin4.sin_family, SOCK_STREAM);
256 sock.setNonBlocking();
257 if (!IsAnyAddress(backend->d_config.sourceAddr)) {
258 sock.setReuseAddr();
259#ifdef IP_BIND_ADDRESS_NO_PORT
260 if (backend->d_config.ipBindAddrNoPort) {
261 SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
262 }
263#endif
264
265 if (!backend->d_config.sourceItfName.empty()) {
266#ifdef SO_BINDTODEVICE
267 setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, backend->d_config.sourceItfName.c_str(), backend->d_config.sourceItfName.length());
268#endif
269 }
270 sock.bind(backend->d_config.sourceAddr);
271 }
272 sock.connect(addr, backend->d_config.tcpConnectTimeout);
273
274 sock.writenWithTimeout(reinterpret_cast<const char*>(packet.data()), packet.size(), backend->d_config.tcpSendTimeout);
275
276 uint16_t responseSize = 0;
277 auto got = sock.readWithTimeout(reinterpret_cast<char*>(&responseSize), sizeof(responseSize), backend->d_config.tcpRecvTimeout);
278 if (got < 0 || static_cast<size_t>(got) != sizeof(responseSize)) {
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
f468a7fe 287 got = sock.readWithTimeout(reinterpret_cast<char*>(packet.data()), packet.size(), backend->d_config.tcpRecvTimeout);
09708c45
RG
288 if (got < 0 || static_cast<size_t>(got) != packet.size()) {
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) {
340 errlog("Error while trying to discover backend upgrade for %s: %s", addr.toStringWithPort(), e.what());
341 }
342 catch (...) {
343 errlog("Error while trying to discover backend upgrade for %s", addr.toStringWithPort());
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
371 time_t now = time(nullptr);
74b08b2a 372 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, now);
fdc8abc6
RG
373 handler->connect(ds->d_config.tcpFastOpen, ds->d_config.remote, timeval{ds->d_config.checkTimeout, 0});
374 return true;
375 }
376 catch (const std::exception& e) {
bd4439a9 377 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
378 }
379 catch (...) {
bd4439a9 380 vinfolog("Exception when trying to use a newly upgraded backend %s (subject %s)", ds->getNameWithAddr(), ds->d_config.d_tlsSubjectName);
fdc8abc6
RG
381 }
382
383 return false;
384}
385
09708c45
RG
386bool ServiceDiscovery::tryToUpgradeBackend(const UpgradeableBackend& backend)
387{
388 ServiceDiscovery::DiscoveredResolverConfig discoveredConfig;
389
8d2fe9be 390 vinfolog("Trying to discover configuration for backend %s", backend.d_ds->getNameWithAddr());
09708c45
RG
391 if (!ServiceDiscovery::getDiscoveredConfig(backend, discoveredConfig)) {
392 return false;
393 }
394
395 if (discoveredConfig.d_protocol != dnsdist::Protocol::DoT && discoveredConfig.d_protocol != dnsdist::Protocol::DoH) {
396 return false;
397 }
398
399 DownstreamState::Config config(backend.d_ds->d_config);
400 config.remote = discoveredConfig.d_addr;
401 if (discoveredConfig.d_port != 0) {
402 config.remote.setPort(discoveredConfig.d_port);
403 }
404 else {
405 if (discoveredConfig.d_protocol == dnsdist::Protocol::DoT) {
406 config.remote.setPort(853);
407 }
408 else if (discoveredConfig.d_protocol == dnsdist::Protocol::DoH) {
409 config.remote.setPort(443);
410 }
411 }
412
413 ComboAddress::addressOnlyEqual comparator;
414 config.d_dohPath = discoveredConfig.d_dohPath;
fdc8abc6 415 if (!discoveredConfig.d_subjectName.empty() && comparator(config.remote, backend.d_ds->d_config.remote)) {
09708c45
RG
416 /* same address, we can used the supplied name for validation */
417 config.d_tlsSubjectName = discoveredConfig.d_subjectName;
418 }
419 else {
420 /* different name, and draft-ietf-add-ddr-04 states that:
421 "In order to be considered a verified Designated Resolver, the TLS
422 certificate presented by the Designated Resolver MUST contain the IP
423 address of the designating Unencrypted Resolver in a subjectAltName
424 extension."
425 */
426 config.d_tlsSubjectName = backend.d_ds->d_config.remote.toString();
74b08b2a 427 config.d_tlsSubjectIsAddr = true;
09708c45
RG
428 }
429
430 if (!backend.d_poolAfterUpgrade.empty()) {
431 config.pools.clear();
432 config.pools.insert(backend.d_poolAfterUpgrade);
433 }
434
435 try {
436 /* create new backend, put it into the right pool(s) */
8d2fe9be 437 auto tlsCtx = getTLSContext(config.d_tlsParams);
09708c45
RG
438 auto newServer = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), true);
439
fdc8abc6
RG
440 /* check that we can connect to the backend (including certificate validation */
441 if (!checkBackendUsability(newServer)) {
442 vinfolog("Failed to use the automatically upgraded server %s, skipping for now", newServer->getNameWithAddr());
443 return false;
444 }
445
09708c45
RG
446 infolog("Added automatically upgraded server %s", newServer->getNameWithAddr());
447
448 auto localPools = g_pools.getCopy();
449 if (!newServer->d_config.pools.empty()) {
450 for (const auto& poolName : newServer->d_config.pools) {
451 addServerToPool(localPools, poolName, newServer);
452 }
453 }
454 else {
455 addServerToPool(localPools, "", newServer);
456 }
457 g_pools.setState(localPools);
458
459 newServer->start();
460
461 auto states = g_dstates.getCopy();
462 states.push_back(newServer);
463 /* remove the existing backend if needed */
464 if (!backend.keepAfterUpgrade) {
465 for (auto it = states.begin(); it != states.end(); ++it) {
466 if (*it == backend.d_ds) {
467 states.erase(it);
468 break;
469 }
470 }
471 }
472
473 std::stable_sort(states.begin(), states.end(), [](const decltype(newServer)& a, const decltype(newServer)& b) {
474 return a->d_config.order < b->d_config.order;
475 });
476 g_dstates.setState(states);
477
478 return true;
479 }
480 catch (const std::exception& e) {
481 warnlog("Error when trying to upgrade a discovered backend: %s", e.what());
482 }
483
484 return false;
485}
486
487void ServiceDiscovery::worker()
488{
489 while (true) {
490 time_t now = time(nullptr);
491
492 auto upgradeables = *(s_upgradeableBackends.lock());
493 std::set<std::shared_ptr<DownstreamState>> upgradedBackends;
494
f468a7fe 495 for (auto backendIt = upgradeables.begin(); backendIt != upgradeables.end();) {
fdc8abc6 496 auto& backend = *backendIt;
09708c45 497 try {
09708c45
RG
498 if (backend.d_nextCheck > now) {
499 ++backendIt;
500 continue;
501 }
502
503 auto upgraded = tryToUpgradeBackend(backend);
504 if (upgraded) {
505 upgradedBackends.insert(backend.d_ds);
506 backendIt = upgradeables.erase(backendIt);
fdc8abc6 507 continue;
09708c45
RG
508 }
509 }
510 catch (const std::exception& e) {
511 vinfolog("Exception in the Service Discovery thread: %s", e.what());
512 }
513 catch (...) {
514 vinfolog("Exception in the Service Discovery thread");
515 }
fdc8abc6
RG
516
517 backend.d_nextCheck = now + backend.d_interval;
518 ++backendIt;
09708c45
RG
519 }
520
09708c45
RG
521 {
522 auto backends = s_upgradeableBackends.lock();
f468a7fe 523 for (auto it = backends->begin(); it != backends->end();) {
09708c45
RG
524 if (upgradedBackends.count(it->d_ds) != 0) {
525 it = backends->erase(it);
526 }
527 else {
528 ++it;
529 }
530 }
531 }
532
533 /* we could sleep until the next check but a new backend
534 could be added in the meantime, so let's just check every
535 minute if we have something to do */
536 sleep(60);
537 }
538}
539
540bool ServiceDiscovery::run()
541{
542 s_thread = std::thread(&ServiceDiscovery::worker);
543 s_thread.detach();
544
545 return true;
546}
547
548LockGuarded<std::vector<ServiceDiscovery::UpgradeableBackend>> ServiceDiscovery::s_upgradeableBackends;
549std::thread ServiceDiscovery::s_thread;
550}