]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdistdist/dnsdist-discovery.cc
dnsdist: Use dnsdist::ServiceDiscovery::s_defaultDoHSVCKey in newServer
[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{
40 s_upgradeableBackends.lock()->emplace_back(UpgradeableBackend{server, poolAfterUpgrade, 0, interval, dohSVCKey, keepAfterUpgrade});
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)) {
148 return false;
149 }
150
151 for (const auto& [priority, resolver] : resolvers) {
152 /* do not compare the ports */
153 std::set<ComboAddress, ComboAddress::addressOnlyLessThan> tentativeAddresses;
154 ServiceDiscovery::DiscoveredResolverConfig tempConfig;
155 tempConfig.d_addr.sin4.sin_family = 0;
156
157 for (const auto& param : resolver.params) {
158 if (param.getKey() == SvcParam::alpn) {
159 auto alpns = param.getALPN();
160 for (const auto& alpn : alpns) {
161 if (alpn == "dot") {
162 tempConfig.d_protocol = dnsdist::Protocol::DoT;
163 if (tempConfig.d_port == 0) {
164 tempConfig.d_port = 853;
165 }
166 }
167 else if (alpn == "h2") {
168 tempConfig.d_protocol = dnsdist::Protocol::DoH;
169 if (tempConfig.d_port == 0) {
170 tempConfig.d_port = 443;
171 }
172 }
173 }
174 }
175 else if (param.getKey() == SvcParam::port) {
176 tempConfig.d_port = param.getPort();
177 }
178 else if (param.getKey() == SvcParam::ipv4hint || param.getKey() == SvcParam::ipv6hint) {
179 if (tempConfig.d_addr.sin4.sin_family == 0) {
180 auto hints = param.getIPHints();
181 for (const auto& hint : hints) {
182 tentativeAddresses.insert(hint);
183 }
184 }
185 }
186 else if (dohSVCKey != 0 && param.getKey() == dohSVCKey) {
187 tempConfig.d_dohPath = param.getValue();
188 auto expression = tempConfig.d_dohPath.find('{');
189 if (expression != std::string::npos) {
190 /* nuke the {?dns} expression, if any, as we only support POST anyway */
191 tempConfig.d_dohPath.resize(expression);
192 }
193 }
194 }
195
f468a7fe 196 if (tempConfig.d_protocol == dnsdist::Protocol::DoH) {
09708c45
RG
197#ifndef HAVE_DNS_OVER_HTTPS
198 continue;
199#endif
200 if (tempConfig.d_dohPath.empty()) {
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! */
214
215 for (const auto& hint : resolver.hints) {
216 tentativeAddresses.insert(hint);
217 }
218
219 /* we prefer the address we already know, whenever possible */
220 if (tentativeAddresses.count(existingAddr) != 0) {
221 tempConfig.d_addr = existingAddr;
222 }
223 else {
224 tempConfig.d_addr = *tentativeAddresses.begin();
225 }
226
227 tempConfig.d_subjectName = resolver.target.toStringNoDot();
228 tempConfig.d_addr.sin4.sin_port = tempConfig.d_port;
229
230 config = tempConfig;
231 return true;
232 }
233
234 return false;
235}
236
237bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeableBackend, ServiceDiscovery::DiscoveredResolverConfig& config)
238{
239 const auto& backend = upgradeableBackend.d_ds;
240 const auto& addr = backend->d_config.remote;
241 try {
242 auto id = dnsdist::getRandomDNSID();
243 PacketBuffer packet;
244 GenericDNSPacketWriter pw(packet, s_discoveryDomain, s_discoveryType);
245 pw.getHeader()->id = id;
246 pw.getHeader()->rd = 1;
247 pw.addOpt(4096, 0, 0);
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();
255 if (!IsAnyAddress(backend->d_config.sourceAddr)) {
256 sock.setReuseAddr();
257#ifdef IP_BIND_ADDRESS_NO_PORT
258 if (backend->d_config.ipBindAddrNoPort) {
259 SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
260 }
261#endif
262
263 if (!backend->d_config.sourceItfName.empty()) {
264#ifdef SO_BINDTODEVICE
265 setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, backend->d_config.sourceItfName.c_str(), backend->d_config.sourceItfName.length());
266#endif
267 }
268 sock.bind(backend->d_config.sourceAddr);
269 }
270 sock.connect(addr, backend->d_config.tcpConnectTimeout);
271
272 sock.writenWithTimeout(reinterpret_cast<const char*>(packet.data()), packet.size(), backend->d_config.tcpSendTimeout);
273
274 uint16_t responseSize = 0;
275 auto got = sock.readWithTimeout(reinterpret_cast<char*>(&responseSize), sizeof(responseSize), backend->d_config.tcpRecvTimeout);
276 if (got < 0 || static_cast<size_t>(got) != sizeof(responseSize)) {
277 if (g_verbose) {
278 warnlog("Error while waiting for the ADD upgrade response from backend %s: %d", addr.toString(), got);
279 }
280 return false;
281 }
282
283 packet.resize(ntohs(responseSize));
284
f468a7fe 285 got = sock.readWithTimeout(reinterpret_cast<char*>(packet.data()), packet.size(), backend->d_config.tcpRecvTimeout);
09708c45
RG
286 if (got < 0 || static_cast<size_t>(got) != packet.size()) {
287 if (g_verbose) {
288 warnlog("Error while waiting for the ADD upgrade response from backend %s: %d", addr.toString(), got);
289 }
290 return false;
291 }
292
293 if (packet.size() <= sizeof(struct dnsheader)) {
294 if (g_verbose) {
295 warnlog("Too short answer of size %d received from the backend %s", packet.size(), addr.toString());
296 }
297 return false;
298 }
299
300 struct dnsheader d;
301 memcpy(&d, packet.data(), sizeof(d));
302 if (d.id != id) {
303 if (g_verbose) {
304 warnlog("Invalid ID (%d / %d) received from the backend %s", d.id, id, addr.toString());
305 }
306 return false;
307 }
308
309 if (d.rcode != RCode::NoError) {
310 if (g_verbose) {
311 warnlog("Response code '%s' received from the backend %s for '%s'", RCode::to_s(d.rcode), addr.toString(), s_discoveryDomain);
312 }
313
314 return false;
315 }
316
317 if (ntohs(d.qdcount) != 1) {
318 if (g_verbose) {
319 warnlog("Invalid answer (qdcount %d) received from the backend %s", ntohs(d.qdcount), addr.toString());
320 }
321 return false;
322 }
323
324 uint16_t receivedType;
325 uint16_t receivedClass;
326 DNSName receivedName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
327
328 if (receivedName != s_discoveryDomain || receivedType != s_discoveryType || receivedClass != QClass::IN) {
329 if (g_verbose) {
330 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());
331 }
332 return false;
333 }
334
335 return handleSVCResult(packet, addr, upgradeableBackend.d_dohKey, config);
336 }
337 catch (const std::exception& e) {
338 errlog("Error while trying to discover backend upgrade for %s: %s", addr.toStringWithPort(), e.what());
339 }
340 catch (...) {
341 errlog("Error while trying to discover backend upgrade for %s", addr.toStringWithPort());
342 }
343
344 return false;
345}
346
347bool ServiceDiscovery::tryToUpgradeBackend(const UpgradeableBackend& backend)
348{
349 ServiceDiscovery::DiscoveredResolverConfig discoveredConfig;
350
351 if (!ServiceDiscovery::getDiscoveredConfig(backend, discoveredConfig)) {
352 return false;
353 }
354
355 if (discoveredConfig.d_protocol != dnsdist::Protocol::DoT && discoveredConfig.d_protocol != dnsdist::Protocol::DoH) {
356 return false;
357 }
358
359 DownstreamState::Config config(backend.d_ds->d_config);
360 config.remote = discoveredConfig.d_addr;
361 if (discoveredConfig.d_port != 0) {
362 config.remote.setPort(discoveredConfig.d_port);
363 }
364 else {
365 if (discoveredConfig.d_protocol == dnsdist::Protocol::DoT) {
366 config.remote.setPort(853);
367 }
368 else if (discoveredConfig.d_protocol == dnsdist::Protocol::DoH) {
369 config.remote.setPort(443);
370 }
371 }
372
373 ComboAddress::addressOnlyEqual comparator;
374 config.d_dohPath = discoveredConfig.d_dohPath;
375 if (comparator(config.remote, backend.d_ds->d_config.remote)) {
376 /* same address, we can used the supplied name for validation */
377 config.d_tlsSubjectName = discoveredConfig.d_subjectName;
378 }
379 else {
380 /* different name, and draft-ietf-add-ddr-04 states that:
381 "In order to be considered a verified Designated Resolver, the TLS
382 certificate presented by the Designated Resolver MUST contain the IP
383 address of the designating Unencrypted Resolver in a subjectAltName
384 extension."
385 */
386 config.d_tlsSubjectName = backend.d_ds->d_config.remote.toString();
387 }
388
389 if (!backend.d_poolAfterUpgrade.empty()) {
390 config.pools.clear();
391 config.pools.insert(backend.d_poolAfterUpgrade);
392 }
393
394 try {
395 /* create new backend, put it into the right pool(s) */
396 TLSContextParameters tlsParams;
397 auto tlsCtx = getTLSContext(tlsParams);
398 auto newServer = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), true);
399
400 infolog("Added automatically upgraded server %s", newServer->getNameWithAddr());
401
402 auto localPools = g_pools.getCopy();
403 if (!newServer->d_config.pools.empty()) {
404 for (const auto& poolName : newServer->d_config.pools) {
405 addServerToPool(localPools, poolName, newServer);
406 }
407 }
408 else {
409 addServerToPool(localPools, "", newServer);
410 }
411 g_pools.setState(localPools);
412
413 newServer->start();
414
415 auto states = g_dstates.getCopy();
416 states.push_back(newServer);
417 /* remove the existing backend if needed */
418 if (!backend.keepAfterUpgrade) {
419 for (auto it = states.begin(); it != states.end(); ++it) {
420 if (*it == backend.d_ds) {
421 states.erase(it);
422 break;
423 }
424 }
425 }
426
427 std::stable_sort(states.begin(), states.end(), [](const decltype(newServer)& a, const decltype(newServer)& b) {
428 return a->d_config.order < b->d_config.order;
429 });
430 g_dstates.setState(states);
431
432 return true;
433 }
434 catch (const std::exception& e) {
435 warnlog("Error when trying to upgrade a discovered backend: %s", e.what());
436 }
437
438 return false;
439}
440
441void ServiceDiscovery::worker()
442{
443 while (true) {
444 time_t now = time(nullptr);
445
446 auto upgradeables = *(s_upgradeableBackends.lock());
447 std::set<std::shared_ptr<DownstreamState>> upgradedBackends;
448
f468a7fe 449 for (auto backendIt = upgradeables.begin(); backendIt != upgradeables.end();) {
09708c45
RG
450 try {
451 auto& backend = *backendIt;
452 if (backend.d_nextCheck > now) {
453 ++backendIt;
454 continue;
455 }
456
457 auto upgraded = tryToUpgradeBackend(backend);
458 if (upgraded) {
459 upgradedBackends.insert(backend.d_ds);
460 backendIt = upgradeables.erase(backendIt);
461 }
462 else {
463 backend.d_nextCheck = now + backend.d_interval;
464 ++backendIt;
465 }
466 }
467 catch (const std::exception& e) {
468 vinfolog("Exception in the Service Discovery thread: %s", e.what());
469 }
470 catch (...) {
471 vinfolog("Exception in the Service Discovery thread");
472 }
473 }
474
09708c45
RG
475 {
476 auto backends = s_upgradeableBackends.lock();
f468a7fe 477 for (auto it = backends->begin(); it != backends->end();) {
09708c45
RG
478 if (upgradedBackends.count(it->d_ds) != 0) {
479 it = backends->erase(it);
480 }
481 else {
482 ++it;
483 }
484 }
485 }
486
487 /* we could sleep until the next check but a new backend
488 could be added in the meantime, so let's just check every
489 minute if we have something to do */
490 sleep(60);
491 }
492}
493
494bool ServiceDiscovery::run()
495{
496 s_thread = std::thread(&ServiceDiscovery::worker);
497 s_thread.detach();
498
499 return true;
500}
501
502LockGuarded<std::vector<ServiceDiscovery::UpgradeableBackend>> ServiceDiscovery::s_upgradeableBackends;
503std::thread ServiceDiscovery::s_thread;
504}