Coverage Report

Created: 2026-10-16 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pdns/pdns/dnsdistdist/dnsdist-backend.cc
Line
Count
Source
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
// for OpenBSD, sys/socket.h needs to come before net/if.h
24
#include <sys/socket.h>
25
#include <net/if.h>
26
27
#include <boost/format.hpp>
28
29
#include "config.h"
30
#include "dnsdist.hh"
31
#include "dnsdist-backend.hh"
32
#include "dnsdist-backoff.hh"
33
#include "dnsdist-metrics.hh"
34
#include "dnsdist-nghttp2.hh"
35
#include "dnsdist-random.hh"
36
#include "dnsdist-rings.hh"
37
#include "dnsdist-snmp.hh"
38
#include "dnsdist-tcp.hh"
39
#include "dnsdist-udp.hh"
40
#include "dnsdist-xsk.hh"
41
#include "dolog.hh"
42
#include "xsk.hh"
43
44
bool DownstreamState::passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq)
45
0
{
46
0
  auto closer = cpq->query.d_idstate.getCloser(__func__); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
47
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
48
  if (!d_config.d_dohPath.empty()) {
49
    return g_dohClientThreads && g_dohClientThreads->passCrossProtocolQueryToThread(std::move(cpq));
50
  }
51
#endif
52
0
  return g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq));
53
0
}
54
55
#ifdef HAVE_XSK
56
void DownstreamState::addXSKDestination(int fd)
57
{
58
  auto socklen = d_config.remote.getSocklen();
59
  ComboAddress local;
60
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): sorry, it's the API
61
  if (getsockname(fd, reinterpret_cast<sockaddr*>(&local), &socklen) != 0) {
62
    return;
63
  }
64
65
  {
66
    auto addresses = d_socketSourceAddresses.write_lock();
67
    addresses->push_back(local);
68
  }
69
  dnsdist::xsk::addDestinationAddress(local);
70
  for (size_t idx = 0; idx < d_xskSockets.size(); idx++) {
71
    d_xskSockets.at(idx)->addWorkerRoute(d_xskInfos.at(idx), local);
72
  }
73
}
74
75
void DownstreamState::removeXSKDestination(int fd)
76
{
77
  auto socklen = d_config.remote.getSocklen();
78
  ComboAddress local;
79
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): sorry, it's the API
80
  if (getsockname(fd, reinterpret_cast<sockaddr*>(&local), &socklen) != 0) {
81
    return;
82
  }
83
84
  dnsdist::xsk::removeDestinationAddress(local);
85
  for (auto& xskSocket : d_xskSockets) {
86
    xskSocket->removeWorkerRoute(local);
87
  }
88
}
89
#endif /* HAVE_XSK */
90
91
bool DownstreamState::reconnect(bool initialAttempt)
92
0
{
93
0
  std::unique_lock<std::mutex> lock(connectLock, std::try_to_lock);
94
0
  if (!lock.owns_lock() || isStopped()) {
95
    /* we are already reconnecting or stopped anyway */
96
0
    return false;
97
0
  }
98
99
0
  if (IsAnyAddress(d_config.remote)) {
100
0
    return true;
101
0
  }
102
103
0
  connected = false;
104
#ifdef HAVE_XSK
105
  if (!d_xskInfos.empty()) {
106
    auto addresses = d_socketSourceAddresses.write_lock();
107
    addresses->clear();
108
  }
109
#endif /* HAVE_XSK */
110
111
0
  for (auto& fd : sockets) {
112
0
    if (fd != -1) {
113
0
      if (sockets.size() > 1) {
114
0
        (*mplexer.lock())->removeReadFD(fd);
115
0
      }
116
#ifdef HAVE_XSK
117
      if (!d_xskInfos.empty()) {
118
        removeXSKDestination(fd);
119
      }
120
#endif /* HAVE_XSK */
121
      /* shutdown() is needed to wake up recv() in the responderThread */
122
0
      shutdown(fd, SHUT_RDWR);
123
0
      close(fd);
124
0
      fd = -1;
125
0
    }
126
0
    fd = SSocket(d_config.remote.sin4.sin_family, SOCK_DGRAM, 0);
127
128
0
#ifdef SO_BINDTODEVICE
129
0
    if (!d_config.sourceItfName.empty()) {
130
0
      int res = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, d_config.sourceItfName.c_str(), d_config.sourceItfName.length());
131
0
      if (res != 0) {
132
0
        int saved = errno;
133
0
        SLOG(infolog("Error setting up the interface on backend socket '%s': %s", d_config.remote.toStringWithPort(), stringerror(saved)),
134
0
             getLogger()->error(Logr::Info, saved, "Error setting up the interface on backend socket", "source.interface", Logging::Loggable(d_config.sourceItfName)));
135
0
      }
136
0
    }
137
0
#endif
138
139
0
    dnsdist::udp::setUDPSocketBufferSizes(fd, *getLogger(), dnsdist::udp::Context::Backend, d_config.remote);
140
141
0
    if (!IsAnyAddress(d_config.sourceAddr)) {
142
0
#ifdef IP_BIND_ADDRESS_NO_PORT
143
0
      if (d_config.ipBindAddrNoPort) {
144
0
        SSetsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
145
0
      }
146
0
#endif
147
0
      SBind(fd, d_config.sourceAddr);
148
0
    }
149
150
0
    try {
151
0
      setDscp(fd, d_config.remote.sin4.sin_family, d_config.dscp);
152
0
      SConnect(fd, d_config.tcpFastOpen, d_config.remote);
153
0
      if (sockets.size() > 1) {
154
0
        (*mplexer.lock())->addReadFD(fd, [](int, boost::any) {});
155
0
      }
156
#ifdef HAVE_XSK
157
      if (!d_xskInfos.empty()) {
158
        addXSKDestination(fd);
159
      }
160
#endif /* HAVE_XSK */
161
0
      connected = true;
162
0
    }
163
0
    catch (const std::runtime_error& error) {
164
0
      if (initialAttempt || dnsdist::configuration::getCurrentRuntimeConfiguration().d_verbose) {
165
0
        if (!IsAnyAddress(d_config.sourceAddr) || !d_config.sourceItfName.empty()) {
166
0
          SLOG(infolog("Error connecting to new server with address %s (source address: %s, source interface: %s): %s", d_config.remote.toStringWithPort(), IsAnyAddress(d_config.sourceAddr) ? "not set" : d_config.sourceAddr.toString(), d_config.sourceItfName.empty() ? "not set" : d_config.sourceItfName, error.what()),
167
0
               getLogger()->error(Logr::Info, error.what(), "Error connecting to server", "source.address", Logging::Loggable(d_config.sourceAddr), "source.interface", Logging::Loggable(d_config.sourceItfName)));
168
0
        }
169
0
        else {
170
0
          SLOG(infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what()),
171
0
               getLogger()->error(Logr::Info, error.what(), "Error connecting to server"));
172
0
        }
173
0
      }
174
0
      connected = false;
175
0
      break;
176
0
    }
