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

PowerDNS / pdns / 13012068652

28 Jan 2025 01:59PM UTC coverage: 64.71% (+0.01%) from 64.699%
13012068652

Pull #14724

github

web-flow
Merge b15562560 into db18c3a17
Pull Request #14724: dnsdist: Add meson support

38328 of 90334 branches covered (42.43%)

Branch coverage included in aggregate %.

361 of 513 new or added lines in 35 files covered. (70.37%)

42 existing lines in 13 files now uncovered.

128150 of 166934 relevant lines covered (76.77%)

4540890.91 hits per line

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

58.95
/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,263✔
45
  }
1,263✔
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,243✔
68
  const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
1,243✔
69
  const auto& downstream = data->d_ds;
1,243✔
70
  try {
1,243✔
71
    if (data->d_buffer.size() < sizeof(dnsheader)) {
1,243!
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,243✔
80
    if (responseHeader.get()->id != data->d_queryID) {
1,243!
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,243!
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,243!
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,243!
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,243✔
113
    uint16_t receivedClass{0};
1,243✔
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,243✔
116

117
    if (receivedName != data->d_checkName || receivedType != data->d_checkType || receivedClass != data->d_checkClass) {
1,243!
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,243✔
125
  catch (const std::exception& e) {
1,243✔
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,243✔
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,243✔
140
}
1,243✔
141

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

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

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

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

174
  void notifyIOError(const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
175
  {
×
NEW
176
    (void)now;
×
NEW
177
    (void)response;
×
178
    ++d_data->d_ds->d_healthCheckMetrics.d_networkErrors;
×
179
    d_data->d_ds->submitHealthCheckResult(d_data->d_initial, false);
×
180
  }
×
181

182
private:
183
  std::shared_ptr<HealthCheckData> d_data;
184
};
185

186
static void healthCheckUDPCallback(int descriptor, FDMultiplexer::funcparam_t& param)
187
{
1,154✔
188
  auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
1,154✔
189
  const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
1,154✔
190

191
  ssize_t got = 0;
1,154✔
192
  ComboAddress from;
1,154✔
193
  do {
1,154✔
194
    from.sin4.sin_family = data->d_ds->d_config.remote.sin4.sin_family;
1,154✔
195
    auto fromlen = from.getSocklen();
1,154✔
196
    data->d_buffer.resize(512);
1,154✔
197

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

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

221
  data->d_buffer.resize(static_cast<size_t>(got));
1,152✔
222

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

233
  data->d_mplexer.removeReadFD(descriptor);
1,152✔
234
  data->d_ds->submitHealthCheckResult(data->d_initial, handleResponse(data));
1,152✔
235
}
1,152✔
236

237
static void healthCheckTCPCallback(int descriptor, FDMultiplexer::funcparam_t& param)
238
{
149✔
239
  (void)descriptor;
149✔
240
  auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
149✔
241

242
  IOStateGuard ioGuard(data->d_ioState);
149✔
243
  try {
149✔
244
    auto ioState = IOState::Done;
149✔
245

246
    if (data->d_tcpState == HealthCheckData::TCPState::WritingQuery) {
149✔
247
      ioState = data->d_tcpHandler->tryWrite(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
29✔
248
      if (ioState == IOState::Done) {
29✔
249
        data->d_bufferPos = 0;
21✔
250
        data->d_buffer.resize(sizeof(uint16_t));
21✔
251
        data->d_tcpState = HealthCheckData::TCPState::ReadingResponseSize;
21✔
252
      }
21✔
253
    }
29✔
254

255
    if (data->d_tcpState == HealthCheckData::TCPState::ReadingResponseSize) {
149✔
256
      ioState = data->d_tcpHandler->tryRead(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
114✔
257
      if (ioState == IOState::Done) {
114✔
258
        data->d_bufferPos = 0;
77✔
259
        uint16_t responseSize{0};
77✔
260
        memcpy(&responseSize, data->d_buffer.data(), sizeof(responseSize));
77✔
261
        data->d_buffer.resize(ntohs(responseSize));
77✔
262
        data->d_tcpState = HealthCheckData::TCPState::ReadingResponse;
77✔
263
      }
77✔
264
    }
114✔
265

266
    if (data->d_tcpState == HealthCheckData::TCPState::ReadingResponse) {
149✔
267
      ioState = data->d_tcpHandler->tryRead(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
104✔
268
      if (ioState == IOState::Done) {
104✔
269
        data->d_ds->submitHealthCheckResult(data->d_initial, handleResponse(data));
77✔
270
      }
77✔
271
    }
104✔
272

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

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

312
bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& downstream, bool initialCheck)
313
{
1,263✔
314
  const auto verboseHealthChecks = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verboseHealthChecks;
1,263✔
315
  try {
1,263✔
316
    uint16_t queryID = dnsdist::getRandomDNSID();
1,263✔
317
    DNSName checkName = downstream->d_config.checkName;
1,263✔
318
    uint16_t checkType = downstream->d_config.checkType.getCode();
1,263✔
319
    uint16_t checkClass = downstream->d_config.checkClass;
1,263✔
320
    dnsheader checkHeader{};
1,263✔
321
    memset(&checkHeader, 0, sizeof(checkHeader));
1,263✔
322

323
    checkHeader.qdcount = htons(1);
1,263✔
324
    checkHeader.id = queryID;
1,263✔
325

326
    checkHeader.rd = true;
1,263✔
327
    if (downstream->d_config.setCD) {
1,263!
328
      checkHeader.cd = true;
×
329
    }
×
330

331
    if (downstream->d_config.checkFunction) {
1,263✔
332
      auto lock = g_lua.lock();
3✔
333
      auto ret = downstream->d_config.checkFunction(checkName, checkType, checkClass, &checkHeader);
3✔
334
      checkName = std::get<0>(ret);
3✔
335
      checkType = std::get<1>(ret);
3✔
336
      checkClass = std::get<2>(ret);
3✔
337
    }
3✔
338

339
    PacketBuffer packet;
1,263✔
340
    GenericDNSPacketWriter<PacketBuffer> dpw(packet, checkName, checkType, checkClass);
1,263✔
341
    dnsheader* requestHeader = dpw.getHeader();
1,263✔
342
    *requestHeader = checkHeader;
1,263✔
343

344
    /* we need to compute that _before_ adding the proxy protocol payload */
345
    uint16_t packetSize = packet.size();
1,263✔
346
    std::string proxyProtocolPayload;
1,263✔
347
    size_t proxyProtocolPayloadSize = 0;
1,263✔
348
    if (downstream->d_config.useProxyProtocol) {
1,263✔
349
      proxyProtocolPayload = makeLocalProxyHeader();
20✔
350
      proxyProtocolPayloadSize = proxyProtocolPayload.size();
20✔
351
      if (!downstream->isDoH()) {
20!
352
        packet.insert(packet.begin(), proxyProtocolPayload.begin(), proxyProtocolPayload.end());
20✔
353
      }
20✔
354
    }
20✔
355

356
    Socket sock(downstream->d_config.remote.sin4.sin_family, downstream->doHealthcheckOverTCP() ? SOCK_STREAM : SOCK_DGRAM);
1,263✔
357

358
    sock.setNonBlocking();
1,263✔
359

360
#ifdef SO_BINDTODEVICE
1,263✔
361
    if (!downstream->d_config.sourceItfName.empty()) {
1,263✔
362
      int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, downstream->d_config.sourceItfName.c_str(), downstream->d_config.sourceItfName.length());
9✔
363
      if (res != 0 && verboseHealthChecks) {
9!
364
        infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", downstream->getNameWithAddr(), stringerror());
×
365
      }
×
366
    }
9✔
367
#endif
1,263✔
368

369
    if (!IsAnyAddress(downstream->d_config.sourceAddr)) {
1,263✔
370
      if (downstream->doHealthcheckOverTCP()) {
9!
371
        sock.setReuseAddr();
9✔
372
      }
9✔
373
#ifdef IP_BIND_ADDRESS_NO_PORT
9✔
374
      if (downstream->d_config.ipBindAddrNoPort) {
9!
375
        SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
9✔
376
      }
9✔
377
#endif
9✔
378
      sock.bind(downstream->d_config.sourceAddr, false);
9✔
379
    }
9✔
380

381
    auto data = std::make_shared<HealthCheckData>(*mplexer, downstream, std::move(checkName), checkType, checkClass, queryID);
1,263✔
382
    data->d_initial = initialCheck;
1,263✔
383

384
    gettimeofday(&data->d_ttd, nullptr);
1,263✔
385
    data->d_ttd.tv_sec += static_cast<decltype(data->d_ttd.tv_sec)>(downstream->d_config.checkTimeout / 1000); /* ms to seconds */
1,263✔
386
    data->d_ttd.tv_usec += static_cast<decltype(data->d_ttd.tv_usec)>((downstream->d_config.checkTimeout % 1000) * 1000); /* remaining ms to us */
1,263✔
387
    normalizeTV(data->d_ttd);
1,263✔
388

389
    if (!downstream->doHealthcheckOverTCP()) {
1,263✔
390
      sock.connect(downstream->d_config.remote);
1,164✔
391
      data->d_udpSocket = std::move(sock);
1,164✔
392
      ssize_t sent = udpClientSendRequestToBackend(downstream, data->d_udpSocket.getHandle(), packet, true);
1,164✔
393
      if (sent < 0) {
1,164!
394
        int ret = errno;
×
395
        if (verboseHealthChecks) {
×
396
          infolog("Error while sending a health check query (ID %d) to backend %s: %d", queryID, downstream->getNameWithAddr(), ret);
×
397
        }
×
398
        return false;
×
399
      }
×
400

401
      mplexer->addReadFD(data->d_udpSocket.getHandle(), &healthCheckUDPCallback, data, &data->d_ttd);
1,164✔
402
    }
1,164✔
403
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
99✔
404
    else if (downstream->isDoH()) {
99✔
405
      InternalQuery query(std::move(packet), InternalQueryState());
14✔
406
      query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
14✔
407
      auto sender = std::shared_ptr<TCPQuerySender>(new HealthCheckQuerySender(data));
14✔
408
      if (!sendH2Query(downstream, mplexer, sender, std::move(query), true)) {
14!
409
        data->d_ds->submitHealthCheckResult(data->d_initial, false);
×
410
      }
×
411
    }
14✔
412
#endif
85✔
413
    else {
85✔
414
      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);
85✔
415
      data->d_ioState = std::make_unique<IOStateHandler>(*mplexer, data->d_tcpHandler->getDescriptor());
85✔
416
      if (downstream->d_tlsCtx) {
85✔
417
        try {
29✔
418
          time_t now = time(nullptr);
29✔
419
          auto tlsSession = g_sessionCache.getSession(downstream->getID(), now);
29✔
420
          if (tlsSession) {
29✔
421
            data->d_tcpHandler->setTLSSession(tlsSession);
13✔
422
          }
13✔
423
        }
29✔
424
        catch (const std::exception& e) {
29✔
425
          vinfolog("Unable to restore a TLS session for the DoT healthcheck for backend %s: %s", downstream->getNameWithAddr(), e.what());
×
426
        }
×
427
      }
29✔
428
      data->d_tcpHandler->tryConnect(downstream->d_config.tcpFastOpen, downstream->d_config.remote);
85✔
429

430
      const std::array<uint8_t, 2> sizeBytes = {static_cast<uint8_t>(packetSize / 256), static_cast<uint8_t>(packetSize % 256)};
85✔
431
      packet.insert(packet.begin() + static_cast<ssize_t>(proxyProtocolPayloadSize), sizeBytes.begin(), sizeBytes.end());
85✔
432
      data->d_buffer = std::move(packet);
85✔
433

434
      auto ioState = data->d_tcpHandler->tryWrite(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
85✔
435
      if (ioState == IOState::Done) {
85✔
436
        data->d_bufferPos = 0;
56✔
437
        data->d_buffer.resize(sizeof(uint16_t));
56✔
438
        data->d_tcpState = HealthCheckData::TCPState::ReadingResponseSize;
56✔
439
        ioState = IOState::NeedRead;
56✔
440
      }
56✔
441

442
      data->d_ioState->update(ioState, healthCheckTCPCallback, data, data->d_ttd);
85✔
443
    }
85✔
444

445
    return true;
1,263✔
446
  }
1,263✔
447
  catch (const std::exception& e) {
1,263✔
448
    if (verboseHealthChecks) {
8!
449
      infolog("Error checking the health of backend %s: %s", downstream->getNameWithAddr(), e.what());
×
450
    }
×
451
    return false;
8✔
452
  }
8✔
453
  catch (...) {
1,263✔
454
    if (verboseHealthChecks) {
×
455
      infolog("Unknown exception while checking the health of backend %s", downstream->getNameWithAddr());
×
456
    }
×
457
    return false;
×
458
  }
×
459
}
1,263✔
460

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

480
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
16✔
481
    handleH2Timeouts(mplexer, now);
16✔
482
#endif
16✔
483

484
    auto timeouts = mplexer.getTimeouts(now);
16✔
485
    for (const auto& timeout : timeouts) {
16✔
486
      if (timeout.second.type() != typeid(std::shared_ptr<HealthCheckData>)) {
1!
487
        continue;
×
488
      }
×
489

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

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

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

535
        ++data->d_ds->d_healthCheckMetrics.d_timeOuts;
×
536
        data->d_ds->submitHealthCheckResult(initial, false);
×
537
      }
×
538
      catch (const std::exception& e) {
×
539
        /* this is not supposed to happen as the submission code should not throw,
540
           but let's provide a nice error message if it ever does. */
541
        if (verboseHealthChecks) {
×
542
          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());
×
543
        }
×
544
      }
×
545
      catch (...) {
×
546
        /* this is even less likely to happen */
547
        if (verboseHealthChecks) {
×
548
          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
×
549
        }
×
550
      }
×
551
    }
×
552
  }
16✔
553
}
1,241✔
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