• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 12595591960

03 Jan 2025 09:27AM UTC coverage: 62.774% (+2.5%) from 60.245%
12595591960

Pull #15008

github

web-flow
Merge c2a2749d3 into 788f396a7
Pull Request #15008: Do not follow CNAME records for ANY or CNAME queries

30393 of 78644 branches covered (38.65%)

Branch coverage included in aggregate %.

105822 of 138350 relevant lines covered (76.49%)

4613078.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.23
/pdns/dnsdistdist/dnsdist-healthchecks.cc
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 "dnsdist-healthchecks.hh"
24
#include "tcpiohandler-mplexer.hh"
25
#include "dnswriter.hh"
26
#include "dolog.hh"
27
#include "dnsdist-lua.hh"
28
#include "dnsdist-random.hh"
29
#include "dnsdist-tcp.hh"
30
#include "dnsdist-nghttp2.hh"
31
#include "dnsdist-session-cache.hh"
32

33
struct HealthCheckData
34
{
35
  enum class TCPState : uint8_t
36
  {
37
    WritingQuery,
38
    ReadingResponseSize,
39
    ReadingResponse
40
  };
41

42
  HealthCheckData(FDMultiplexer& mplexer, std::shared_ptr<DownstreamState> downstream, DNSName&& checkName, uint16_t checkType, uint16_t checkClass, uint16_t queryID) :
43
    d_ds(std::move(downstream)), d_mplexer(mplexer), d_udpSocket(-1), d_checkName(std::move(checkName)), d_checkType(checkType), d_checkClass(checkClass), d_queryID(queryID)
44
  {
1,233✔
45
  }
1,233✔
46

47
  const std::shared_ptr<DownstreamState> d_ds;
48
  FDMultiplexer& d_mplexer;
49
  std::unique_ptr<TCPIOHandler> d_tcpHandler{nullptr};
50
  std::unique_ptr<IOStateHandler> d_ioState{nullptr};
51
  PacketBuffer d_buffer;
52
  Socket d_udpSocket;
53
  DNSName d_checkName;
54
  struct timeval d_ttd
55
  {
56
    0, 0
57
  };
58
  size_t d_bufferPos{0};
59
  uint16_t d_checkType;
60
  uint16_t d_checkClass;
61
  uint16_t d_queryID;
62
  TCPState d_tcpState{TCPState::WritingQuery};
63
  bool d_initial{false};
64
};
65