177
0
  }
178
179
  /* if at least one (re-)connection failed, close all sockets */
180
0
  if (!connected) {
181
#ifdef HAVE_XSK
182
    if (!d_xskInfos.empty()) {
183
      auto addresses = d_socketSourceAddresses.write_lock();
184
      addresses->clear();
185
    }
186
#endif /* HAVE_XSK */
187
0
    for (auto& fd : sockets) {
188
0
      if (fd != -1) {
189
#ifdef HAVE_XSK
190
        if (!d_xskInfos.empty()) {
191
          removeXSKDestination(fd);
192
        }
193
#endif /* HAVE_XSK */
194
0
        if (sockets.size() > 1) {
195
0
          try {
196
0
            (*mplexer.lock())->removeReadFD(fd);
197
0
          }
198
0
          catch (const FDMultiplexerException& e) {
199
            /* some sockets might not have been added to the multiplexer
200
               yet, that's fine */
201
0
          }
202
0
        }
203
        /* shutdown() is needed to wake up recv() in the responderThread */
204
0
        shutdown(fd, SHUT_RDWR);
205
0
        close(fd);
206
0
        fd = -1;
207
0
      }
208
0
    }
209
0
  }
210
211
0
  if (connected) {
212
0
    lock.unlock();
213
0
    d_connectedWait.notify_all();
214
0
    if (!initialAttempt) {
215
      /* we need to be careful not to start this
216
         thread too soon, as the creation should only
217
         happen after the configuration has been parsed */
218
0
      start();
219
0
    }
220
0
  }
221
222
0
  return connected;
223
0
}
224
225
void DownstreamState::waitUntilConnected()
226
0
{
227
0
  if (d_stopped) {
228
0
    return;
229
0
  }
230
0
  if (connected) {
231
0
    return;
232
0
  }
233
0
  {
234
0
    std::unique_lock<std::mutex> lock(connectLock);
235
0
    d_connectedWait.wait(lock, [this] {
236
0
      return connected.load();
237
0
    });
238
0
  }
239
0
}
240
241
void DownstreamState::stop()
242
0
{
243
0
  if (d_stopped) {
244
0
    return;
245
0
  }
246
0
  d_stopped = true;
247
248
0
  {
249
0
    auto tlock = std::scoped_lock(connectLock);
250
0
    auto slock = mplexer.lock();
251
252
0
    for (auto& fd : sockets) {
253
0
      if (fd != -1) {
254
        /* shutdown() is needed to wake up recv() in the responderThread */
255
0
        shutdown(fd, SHUT_RDWR);
256
0
      }
257
0
    }
258
0
  }
259
0
}
260
261
void DownstreamState::hash()
262
0
{
263
0
  const auto hashPerturbation = dnsdist::configuration::getImmutableConfiguration().d_hashPerturbation;
264
0
  VERBOSESLOG(infolog("Computing hashes for id=%s and weight=%d, hash_perturbation=%d", *d_config.id, d_config.d_weight, hashPerturbation),
265
0
              dnsdist::logging::getTopLogger("backend")->info(Logr::Info, "Computing hashes for backend", "backend.id", Logging::Loggable(*d_config.id), "backend.weight", Logging::Loggable(d_config.d_weight), "backend.hash_perturbation", Logging::Loggable(hashPerturbation)));
266
267
0
  auto weight = d_config.d_weight;
268
0
  auto idStr = boost::str(boost::format("%s") % *d_config.id);
269
0
  auto lockedHashes = hashes.write_lock();
270
0
  lockedHashes->clear();
271
0
  lockedHashes->reserve(weight);
272
0
  while (weight > 0) {
273
0
    std::string uuid = boost::str(boost::format("%s-%d") % idStr % weight);
274
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): sorry, it's the burtle API
275
0
    unsigned int wshash = burtleCI(reinterpret_cast<const unsigned char*>(uuid.c_str()), uuid.size(), hashPerturbation);
276
0
    lockedHashes->push_back(wshash);
277
0
    --weight;
278
0
  }
279
0
  std::sort(lockedHashes->begin(), lockedHashes->end());
280
0
  hashesComputed = true;
281
0
}
282
283
void DownstreamState::setId(const boost::uuids::uuid& newId)
284
0
{
285
0
  d_config.id = newId;
286
  // compute hashes only if already done
287
0
  if (hashesComputed) {
288
0
    hash();
289
0
  }
290
0
}
291
292
void DownstreamState::setWeight(int newWeight)
293
0
{
294
0
  if (newWeight < 1) {
295
0
    SLOG(errlog("Error setting server's weight: downstream weight value must be greater than 0."),
296
0
         getLogger()->info(Logr::Error, "Error setting server's weight: downstream weight value must be greater than 0", "backend.weight", Logging::Loggable(newWeight)));
297
0
    return;
298
0
  }
299
300
0
  d_config.d_weight = newWeight;
301
302
0
  if (hashesComputed) {
303
0
    hash();
304
0
  }
305
0
}
306
307
DownstreamState::DownstreamState(DownstreamState::Config&& config, std::shared_ptr<TLSCtx> tlsCtx, bool connect) :
308
0
  d_config(std::move(config)), d_tlsCtx(std::move(tlsCtx))
309
0
{
310
0
  threadStarted.clear();
311
312
0
  if (d_config.d_qpsLimit > 0) {
313
0
    d_qpsLimiter = QPSLimiter(d_config.d_qpsLimit, d_config.d_qpsLimit);
314
0
  }
315
316
0
  if (d_config.id) {
317
0
    setId(*d_config.id);
318
0
  }
319
0
  else {
320
0
    d_config.id = getUniqueID();
321
0
  }
322
323
0
  if (d_config.d_weight > 0) {
324
0
    setWeight(d_config.d_weight);
325
0
  }
326
327
0
  if (d_config.d_availability == Availability::Auto && d_config.d_healthCheckMode == HealthCheckMode::Lazy && d_config.d_lazyHealthCheckSampleSize > 0) {
328
0
    d_lazyHealthCheckStats.lock()->d_lastResults.set_capacity(d_config.d_lazyHealthCheckSampleSize);
329
0
    setUpStatus(true);
330
0
  }
331
332
0
  setName(d_config.name);
333
334
0
  if (d_tlsCtx && !d_config.d_dohPath.empty()) {
335
#ifdef HAVE_NGHTTP2
336
    auto outgoingDoHWorkerThreads = dnsdist::configuration::getImmutableConfiguration().d_outgoingDoHWorkers;
337
    if (dnsdist::configuration::isImmutableConfigurationDone() && outgoingDoHWorkerThreads && *outgoingDoHWorkerThreads == 0) {
338
      throw std::runtime_error("Error: setOutgoingDoHWorkerThreads() is set to 0 so no outgoing DoH worker thread is available to serve queries");
339
    }
340
341
    if (!dnsdist::configuration::isImmutableConfigurationDone() && (!outgoingDoHWorkerThreads || *outgoingDoHWorkerThreads == 0)) {
342
      dnsdist::configuration::updateImmutableConfiguration([](dnsdist::configuration::ImmutableConfiguration& immutableConfig) {
343
        immutableConfig.d_outgoingDoHWorkers = 1;
344
      });
345
    }
346
#endif /* HAVE_NGHTTP2 */
347
0
  }
348
349
0
  if (connect && !isTCPOnly()) {
350
0
    if (!IsAnyAddress(d_config.remote)) {
351
0
      connectUDPSockets();
352
0
    }
353
0
  }
354
355
0
  sw.start();
356
0
}
357
358
void DownstreamState::start()
359
0
{
360
0
  if (connected && !threadStarted.test_and_set()) {
361
#ifdef HAVE_XSK
362
    for (auto& xskInfo : d_xskInfos) {
363
      auto xskResponderThread = std::thread(dnsdist::xsk::XskResponderThread, shared_from_this(), xskInfo);
364
      if (!d_config.d_cpus.empty()) {
365
        mapThreadToCPUList(xskResponderThread.native_handle(), d_config.d_cpus);
366
      }
367
      xskResponderThread.detach();
368
    }
369
#endif /* HAVE_XSK */
370
371
0
    auto tid = std::thread(dnsdist::udp::responderThread, shared_from_this());
372
0
    if (!d_config.d_cpus.empty()) {
373
0
      mapThreadToCPUList(tid.native_handle(), d_config.d_cpus);
374
0
    }
375
0
    tid.detach();
376
0
  }
377
0
}
378
379
void DownstreamState::connectUDPSockets()
380
0
{
381
0
  const auto& config = dnsdist::configuration::getImmutableConfiguration();
382
0
  if (config.d_randomizeIDsToBackend) {
383
0
    idStates.clear();
384
0
  }
385
0
  else {
386
0
    const auto maxUDPOutstanding = d_config.d_maxUDPOutstanding > 0 ? d_config.d_maxUDPOutstanding : config.d_maxUDPOutstanding;
387
0
    idStates.resize(maxUDPOutstanding);
388
0
  }
389
0
  sockets.resize(d_config.d_numberOfSockets);
390
391
0
  if (sockets.size() > 1) {
392
0
    *(mplexer.lock()) = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(sockets.size()));
393
0
  }
394
395
0
  for (auto& fd : sockets) {
396
0
    fd = -1;
397
0
  }
398
399
0
  reconnect(true);
400
0
}
401
402
DownstreamState::~DownstreamState()
403
0
{
404
0
  for (auto& fd : sockets) {
405
0
    if (fd >= 0) {
406
0
      close(fd);
407
0
      fd = -1;
408
0
    }
409
0
  }
410
0
}
411
412
void DownstreamState::incCurrentConnectionsCount()
413
0
{
414
0
  auto currentConnectionsCount = ++tcpCurrentConnections;
415
0
  if (currentConnectionsCount > tcpMaxConcurrentConnections) {
416
0
    tcpMaxConcurrentConnections.store(currentConnectionsCount);
417
0
  }
418
0
}
419
420
int DownstreamState::pickSocketForSending()
421
0
{
422
0
  size_t numberOfSockets = sockets.size();
423
0
  if (numberOfSockets == 1) {
424
0
    return sockets[0];
425
0
  }
426
427
0
  size_t idx{0};
428
0
  if (dnsdist::configuration::getImmutableConfiguration().d_randomizeUDPSocketsToBackend) {
429
0
    idx = dnsdist::getRandomValue(numberOfSockets);
430
0
  }
431
0
  else {
432
0
    idx = socketsOffset++;
433
0
  }
434
435
0
  return sockets[idx % numberOfSockets];
436
0
}
437
438
void DownstreamState::pickSocketsReadyForReceiving(std::vector<int>& ready)
439
0
{
440
0
  ready.clear();
441
442
0
  if (sockets.size() == 1) {
443
0
    ready.push_back(sockets[0]);
444
0
    return;
445
0
  }
446
447
0
  (*mplexer.lock())->getAvailableFDs(ready, 1000);
448
0
}
449
450
static bool isIDSExpired(const IDState& ids, uint8_t udpTimeout)
451
0
{
452
0
  auto age = ids.age.load();
453
0
  return age > udpTimeout;
454
0
}
455
456
void DownstreamState::handleUDPTimeout(IDState& ids)
457
0
{
458
0
  ids.age = 0;
459
0
  ids.inUse = false;
460
0
  ++reuseds;
461
0
  --outstanding;
462
0
  ++dnsdist::metrics::g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
463
0
  VERBOSESLOG(infolog("Had a downstream timeout from %s (%s) for query for %s|%s from %s",
464
0
                      d_config.remote.toStringWithPort(), getName(),
465
0
                      ids.internal.qname.toLogString(), QType(ids.internal.qtype).toString(), ids.internal.origRemote.toStringWithPort()),
466
0
              getLogger()->info(Logr::Info, "Had a downstream timeout", "dns.question.name", Logging::Loggable(ids.internal.qname), "dns.question.type", Logging::Loggable(ids.internal.qtype), "dns.question.class", Logging::Loggable(ids.internal.qclass), "dns.question.id", Logging::Loggable(ntohs(ids.internal.origID)), "client.address", Logging::Loggable(ids.internal.origRemote)));
467
468
0
  const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
469
0
  const auto& timeoutRespRules = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::TimeoutResponseRules);
470
0
  auto sender = ids.internal.du == nullptr ? nullptr : ids.internal.du->getQuerySender();
471
0
  if (!handleTimeoutResponseRules(timeoutRespRules, ids.internal, shared_from_this(), sender)) {
472
0
    DOHUnitInterface::handleTimeout(std::move(ids.internal.du));
473
0
  }
474
475
0
  if (g_rings.shouldRecordResponses()) {
476
0
    timespec now{};
477
0
    gettime(&now);
478
479
0
    dnsheader fake{};
480
0
    memset(&fake, 0, sizeof(fake));
481
0
    fake.id = ids.internal.origID;
482
0
    uint16_t* flags = getFlagsFromDNSHeader(&fake);
483
0
    *flags = ids.internal.origFlags;
484
485
0
    g_rings.insertResponse(now, ids.internal.origRemote, std::move(ids.internal.qname), ids.internal.qtype, std::numeric_limits<unsigned int>::max(), 0, fake, d_config.remote, getProtocol());
486
0
  }
487
488
0
  reportTimeoutOrError();
489
0
}
490
491
void DownstreamState::reportResponse(uint8_t rcode)
492
0
{
493
0
  if (d_config.d_availability == Availability::Auto && d_config.d_healthCheckMode == HealthCheckMode::Lazy && d_config.d_lazyHealthCheckSampleSize > 0) {
494
0
    bool failure = d_config.d_lazyHealthCheckMode == LazyHealthCheckMode::TimeoutOrServFail ? rcode == RCode::ServFail : false;
495
0
    d_lazyHealthCheckStats.lock()->d_lastResults.push_back(failure);
496
0
  }
497
0
}
498
499
void DownstreamState::reportTimeoutOrError()
500
0
{
501
0
  if (d_config.d_availability == Availability::Auto && d_config.d_healthCheckMode == HealthCheckMode::Lazy && d_config.d_lazyHealthCheckSampleSize > 0) {
502
0
    d_lazyHealthCheckStats.lock()->d_lastResults.push_back(true);
503
0
  }
504
0
}
505
506
void DownstreamState::handleUDPTimeouts()
507
0
{
508
0
  if (getProtocol() != dnsdist::Protocol::DoUDP) {
509
0
    return;
510
0
  }
511
512
0
  const auto& config = dnsdist::configuration::getImmutableConfiguration();
513
0
  const auto udpTimeout = d_config.udpTimeout > 0 ? d_config.udpTimeout : config.d_udpTimeout;
514
0
  if (config.d_randomizeIDsToBackend) {
515
0
    auto map = d_idStatesMap.lock();
516
0
    for (auto it = map->begin(); it != map->end();) {
517
0
      auto& ids = it->second;
518
0
      if (isIDSExpired(ids, udpTimeout)) {
519
0
        handleUDPTimeout(ids);
520
0
        it = map->erase(it);
521
0
        continue;
522
0
      }
523
0
      ++ids.age;
524
0
      ++it;
525
0
    }
526
0
  }
527
0
  else {
528
0
    if (outstanding.load() > 0) {
529
0
      for (IDState& ids : idStates) {
530
0
        if (!ids.isInUse()) {
531
0
          continue;
532
0
        }
533
0
        if (!isIDSExpired(ids, udpTimeout)) {
534
0
          ++ids.age;
535
0
          continue;
536
0
        }
537
0
        auto guard = ids.acquire();
538
0
        if (!guard) {
539
0
          continue;
540
0
        }
541
        /* check again, now that we have locked this state */
542
0
        if (ids.isInUse() && isIDSExpired(ids, udpTimeout)) {
543
0
          handleUDPTimeout(ids);
544
0
        }
545
0
      }
546
0
    }
547
0
  }
548
0
}
549
550
uint16_t DownstreamState::saveState(InternalQueryState&& state)
551
0
{
552
0
  const auto& config = dnsdist::configuration::getImmutableConfiguration();
553
0
  if (config.d_randomizeIDsToBackend) {
554
    /* if the state is already in use we will retry,
555
       up to 5 five times. The last selected one is used
556
       even if it was already in use */
557
0
    size_t remainingAttempts = 5;
558
0
    auto map = d_idStatesMap.lock();
559
560
0
    do {
561
0
      uint16_t selectedID = dnsdist::getRandomValue(std::numeric_limits<uint16_t>::max());
562
0
      auto [it, inserted] = map->emplace(selectedID, IDState());
563
564
0
      if (!inserted) {
565
0
        remainingAttempts--;
566
0
        if (remainingAttempts > 0) {
567
0
          continue;
568
0
        }
569
570
0
        auto oldDU = std::move(it->second.internal.du);
571
0
        ++reuseds;
572
0
        ++dnsdist::metrics::g_stats.downstreamTimeouts;
573
0
        DOHUnitInterface::handleTimeout(std::move(oldDU));
574
0
      }
575
0
      else {
576
0
        ++outstanding;
577
0
      }
578
579
0
      it->second.internal = std::move(state);
580
0
      it->second.age.store(0);
581
582
0
      return it->first;
583
0
    } while (true);
584
0
  }
585
586
0
  do {
587
0
    uint16_t selectedID = (idOffset++) % idStates.size();
588
0
    IDState& ids = idStates.at(selectedID);
589
0
    auto guard = ids.acquire();
590
0
    if (!guard) {
591
0
      continue;
592
0
    }
593
0
    if (ids.isInUse()) {
594
      /* we are reusing a state, no change in outstanding but if there was an existing DOHUnit we need
595
         to handle it because it's about to be overwritten. */
596
0
      auto oldDU = std::move(ids.internal.du);
597
0
      ++reuseds;
598
0
      ++dnsdist::metrics::g_stats.downstreamTimeouts;
599
0
      DOHUnitInterface::handleTimeout(std::move(oldDU));
600
0
    }
601
0
    else {
602
0
      ++outstanding;
603
0
    }
604
0
    ids.internal = std::move(state);
605
0
    ids.age.store(0);
606
0
    ids.inUse = true;
607
0
    return selectedID;
608
0
  } while (true);
609
0
}
610
611
void DownstreamState::restoreState(uint16_t id, InternalQueryState&& state)
612
0
{
613
0
  const auto& config = dnsdist::configuration::getImmutableConfiguration();
614
0
  if (config.d_randomizeIDsToBackend) {
615
0
    auto map = d_idStatesMap.lock();
616
617
0
    auto [it, inserted] = map->emplace(id, IDState());
618
0
    if (!inserted) {
619
      /* already used */
620
0
      ++reuseds;
621
0
      ++dnsdist::metrics::g_stats.downstreamTimeouts;
622
0
      DOHUnitInterface::handleTimeout(std::move(state.du));
623
0
    }
624
0
    else {
625
0
      it->second.internal = std::move(state);
626
0
      ++outstanding;
627
0
    }
628
0
    return;
629
0
  }
630
631
0
  auto& ids = idStates.at(id);
632
0
  auto guard = ids.acquire();
633
0
  if (!guard) {
634
    /* already used */
635
0
    ++reuseds;
636
0
    ++dnsdist::metrics::g_stats.downstreamTimeouts;
637
0
    DOHUnitInterface::handleTimeout(std::move(state.du));
638
0
    return;
639
0
  }
640
0
  if (ids.isInUse()) {
641
    /* already used */
642
0
    ++reuseds;
643
0
    ++dnsdist::metrics::g_stats.downstreamTimeouts;
644
0
    DOHUnitInterface::handleTimeout(std::move(state.du));
645
0
    return;
646
0
  }
647
0
  ids.internal = std::move(state);
648
0
  ids.inUse = true;
649
0
  ++outstanding;
650
0
}
651
652
std::optional<InternalQueryState> DownstreamState::getState(uint16_t id)
653
0
{
654
0
  std::optional<InternalQueryState> result = std::nullopt;
655
0
  const auto& config = dnsdist::configuration::getImmutableConfiguration();
656
0
  if (config.d_randomizeIDsToBackend) {
657
0
    auto map = d_idStatesMap.lock();
658
659
0
    auto it = map->find(id);
660
0
    if (it == map->end()) {
661
0
      return result;
662
0
    }
663
664
0
    result = std::move(it->second.internal);
665
0
    map->erase(it);
666
0
    --outstanding;
667
0
    return result;
668
0
  }
669
670
0
  if (id >= idStates.size()) {
671
0
    return result;
672
0
  }
673
674
0
  auto& ids = idStates.at(id);
675
0
  auto guard = ids.acquire();
676
0
  if (!guard) {
677
0
    return result;
678
0
  }
679
680
0
  if (ids.isInUse()) {
681
0
    result = std::move(ids.internal);
682
0
    --outstanding;
683
0
  }
684
0
  ids.inUse = false;
685
0
  return result;
686
0
}
687
688
bool DownstreamState::healthCheckRequired(std::optional<time_t> currentTime)
689
0
{
690
0
  if (d_config.d_availability != DownstreamState::Availability::Auto) {
691
0
    return false;
692
0
  }
693
694
0
  if (d_config.d_healthCheckMode == DownstreamState::HealthCheckMode::Lazy) {
695
0
    auto stats = d_lazyHealthCheckStats.lock();
696
0
    if (stats->d_status == LazyHealthCheckStats::LazyStatus::PotentialFailure) {
697
0
      VERBOSESLOG(infolog("Sending health-check query for %s which is still in the Potential Failure state", getNameWithAddr()),
698
0
                  getLogger()->info(Logr::Info, "Sending health-check query for a backend which is still in the Potential Failure state"));
699
0
      return true;
700
0
    }
701
0
    if (stats->d_status == LazyHealthCheckStats::LazyStatus::Failed) {
702
0
      auto now = currentTime ? *currentTime : time(nullptr);
703
0
      if (stats->d_nextCheck <= now) {
704
        /* we update the next check time here because the check might time out,
705
           and we do not want to send a second check during that time unless
706
           the timer is actually very short */
707
0
        VERBOSESLOG(infolog("Sending health-check query for %s which is still in the Failed state", getNameWithAddr()),
708
0
                    getLogger()->info(Logr::Info, "Sending health-check query for backend which is still in the Failed state"));
709
0
        updateNextLazyHealthCheck(*stats, true, now);
710
0
        return true;
711
0
      }
712
0
      return false;
713
0
    }
714
0
    if (stats->d_status == LazyHealthCheckStats::LazyStatus::Healthy) {
715
0
      auto& lastResults = stats->d_lastResults;
716
0
      size_t totalCount = lastResults.size();
717
0
      if (totalCount < d_config.d_lazyHealthCheckMinSampleCount) {
718
0
        return false;
719
0
      }
720
721
0
      size_t failures = 0;
722
0
      for (const auto& result : lastResults) {
723
0
        if (result) {
724
0
          ++failures;
725
0
        }
726
0
      }
727
728
0
      const auto maxFailureRate = static_cast<float>(d_config.d_lazyHealthCheckThreshold);
729
0
      auto current = (100.0 * failures) / totalCount;
730
0
      if (current >= maxFailureRate) {
731
0
        lastResults.clear();
732
0
        VERBOSESLOG(infolog("Backend %s reached the lazy health-check threshold (%f%% out of %f%%, looking at sample of %d items with %d failures), moving to Potential Failure state", getNameWithAddr(), current, maxFailureRate, totalCount, failures),
733
0
                    getLogger()->info(Logr::Info, "Backend reached the lazy health-check threshold, moving to Potential failure state", "backend.health_check.current_failure_rate", Logging::Loggable(current), "backend.health_check.max_failure_rate", Logging::Loggable(maxFailureRate), "backend.health_check.samples", Logging::Loggable(totalCount), "backend.health_check.failures_count", Logging::Loggable(failures)));
734
0
        stats->d_status = LazyHealthCheckStats::LazyStatus::PotentialFailure;
735
0
        consecutiveSuccessfulChecks = 0;
736
        /* we update the next check time here because the check might time out,
737
           and we do not want to send a second check during that time unless
738
           the timer is actually very short */
739
0
        updateNextLazyHealthCheck(*stats, true);
740
0
        return true;
741
0
      }
742
0
    }
743
744
0
    return false;
745
0
  }
746
0
  if (d_config.d_healthCheckMode == DownstreamState::HealthCheckMode::Active) {
747
748
0
    if (d_nextCheck > 1) {
749
0
      --d_nextCheck;
750
0
      return false;
751
0
    }
752
753
0
    d_nextCheck = d_config.checkInterval;
754
0
    return true;
755
0
  }
756
757
0
  return false;
758
0
}
759
760
time_t DownstreamState::getNextLazyHealthCheck()
761
0
{
762
0
  auto stats = d_lazyHealthCheckStats.lock();
763
0
  return stats->d_nextCheck;
764
0
}
765
766
void DownstreamState::updateNextLazyHealthCheck(LazyHealthCheckStats& stats, bool checkScheduled, std::optional<time_t> currentTime)
767
0
{
768
0
  auto now = currentTime ? *currentTime : time(nullptr);
769
0
  if (d_config.d_lazyHealthCheckUseExponentialBackOff) {
770
0
    if (stats.d_status == DownstreamState::LazyHealthCheckStats::LazyStatus::PotentialFailure) {
771
      /* we are still in the "up" state, we need to send the next query quickly to
772
         determine if the backend is really down */
773
0
      stats.d_nextCheck = now + d_config.checkInterval;
774
0
      VERBOSESLOG(infolog("Backend %s is in potential failure state, next check in %d seconds", getNameWithAddr(), d_config.checkInterval.load()),
775
0
                  getLogger()->info(Logr::Info, "Backend is in potential failure state", "backend.health_check.next_check", Logging::Loggable(d_config.checkInterval.load())));
776
0
    }
777
0
    else if (consecutiveSuccessfulChecks > 0) {
778
      /* we are in 'Failed' state, but just had one (or more) successful check,
779
         so we want the next one to happen quite quickly as the backend might
780
         be available again. */
781
0
      stats.d_nextCheck = now + d_config.d_lazyHealthCheckFailedInterval;
782
0
      if (!checkScheduled) {
783
0
        VERBOSESLOG(infolog("Backend %s is in failed state but had %d consecutive successful checks, next check in %d seconds", getNameWithAddr(), std::to_string(consecutiveSuccessfulChecks), d_config.d_lazyHealthCheckFailedInterval),
784
0
                    getLogger()->info(Logr::Info, "Backend is in failed state but had a successful check", "backend.health_check.consecutive_successful_checks", Logging::Loggable(consecutiveSuccessfulChecks), "backend.health_check.next_check", Logging::Loggable(d_config.d_lazyHealthCheckFailedInterval)));
785
0
      }
786
0
    }
787
0
    else {
788
0
      uint16_t failedTests = currentCheckFailures;
789
0
      if (checkScheduled) {
790
        /* we are planning the check after that one, which will only
791
           occur if there is a failure */
792
0
        failedTests++;
793
0
      }
794
795
0
      time_t backOff = d_config.d_lazyHealthCheckMaxBackOff;
796
0
      const ExponentialBackOffTimer backOffTimer(d_config.d_lazyHealthCheckMaxBackOff);
797
0
      auto backOffCoeffTmp = backOffTimer.get(failedTests - 1);
798
      /* backOffCoeffTmp cannot be higher than d_config.d_lazyHealthCheckMaxBackOff */
799
0
      const auto backOffCoeff = static_cast<time_t>(backOffCoeffTmp);
800
0
      if ((std::numeric_limits<time_t>::max() / d_config.d_lazyHealthCheckFailedInterval) >= backOffCoeff) {
801
0
        backOff = d_config.d_lazyHealthCheckFailedInterval * backOffCoeff;
802
0
        if (backOff > d_config.d_lazyHealthCheckMaxBackOff || (std::numeric_limits<time_t>::max() - now) <= backOff) {
803
0
          backOff = d_config.d_lazyHealthCheckMaxBackOff;
804
0
        }
805
0
      }
806
807
0
      stats.d_nextCheck = now + backOff;
808
0
      VERBOSESLOG(infolog("Backend %s is in failed state and has failed %d consecutive checks, next check in %d seconds", getNameWithAddr(), failedTests, backOff),
809
0
                  getLogger()->info(Logr::Info, "Backend is in failed state and has failed its last check", "backend.health_check.consecutive_failed_checks", Logging::Loggable(failedTests), "backend.health_check.next_check", Logging::Loggable(backOff)));
810
0
    }
811
0
  }
812
0
  else {
813
0
    stats.d_nextCheck = now + d_config.d_lazyHealthCheckFailedInterval;
814
0
    VERBOSESLOG(infolog("Backend %s is in %s state, next check in %d seconds", getNameWithAddr(), (stats.d_status == DownstreamState::LazyHealthCheckStats::LazyStatus::PotentialFailure ? "potential failure" : "failed"), d_config.d_lazyHealthCheckFailedInterval),
815
0
                getLogger()->info(Logr::Info, "Scheduling the next lazy check for this backend", "state", Logging::Loggable(stats.d_status == DownstreamState::LazyHealthCheckStats::LazyStatus::PotentialFailure ? "potential failure" : "failed"), "backend.health_check.next_check", Logging::Loggable(d_config.d_lazyHealthCheckFailedInterval)));
816
0
  }
817
0
}
818
819
void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
820
0
{
821
0
  if (!newResult) {
822
0
    ++d_healthCheckMetrics.d_failures;
823
0
  }
824
825
0
  if (initial) {
826
    /* if this is the initial health-check, at startup, we do not care
827
       about the minimum number of failed/successful health-checks */
828
0
    if (!IsAnyAddress(d_config.remote)) {
829
0
      SLOG(infolog("Marking downstream %s as '%s'", getNameWithAddr(), newResult ? "up" : "down"),
830
0
           getLogger()->info(Logr::Info, "Setting initial status for backend", "backend.health_check.status", Logging::Loggable(newResult ? "up" : "down")));
831
0
    }
832
0
    setUpStatus(newResult);
833
0
    if (newResult == false) {
834
0
      currentCheckFailures++;
835
0
      if (d_config.d_healthCheckMode == DownstreamState::HealthCheckMode::Lazy) {
836
0
        auto stats = d_lazyHealthCheckStats.lock();
837
0
        stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
838
0
        updateNextLazyHealthCheck(*stats, false);
839
0
      }
840
0
    }
841
0
    handleServerStateChange(getNameWithAddr(), newResult);
842
0
    return;
843
0
  }
844
845
0
  bool newState = newResult;
846
847
0
  if (newResult) {
848
    /* check succeeded */
849
0
    currentCheckFailures = 0;
850
0
    consecutiveSuccessfulChecks++;
851
852
0
    if (!upStatus.load(std::memory_order_relaxed)) {
853
      /* we were previously marked as "down" and had a successful health-check,
854
         let's see if this is enough to move to the "up" state or if we need
855
         more successful health-checks for that */
856
0
      if (consecutiveSuccessfulChecks < d_config.minRiseSuccesses) {
857
        /* we need more than one successful check to rise
858
           and we didn't reach the threshold yet, let's stay down */
859
0
        newState = false;
860
861
0
        if (d_config.d_healthCheckMode == DownstreamState::HealthCheckMode::Lazy) {
862
0
          auto stats = d_lazyHealthCheckStats.lock();
863
0
          updateNextLazyHealthCheck(*stats, false);
864
0
        }
865
0
      }
866
0
    }
867
868
0
    if (newState) {
869
0
      if (d_config.d_healthCheckMode == DownstreamState::HealthCheckMode::Lazy) {
870
0
        auto stats = d_lazyHealthCheckStats.lock();
871
0
        VERBOSESLOG(infolog("Backend %s had %d successful checks, moving to Healthy", getNameWithAddr(), std::to_string(consecutiveSuccessfulChecks)),
872
0
                    getLogger()->info(Logr::Info, "Backend had a successful check, moving to Healthy", "backend.health_check.consecutive_successful_checks", Logging::Loggable(consecutiveSuccessfulChecks)));
873
0
        stats->d_status = LazyHealthCheckStats::LazyStatus::Healthy;
874
0
        stats->d_lastResults.clear();
875
0
      }
876
0
    }
877
0
  }
878
0
  else {
879
    /* check failed */
880
0
    consecutiveSuccessfulChecks = 0;
881
882
0
    currentCheckFailures++;
883
884
0
    if (upStatus.load(std::memory_order_relaxed)) {
885
      /* we were previously marked as "up" and failed a health-check,
886
         let's see if this is enough to move to the "down" state or if
887
         need more failed checks for that */
888
0
      if (currentCheckFailures < d_config.maxCheckFailures) {
889
        /* we need more than one failure to be marked as down,
890
           and we did not reach the threshold yet, let's stay up */
891
0
        newState = true;
892
0
      }
893
0
      else if (d_config.d_healthCheckMode == DownstreamState::HealthCheckMode::Lazy) {
894
0
        auto stats = d_lazyHealthCheckStats.lock();
895
0
        VERBOSESLOG(infolog("Backend %s failed its health-check, moving from Potential failure to Failed", getNameWithAddr()),
896
0
                    getLogger()->info(Logr::Info, "Backend failed its health-check, moving from Potential failure to Failed"));
897
0
        stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
898
0
        currentCheckFailures = 1;
899
0
        updateNextLazyHealthCheck(*stats, false);
900
0
      }
901
0
    }
902
0
  }
903
904
0
  if (newState != upStatus.load(std::memory_order_relaxed)) {
905
    /* we are actually moving to a new state */
906
0
    if (!IsAnyAddress(d_config.remote)) {
907
0
      SLOG(infolog("Marking downstream %s as '%s'", getNameWithAddr(), newState ? "up" : "down"),
908
0
           getLogger()->info(Logr::Info, "Updating status for backend", "backend.health_check.status", Logging::Loggable(newState ? "up" : "down")));
909
0
    }
910
911
0
    if (newState && !isTCPOnly() && (!connected || d_config.reconnectOnUp)) {
912
0
      newState = reconnect();
913
0
    }
914
915
0
    setUpStatus(newState);
916
0
    if (g_snmpAgent != nullptr && dnsdist::configuration::getImmutableConfiguration().d_snmpTrapsEnabled) {
917
0
      g_snmpAgent->sendBackendStatusChangeTrap(*this);
918
0
    }
919
0
    handleServerStateChange(getNameWithAddr(), newResult);
920
0
  }
921
0
}
922
923
#ifdef HAVE_XSK
924
[[nodiscard]] ComboAddress DownstreamState::pickSourceAddressForSending()
925
{
926
  if (!connected) {
927
    waitUntilConnected();
928
  }
929
930
  auto addresses = d_socketSourceAddresses.read_lock();
931
  auto numberOfAddresses = addresses->size();
932
  if (numberOfAddresses == 0) {
933
    throw std::runtime_error("No source address available for sending XSK data to backend " + getNameWithAddr());
934
  }
935
  size_t idx = dnsdist::getRandomValue(numberOfAddresses);
936
  return (*addresses)[idx % numberOfAddresses];
937
}
938
939
void DownstreamState::registerXsk(std::vector<std::shared_ptr<XskSocket>>& xsks)
940
{
941
  d_xskSockets = xsks;
942
943
  if (d_config.sourceAddr.sin4.sin_family == 0 || (IsAnyAddress(d_config.sourceAddr))) {
944
    const auto& ifName = xsks.at(0)->getInterfaceName();
945
    auto addresses = getListOfAddressesOfNetworkInterface(ifName);
946
    if (addresses.empty()) {
947
      throw std::runtime_error("Unable to get source address from interface " + ifName);
948
    }
949
950
    if (addresses.size() > 1) {
951
      SLOG(warnlog("More than one address configured on interface %s, picking the first one (%s) for XSK. Set the 'source' parameter on 'newServer' if you want to use a different address.", ifName, addresses.at(0).toString()),
952
           getLogger()->info(Logr::Warning, "More than one address configured on this interface, picking the first one for XSK. Set the 'source' parameter on 'newServer' if you want to use a different address.", "source.interface", Logging::Loggable(ifName), "backend.address", Logging::Loggable(addresses.at(0))));
953
    }
954
    d_config.sourceAddr = addresses.at(0);
955
  }
956
  d_config.sourceMACAddr = d_xskSockets.at(0)->getSourceMACAddress();
957
958
  for (auto& xsk : d_xskSockets) {
959
    auto xskInfo = XskWorker::create(XskWorker::Type::Bidirectional, xsk->sharedEmptyFrameOffset);
960
    d_xskInfos.push_back(xskInfo);
961
    xsk->addWorker(xskInfo);
962
  }
963
  reconnect(false);
964
}
965
#endif /* HAVE_XSK */
966
967
bool DownstreamState::parseSourceParameter(const std::string& source, DownstreamState::Config& config)
968
0
{
969
  /* handle source in the following forms:
970
     - v4 address ("192.0.2.1")
971
     - v6 address ("2001:DB8::1")
972
     - interface name ("eth0")
973
     - v4 address and interface name ("192.0.2.1@eth0")
974
     - v6 address and interface name ("2001:DB8::1@eth0")
975
  */
976
0
  std::string::size_type pos = source.find('@');
977
0
  if (pos == std::string::npos) {
978
    /* no '@', try to parse that as a valid v4/v6 address */
979
0
    try {
980
0
      config.sourceAddr = ComboAddress(source);
981
0
      return true;
982
0
    }
983
0
    catch (...) {
984
0
    }
985
0
  }
986
987
  /* try to parse as interface name, or v4/v6@itf */
988
0
  config.sourceItfName = source.substr(pos == std::string::npos ? 0 : pos + 1);
989
0
  unsigned int itfIdx = if_nametoindex(config.sourceItfName.c_str());
990
0
  if (itfIdx != 0) {
991
0
    if (pos == 0 || pos == std::string::npos) {
992
      /* "eth0" or "@eth0" */
993
0
      config.sourceItf = itfIdx;
994
0
    }
995
0
    else {
996
      /* "192.0.2.1@eth0" */
997
0
      config.sourceAddr = ComboAddress(source.substr(0, pos));
998
0
      config.sourceItf = itfIdx;
999
0
    }
1000
0
#ifdef SO_BINDTODEVICE
1001
0
    if (!dnsdist::configuration::isImmutableConfigurationDone()) {
1002
      /* we need to retain CAP_NET_RAW to be able to set SO_BINDTODEVICE in the health checks */
1003
0
      dnsdist::configuration::updateImmutableConfiguration([](dnsdist::configuration::ImmutableConfiguration& currentConfig) {
1004
0
        currentConfig.d_capabilitiesToRetain.insert("CAP_NET_RAW");
1005
0
      });
1006
0
    }
1007
0
#endif
1008
0
    return true;
1009
0
  }
1010
1011
0
  SLOG(warnlog("Dismissing source %s because '%s' is not a valid interface name", source, config.sourceItfName),
1012
0
       dnsdist::logging::getTopLogger("backend")->info(Logr::Warning, "Dismissing source because the interface name is not valid", "backend.name", Logging::Loggable(config.name), "backend.address", Logging::Loggable(config.remote), "source.address", Logging::Loggable(source), "source.interface", Logging::Loggable(config.sourceItfName)));
1013
1014
0
  return false;
1015
0
}
1016
1017
bool DownstreamState::parseAvailabilityConfigFromStr(DownstreamState::Config& config, const std::string& str)
1018
0
{
1019
0
  if (pdns_iequals(str, "auto")) {
1020
0
    config.d_availability = DownstreamState::Availability::Auto;
1021
0
    config.d_healthCheckMode = DownstreamState::HealthCheckMode::Active;
1022
0
    return true;
1023
0
  }
1024
0
  if (pdns_iequals(str, "lazy")) {
1025
0
    config.d_availability = DownstreamState::Availability::Auto;
1026
0
    config.d_healthCheckMode = DownstreamState::HealthCheckMode::Lazy;
1027
0
    return true;
1028
0
  }
1029
0
  if (pdns_iequals(str, "up")) {
1030
0
    config.d_availability = DownstreamState::Availability::Up;
1031
0
    return true;
1032
0
  }
1033
0
  if (pdns_iequals(str, "down")) {
1034
0
    config.d_availability = DownstreamState::Availability::Down;
1035
0
    return true;
1036
0
  }
1037
0
  return false;
1038
0
}
1039
1040
unsigned int DownstreamState::getQPSLimit() const
1041
0
{
1042
0
  return d_qpsLimiter ? d_qpsLimiter->getRate() : 0U;
1043
0
}
1044
1045
[[nodiscard]] std::shared_ptr<const Logr::Logger> DownstreamState::getLogger() const
1046
0
{
1047
0
  return dnsdist::logging::getTopLogger("backend")->withValues("backend.name", Logging::Loggable(getName()), "backend.address", Logging::Loggable(d_config.remote), "backend.protocol", Logging::Loggable(getProtocol()));
1048
0
}
1049
1050
bool DownstreamState::canAcceptNewQueries(bool enforceQPS) const
1051
0
{
1052
0
  if (!isUp()) {
1053
0
    return false;
1054
0
  }
1055
1056
0
  if (d_config.d_maxOutstandingQueries > 0 && outstanding.load() >= d_config.d_maxOutstandingQueries) {
1057
0
    return false;
1058
0
  }
1059
1060
0
  if (enforceQPS && d_qpsLimiter && !d_qpsLimiter->checkOnly()) {
1061
0
    return false;
1062
0
  }
1063
1064
0
  return true;
1065
0
}
1066
1067
size_t ServerPool::countServers(bool upOnly) const
1068
0
{
1069
0
  size_t count = 0;
1070
0
  for (const auto& server : d_servers) {
1071
0
    if (!upOnly || std::get<1>(server)->canAcceptNewQueries(true)) {
1072
0
      count++;
1073
0
    }
1074
0
  }
1075
1076
0
  return count;
1077
0
}
1078
1079
size_t ServerPool::poolLoad() const
1080
0
{
1081
0
  size_t load = 0;
1082
0
  for (const auto& server : d_servers) {
1083
0
    size_t serverOutstanding = std::get<1>(server)->outstanding.load();
1084
0
    load += serverOutstanding;
1085
0
  }
1086
0
  return load;
1087
0
}
1088
1089
bool ServerPool::hasAtLeastOneServerAvailable() const
1090
0
{
1091
  // NOLINTNEXTLINE(readability-use-anyofallof): no it's not more readable
1092
0
  for (const auto& server : d_servers) {
1093
0
    if (std::get<1>(server)->canAcceptNewQueries(true)) {
1094
0
      return true;
1095
0
    }
1096
0
  }
1097
0
  return false;
1098
0
}
1099
1100
const ServerPolicy::NumberedServerVector& ServerPool::getServers() const
1101
0
{
1102
0
  return d_servers;
1103
0
}
1104
1105
void ServerPool::addServer(std::shared_ptr<DownstreamState>& server)
1106
0
{
1107
0
  auto count = static_cast<unsigned int>(d_servers.size());
1108
0
  d_servers.emplace_back(++count, server);
1109
  /* we need to reorder based on the server 'order' */
1110
0
  std::stable_sort(d_servers.begin(), d_servers.end(), [](const std::pair<unsigned int, std::shared_ptr<DownstreamState>>& lhs, const std::pair<unsigned int, std::shared_ptr<DownstreamState>>& rhs) {
1111
0
    return lhs.second->d_config.order < rhs.second->d_config.order;
1112
0
  });
1113
  /* and now we need to renumber for Lua (custom policies) */
1114
0
  size_t idx = 1;
1115
0
  for (auto& serv : d_servers) {
1116
0
    serv.first = idx++;
1117
0
  }
1118
1119
0
  updateConsistency();
1120
0
}
1121
1122
void ServerPool::removeServer(shared_ptr<DownstreamState>& server)
1123
0
{
1124
0
  size_t idx = 1;
1125
0
  bool found = false;
1126
0
  for (auto it = d_servers.begin(); it != d_servers.end();) {
1127
0
    if (found) {
1128
      /* we need to renumber the servers placed
1129
         after the removed one, for Lua (custom policies) */
1130
0
      it->first = idx++;
1131
0
      it++;
1132
0
    }
1133
0
    else if (it->second == server) {
1134
0
      it = d_servers.erase(it);
1135
0
      found = true;
1136
0
    }
1137
0
    else {
1138
0
      idx++;
1139
0
      it++;
1140
0
    }
1141
0
  }
1142
1143
0
  if (found && !d_isConsistent) {
1144
0
    updateConsistency();
1145
0
  }
1146
0
}
1147
1148
void ServerPool::updateConsistency()
1149
0
{
1150
0
  bool consistent{true};
1151
0
  bool first{true};
1152
0
  bool useECS{false};
1153
0
  bool tcpOnly{false};
1154
0
  bool zeroScope{true};
1155
1156
0
  for (const auto& serverPair : d_servers) {
1157
0
    const auto& server = serverPair.second;
1158
0
    if (first) {
1159
0
      first = false;
1160
0
      useECS = server->d_config.useECS;
1161
0
      tcpOnly = server->isTCPOnly();
1162
0
      zeroScope = !server->d_config.disableZeroScope;
1163
0
      continue;
1164
0
    }
1165
0
    if (consistent) {
1166
0
      if (server->d_config.useECS != useECS) {
1167
0
        consistent = false;
1168
0
      }
1169
0
      if (server->d_config.disableZeroScope == zeroScope) {
1170
0
        consistent = false;
1171
0
      }
1172
0
    }
1173
0
    if (server->isTCPOnly() != tcpOnly) {
1174
0
      consistent = false;
1175
0
      tcpOnly = false;
1176
0
    }
1177
0
  }
1178
1179
0
  d_tcpOnly = tcpOnly;
1180
0
  if (consistent) {
1181
    /* at this point we know that all servers agree
1182
       on these settings, so let's just use the same
1183
       values for the pool itself */
1184
0
    d_useECS = useECS;
1185
0
    d_zeroScope = zeroScope;
1186
0
  }
1187
0
  d_isConsistent = consistent;
1188
0
}
1189
1190
void ServerPool::setZeroScope(bool enabled)
1191
0
{
1192
0
  d_zeroScope = enabled;
1193
0
  updateConsistency();
1194
0
}
1195
1196
void ServerPool::setECS(bool useECS)
1197
0
{
1198
0
  d_useECS = useECS;
1199
0
  updateConsistency();
1200
0
}
1201
1202
bool ServerPool::shouldKeepStaleData() const
1203
0
{
1204
0
  return !getServers().empty() && countServers(true) == 0;
1205
0
}
1206
1207
namespace dnsdist::backend
1208
{
1209
void registerNewBackend(std::shared_ptr<DownstreamState>& backend)
1210
0
{
1211
0
  dnsdist::configuration::updateRuntimeConfiguration([&backend](dnsdist::configuration::RuntimeConfiguration& config) {
1212
0
    auto& backends = config.d_backends;
1213
0
    backends.push_back(backend);
1214
0
    std::stable_sort(backends.begin(), backends.end(), [](const std::shared_ptr<DownstreamState>& lhs, const std::shared_ptr<DownstreamState>& rhs) {
1215
0
      return lhs->d_config.order < rhs->d_config.order;
1216
0
    });
1217
0
  });
1218
0
}
1219
}