66
static bool handleResponse(std::shared_ptr<HealthCheckData>& data)
67
{
1,213✔
68
  const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
1,213✔
69
  const auto& downstream = data->d_ds;
1,213✔
70
  try {
1,213✔
71
    if (data->d_buffer.size() < sizeof(dnsheader)) {
1,213!
72
      ++data->d_ds->d_healthCheckMetrics.d_parseErrors;
×
73
      if (verboseHealthChecks) {
×
74
        infolog("Invalid health check response of size %d from backend %s, expecting at least %d", data->d_buffer.size(), downstream->getNameWithAddr(), sizeof(dnsheader));
×
75
      }
×
76
      return false;
×
77
    }
×
78

79
    dnsheader_aligned responseHeader(data->d_buffer.data());
1,213✔
80
    if (responseHeader.get()->id != data->d_queryID) {
1,213!
81
      ++data->d_ds->d_healthCheckMetrics.d_mismatchErrors;
×
82
      if (verboseHealthChecks) {
×
83
        infolog("Invalid health check response id %d from backend %s, expecting %d", responseHeader.get()->id, downstream->getNameWithAddr(), data->d_queryID);
×
84
      }
×
85
      return false;
×
86
    }
×
87

88
    if (!responseHeader.get()->qr) {
1,213!
89
      ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
×
90
      if (verboseHealthChecks) {
×
91
        infolog("Invalid health check response from backend %s, expecting QR to be set", downstream->getNameWithAddr());
×
92
      }
×
93
      return false;
×
94
    }
×
95

96
    if (responseHeader.get()->rcode == RCode::ServFail) {
1,213!
97
      ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
×
98
      if (verboseHealthChecks) {
×
99
        infolog("Backend %s responded to health check with ServFail", downstream->getNameWithAddr());
×
100
      }
×
101
      return false;
×
102
    }
×
103

104
    if (downstream->d_config.mustResolve && (responseHeader.get()->rcode == RCode::NXDomain || responseHeader.get()->rcode == RCode::Refused)) {
1,213!
105
      ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
×
106
      if (verboseHealthChecks) {
×
107
        infolog("Backend %s responded to health check with %s while mustResolve is set", downstream->getNameWithAddr(), responseHeader.get()->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
×
108
      }
×
109
      return false;
×
110
    }
×
111

112
    uint16_t receivedType{0};
1,213✔
113
    uint16_t receivedClass{0};
1,213✔
114
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
115
    DNSName receivedName(reinterpret_cast<const char*>(data->d_buffer.data()), static_cast<int>(data->d_buffer.size()), sizeof(dnsheader), false, &receivedType, &receivedClass);
1,213✔
116

117
    if (receivedName != data->d_checkName || receivedType != data->d_checkType || receivedClass != data->d_checkClass) {
1,213!
118
      ++data->d_ds->d_healthCheckMetrics.d_mismatchErrors;
×
119
      if (verboseHealthChecks) {
×
120
        infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", downstream->getNameWithAddr(), receivedName.toLogString(), data->d_checkName.toLogString(), QType(receivedType).toString(), QType(data->d_checkType).toString(), receivedClass, data->d_checkClass);
×
121
      }
×
122
      return false;
×
123
    }
×
124
  }
1,213✔
125
  catch (const std::exception& e) {
1,213✔
126
    ++data->d_ds->d_healthCheckMetrics.d_parseErrors;
×
127
    if (verboseHealthChecks) {
×
128
      infolog("Error checking the health of backend %s: %s", downstream->getNameWithAddr(), e.what());
×
129
    }
×
130
    return false;
×
131
  }
×
132
  catch (...) {
1,213✔
133
    if (verboseHealthChecks) {
×
134
      infolog("Unknown exception while checking the health of backend %s", downstream->getNameWithAddr());
×
135
    }
×
136
    return false;
×
137
  }
×
138

139
  return true;
1,213✔
140
}
1,213✔
141

142
class HealthCheckQuerySender : public TCPQuerySender
143
{
144
public:
145
  HealthCheckQuerySender(std::shared_ptr<HealthCheckData>& data) :
146
    d_data(data)
147
  {
12✔
148
  }
12✔
149
  HealthCheckQuerySender(const HealthCheckQuerySender&) = default;
150
  HealthCheckQuerySender(HealthCheckQuerySender&&) = default;
151
  HealthCheckQuerySender& operator=(const HealthCheckQuerySender&) = default;
152
  HealthCheckQuerySender& operator=(HealthCheckQuerySender&&) = default;
153
  ~HealthCheckQuerySender() override = default;
12✔
154

155
  [[nodiscard]] bool active() const override
156
  {
×
157
    return true;
×
158
  }
×
159

160
  void handleResponse(const struct timeval& now, TCPResponse&& response) override
161
  {
12✔
162
    d_data->d_buffer = std::move(response.d_buffer);
12✔
163
    d_data->d_ds->submitHealthCheckResult(d_data->d_initial, ::handleResponse(d_data));
12✔
164
  }
12✔
165

166
  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
167
  {
×
168
    throw std::runtime_error("Unexpected XFR reponse to a health check query");
×
169
  }
×
170

171
  void notifyIOError(const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
172
  {
×
173
    ++d_data->d_ds->d_healthCheckMetrics.d_networkErrors;
×
174
    d_data->d_ds->submitHealthCheckResult(d_data->d_initial, false);
×
175
  }
×
176

177
private:
178
  std::shared_ptr<HealthCheckData> d_data;
179
};
180

181
static void healthCheckUDPCallback(int descriptor, FDMultiplexer::funcparam_t& param)
182
{
1,128✔
183
  auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
1,128✔
184
  const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
1,128✔
185

186
  ssize_t got = 0;
1,128✔
187
  ComboAddress from;
1,128✔
188
  do {
1,128✔
189
    from.sin4.sin_family = data->d_ds->d_config.remote.sin4.sin_family;
1,128✔
190
    auto fromlen = from.getSocklen();
1,128✔
191
    data->d_buffer.resize(512);
1,128✔
192

193
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
194
    got = recvfrom(data->d_udpSocket.getHandle(), data->d_buffer.data(), data->d_buffer.size(), 0, reinterpret_cast<sockaddr*>(&from), &fromlen);
1,128✔
195
    if (got < 0) {
1,128✔
196
      int savederrno = errno;
2✔
197
      if (savederrno == EINTR) {
2!
198
        /* interrupted before any data was available, let's try again */
199
        continue;
×
200
      }
×
201
      if (savederrno == EWOULDBLOCK || savederrno == EAGAIN) {
2!
202
        /* spurious wake-up, let's return to sleep */
203
        return;
×
204
      }
×
205

206
      if (verboseHealthChecks) {
2!
207
        infolog("Error receiving health check response from %s: %s", data->d_ds->d_config.remote.toStringWithPort(), stringerror(savederrno));
×
208
      }
×
209
      ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
2✔
210
      data->d_ds->submitHealthCheckResult(data->d_initial, false);
2✔
211
      data->d_mplexer.removeReadFD(descriptor);
2✔
212
      return;
2✔
213
    }
2✔
214
  } while (got < 0);
1,128!
215

216
  data->d_buffer.resize(static_cast<size_t>(got));
1,126✔
217

218
  /* we are using a connected socket but hey.. */
219
  if (from != data->d_ds->d_config.remote) {
1,126!
220
    if (verboseHealthChecks) {
×
221
      infolog("Invalid health check response received from %s, expecting one from %s", from.toStringWithPort(), data->d_ds->d_config.remote.toStringWithPort());
×
222
    }
×
223
    ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
×
224
    data->d_ds->submitHealthCheckResult(data->d_initial, false);
×
225
    return;
×
226
  }
×
227

228
  data->d_mplexer.removeReadFD(descriptor);
1,126✔
229
  data->d_ds->submitHealthCheckResult(data->d_initial, handleResponse(data));
1,126✔
230
}
1,126✔
231

232
static void healthCheckTCPCallback(int descriptor, FDMultiplexer::funcparam_t& param)
233
{
142✔
234
  auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
142✔
235

236
  IOStateGuard ioGuard(data->d_ioState);
142✔
237
  try {
142✔
238
    auto ioState = IOState::Done;
142✔
239

240
    if (data->d_tcpState == HealthCheckData::TCPState::WritingQuery) {
142✔
241
      ioState = data->d_tcpHandler->tryWrite(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
26✔
242
      if (ioState == IOState::Done) {
26✔
243
        data->d_bufferPos = 0;
18✔
244
        data->d_buffer.resize(sizeof(uint16_t));
18✔
245
        data->d_tcpState = HealthCheckData::TCPState::ReadingResponseSize;
18✔
246
      }
18✔
247
    }
26✔
248

249
    if (data->d_tcpState == HealthCheckData::TCPState::ReadingResponseSize) {
142✔
250
      ioState = data->d_tcpHandler->tryRead(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
105✔
251
      if (ioState == IOState::Done) {
105✔
252
        data->d_bufferPos = 0;
75✔
253
        uint16_t responseSize{0};
75✔
254
        memcpy(&responseSize, data->d_buffer.data(), sizeof(responseSize));
75✔
255
        data->d_buffer.resize(ntohs(responseSize));
75✔
256
        data->d_tcpState = HealthCheckData::TCPState::ReadingResponse;
75✔
257
      }
75✔
258
    }
105✔
259

260
    if (data->d_tcpState == HealthCheckData::TCPState::ReadingResponse) {
142✔
261
      ioState = data->d_tcpHandler->tryRead(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
104✔
262
      if (ioState == IOState::Done) {
104✔
263
        data->d_ds->submitHealthCheckResult(data->d_initial, handleResponse(data));
75✔
264
      }
75✔
265
    }
104✔
266

267
    if (ioState == IOState::Done) {
142✔
268
      /* remove us from the mplexer, we are done */
269
      data->d_ioState->update(ioState, healthCheckTCPCallback, data);
75✔
270
      if (data->d_tcpHandler->isTLS()) {
75✔
271
        try {
19✔
272
          auto sessions = data->d_tcpHandler->getTLSSessions();
19✔
273
          if (!sessions.empty()) {
19!
274
            g_sessionCache.putSessions(data->d_ds->getID(), time(nullptr), std::move(sessions));
19✔
275
          }
19✔
276
        }
19✔
277
        catch (const std::exception& e) {
19✔
278
          vinfolog("Unable to get a TLS session from the DoT healthcheck: %s", e.what());
×
279
        }
×
280
      }
19✔
281
    }
75✔
282
    else {
67✔
283
      data->d_ioState->update(ioState, healthCheckTCPCallback, data, data->d_ttd);
67✔
284
    }
67✔
285

286
    /* the state has been updated, we can release the guard */
287
    ioGuard.release();
142✔
288
  }
142✔
289
  catch (const std::exception& e) {
142✔
290
    ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
8✔
291
    data->d_ds->submitHealthCheckResult(data->d_initial, false);
8✔
292
    const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
8✔
293
    if (verboseHealthChecks) {
8!
294
      infolog("Error checking the health of backend %s: %s", data->d_ds->getNameWithAddr(), e.what());
×
295
    }
×
296
  }
8✔
297
  catch (...) {
142✔
298
    data->d_ds->submitHealthCheckResult(data->d_initial, false);
×
299
    const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
×
300
    if (verboseHealthChecks) {
×
301
      infolog("Unknown exception while checking the health of backend %s", data->d_ds->getNameWithAddr());
×
302
    }
×
303
  }
×
304
}
142✔
305

306
bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& downstream, bool initialCheck)
307
{
1,233✔
308
  const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
1,233✔
309
  try {
1,233✔
310
    uint16_t queryID = dnsdist::getRandomDNSID();
1,233✔
311
    DNSName checkName = downstream->d_config.checkName;
1,233✔
312
    uint16_t checkType = downstream->d_config.checkType.getCode();
1,233✔
313
    uint16_t checkClass = downstream->d_config.checkClass;
1,233✔
314
    dnsheader checkHeader{};
1,233✔
315
    memset(&checkHeader, 0, sizeof(checkHeader));
1,233✔
316

317
    checkHeader.qdcount = htons(1);
1,233✔
318
    checkHeader.id = queryID;
1,233✔
319

320
    checkHeader.rd = true;
1,233✔
321
    if (downstream->d_config.setCD) {
1,233!
322
      checkHeader.cd = true;
×
323
    }
×
324

325
    if (downstream->d_config.checkFunction) {
1,233✔
326
      auto lock = g_lua.lock();
3✔
327
      auto ret = downstream->d_config.checkFunction(checkName, checkType, checkClass, &checkHeader);
3✔
328
      checkName = std::get<0>(ret);
3✔
329
      checkType = std::get<1>(ret);
3✔
330
      checkClass = std::get<2>(ret);
3✔
331
    }
3✔
332

333
    PacketBuffer packet;
1,233✔
334
    GenericDNSPacketWriter<PacketBuffer> dpw(packet, checkName, checkType, checkClass);
1,233✔
335
    dnsheader* requestHeader = dpw.getHeader();
1,233✔
336
    *requestHeader = checkHeader;
1,233✔
337

338
    /* we need to compute that _before_ adding the proxy protocol payload */
339
    uint16_t packetSize = packet.size();
1,233✔
340
    std::string proxyProtocolPayload;
1,233✔
341
    size_t proxyProtocolPayloadSize = 0;
1,233✔
342
    if (downstream->d_config.useProxyProtocol) {
1,233✔
343
      proxyProtocolPayload = makeLocalProxyHeader();
20✔
344
      proxyProtocolPayloadSize = proxyProtocolPayload.size();
20✔
345
      if (!downstream->isDoH()) {
20!
346
        packet.insert(packet.begin(), proxyProtocolPayload.begin(), proxyProtocolPayload.end());
20✔
347
      }
20✔
348
    }
20✔
349

350
    Socket sock(downstream->d_config.remote.sin4.sin_family, downstream->doHealthcheckOverTCP() ? SOCK_STREAM : SOCK_DGRAM);
1,233✔
351

352
    sock.setNonBlocking();
1,233✔
353

354
#ifdef SO_BINDTODEVICE
1,233✔
355
    if (!downstream->d_config.sourceItfName.empty()) {
1,233✔
356
      int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, downstream->d_config.sourceItfName.c_str(), downstream->d_config.sourceItfName.length());
9✔
357
      if (res != 0 && verboseHealthChecks) {
9!
358
        infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", downstream->getNameWithAddr(), stringerror());
×
359
      }
×
360
    }
9✔
361
#endif
1,233✔
362

363
    if (!IsAnyAddress(downstream->d_config.sourceAddr)) {
1,233✔
364
      if (downstream->doHealthcheckOverTCP()) {
9!
365
        sock.setReuseAddr();
9✔
366
      }
9✔
367
#ifdef IP_BIND_ADDRESS_NO_PORT
9✔
368
      if (downstream->d_config.ipBindAddrNoPort) {
9!
369
        SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
9✔
370
      }
9✔
371
#endif
9✔
372
      sock.bind(downstream->d_config.sourceAddr, false);
9✔
373
    }
9✔
374

375
    auto data = std::make_shared<HealthCheckData>(*mplexer, downstream, std::move(checkName), checkType, checkClass, queryID);
1,233✔
376
    data->d_initial = initialCheck;
1,233✔
377

378
    gettimeofday(&data->d_ttd, nullptr);
1,233✔
379
    data->d_ttd.tv_sec += static_cast<decltype(data->d_ttd.tv_sec)>(downstream->d_config.checkTimeout / 1000); /* ms to seconds */
1,233✔
380
    data->d_ttd.tv_usec += static_cast<decltype(data->d_ttd.tv_usec)>((downstream->d_config.checkTimeout % 1000) * 1000); /* remaining ms to us */
1,233✔
381
    normalizeTV(data->d_ttd);
1,233✔
382

383
    if (!downstream->doHealthcheckOverTCP()) {
1,233✔
384
      sock.connect(downstream->d_config.remote);
1,138✔
385
      data->d_udpSocket = std::move(sock);
1,138✔
386
      ssize_t sent = udpClientSendRequestToBackend(downstream, data->d_udpSocket.getHandle(), packet, true);
1,138✔
387
      if (sent < 0) {
1,138!
388
        int ret = errno;
×
389
        if (verboseHealthChecks) {
×
390
          infolog("Error while sending a health check query (ID %d) to backend %s: %d", queryID, downstream->getNameWithAddr(), ret);
×
391
        }
×
392
        return false;
×
393
      }
×
394

395
      mplexer->addReadFD(data->d_udpSocket.getHandle(), &healthCheckUDPCallback, data, &data->d_ttd);
1,138✔
396
    }
1,138✔
397
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
95✔
398
    else if (downstream->isDoH()) {
95✔
399
      InternalQuery query(std::move(packet), InternalQueryState());
12✔
400
      query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
12✔
401
      auto sender = std::shared_ptr<TCPQuerySender>(new HealthCheckQuerySender(data));
12✔
402
      if (!sendH2Query(downstream, mplexer, sender, std::move(query), true)) {
12!
403
        data->d_ds->submitHealthCheckResult(data->d_initial, false);
×
404
      }
×
405
    }
12✔
406
#endif
83✔
407
    else {
83✔
408
      data->d_tcpHandler = std::make_unique<TCPIOHandler>(downstream->d_config.d_tlsSubjectName, downstream->d_config.d_tlsSubjectIsAddr, sock.releaseHandle(), timeval{downstream->d_config.checkTimeout, 0}, downstream->d_tlsCtx);
83✔
409
      data->d_ioState = std::make_unique<IOStateHandler>(*mplexer, data->d_tcpHandler->getDescriptor());
83✔
410
      if (downstream->d_tlsCtx) {
83✔
411
        try {
27✔
412
          time_t now = time(nullptr);
27✔
413
          auto tlsSession = g_sessionCache.getSession(downstream->getID(), now);
27✔
414
          if (tlsSession) {
27✔
415
            data->d_tcpHandler->setTLSSession(tlsSession);
12✔
416
          }
12✔
417
        }
27✔
418
        catch (const std::exception& e) {
27✔
419
          vinfolog("Unable to restore a TLS session for the DoT healthcheck for backend %s: %s", downstream->getNameWithAddr(), e.what());
×
420
        }
×
421
      }
27✔
422
      data->d_tcpHandler->tryConnect(downstream->d_config.tcpFastOpen, downstream->d_config.remote);
83✔
423

424
      const std::array<uint8_t, 2> sizeBytes = {static_cast<uint8_t>(packetSize / 256), static_cast<uint8_t>(packetSize % 256)};
83✔
425
      packet.insert(packet.begin() + static_cast<ssize_t>(proxyProtocolPayloadSize), sizeBytes.begin(), sizeBytes.end());
83✔
426
      data->d_buffer = std::move(packet);
83✔
427

428
      auto ioState = data->d_tcpHandler->tryWrite(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
83✔
429
      if (ioState == IOState::Done) {
83✔
430
        data->d_bufferPos = 0;
57✔
431
        data->d_buffer.resize(sizeof(uint16_t));
57✔
432
        data->d_tcpState = HealthCheckData::TCPState::ReadingResponseSize;
57✔
433
        ioState = IOState::NeedRead;
57✔
434
      }
57✔
435

436
      data->d_ioState->update(ioState, healthCheckTCPCallback, data, data->d_ttd);
83✔
437
    }
83✔
438

439
    return true;
1,233✔
440
  }
1,233✔
441
  catch (const std::exception& e) {
1,233✔
442
    if (verboseHealthChecks) {
8!
443
      infolog("Error checking the health of backend %s: %s", downstream->getNameWithAddr(), e.what());
×
444
    }
×
445
    return false;
8✔
446
  }
8✔
447
  catch (...) {
1,233✔
448
    if (verboseHealthChecks) {
×
449
      infolog("Unknown exception while checking the health of backend %s", downstream->getNameWithAddr());
×
450
    }
×
451
    return false;
×
452
  }
×
453
}
1,233✔
454

455
void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
456
{
1,210✔
457
  const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
1,210✔
458
  while (mplexer.getWatchedFDCount(false) > 0 || mplexer.getWatchedFDCount(true) > 0) {
2,555!
459
    struct timeval now
1,345✔
460
    {
1,345✔
461
    };
1,345✔
462
    int ret = mplexer.run(&now, 100);
1,345✔
463
    if (ret == -1) {
1,345!
464
      if (verboseHealthChecks) {
×
465
        infolog("Error while waiting for the health check response from backends: %d", ret);
×
466
      }
×
467
      break;
×
468
    }
×
469
    if (ret > 0) {
1,345✔
470
      /* we got at least one event other than a timeout */
471
      continue;
1,329✔
472
    }
1,329✔
473

474
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
16✔
475
    handleH2Timeouts(mplexer, now);
16✔
476
#endif
16✔
477

478
    auto timeouts = mplexer.getTimeouts(now);
16✔
479
    for (const auto& timeout : timeouts) {
16✔
480
      if (timeout.second.type() != typeid(std::shared_ptr<HealthCheckData>)) {
1!
481
        continue;
×
482
      }
×
483

484
      auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
1✔
485
      try {
1✔
486
        /* UDP does not have an IO state, H2 is handled separately */
487
        if (data->d_ioState) {
1!
488
          data->d_ioState.reset();
×
489
        }
×
490
        else {
1✔
491
          mplexer.removeReadFD(timeout.first);
1✔
492
        }
1✔
493
        if (verboseHealthChecks) {
1!
494
          infolog("Timeout while waiting for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
×
495
        }
×
496

497
        ++data->d_ds->d_healthCheckMetrics.d_timeOuts;
1✔
498
        data->d_ds->submitHealthCheckResult(initial, false);
1✔
499
      }
1✔
500
      catch (const std::exception& e) {
1✔
501
        /* this is not supposed to happen as the file descriptor has to be
502
           there for us to reach that code, and the submission code should not throw,
503
           but let's provide a nice error message if it ever does. */
504
        if (verboseHealthChecks) {
×
505
          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s: %s", data->d_queryID, data->d_ds->getNameWithAddr(), e.what());
×
506
        }
×
507
      }
×
508
      catch (...) {
1✔
509
        /* this is even less likely to happen */
510
        if (verboseHealthChecks) {
×
511
          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
×
512
        }
×
513
      }
×
514
    }
1✔
515

516
    timeouts = mplexer.getTimeouts(now, true);
16✔
517
    for (const auto& timeout : timeouts) {
16!
518
      if (timeout.second.type() != typeid(std::shared_ptr<HealthCheckData>)) {
×
519
        continue;
×
520
      }
×
521
      auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
×
522
      try {
×
523
        /* UDP does not block while writing, H2 is handled separately */
524
        data->d_ioState.reset();
×
525
        if (verboseHealthChecks) {
×
526
          infolog("Timeout while waiting for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
×
527
        }
×
528

529
        ++data->d_ds->d_healthCheckMetrics.d_timeOuts;
×
530
        data->d_ds->submitHealthCheckResult(initial, false);
×
531
      }
×
532
      catch (const std::exception& e) {
×
533
        /* this is not supposed to happen as the submission code should not throw,
534
           but let's provide a nice error message if it ever does. */
535
        if (verboseHealthChecks) {
×
536
          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s: %s", data->d_queryID, data->d_ds->getNameWithAddr(), e.what());
×
537
        }
×
538
      }
×
539
      catch (...) {
×
540
        /* this is even less likely to happen */
541
        if (verboseHealthChecks) {
×
542
          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
×
543
        }
×
544
      }
×
545
    }
×
546
  }
16✔
547
}
1,210✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc