• 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

68.73
/pdns/dnsdistdist/dnsdist.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 "config.h"
24

25
#include <cstdint>
26
#include <filesystem>
27
#include <fstream>
28
#include <getopt.h>
29
#include <grp.h>
30
#include <limits>
31
#include <netinet/tcp.h>
32
#include <pwd.h>
33
#include <set>
34
#include <sys/resource.h>
35
#include <unistd.h>
36

37
#include "dnsdist-systemd.hh"
38
#ifdef HAVE_SYSTEMD
39
#include <systemd/sd-daemon.h>
40
#endif
41

42
#include "dnsdist.hh"
43
#include "dnsdist-async.hh"
44
#include "dnsdist-cache.hh"
45
#include "dnsdist-carbon.hh"
46
#include "dnsdist-configuration.hh"
47
#include "dnsdist-configuration-yaml.hh"
48
#include "dnsdist-console.hh"
49
#include "dnsdist-crypto.hh"
50
#include "dnsdist-discovery.hh"
51
#include "dnsdist-dynblocks.hh"
52
#include "dnsdist-ecs.hh"
53
#include "dnsdist-edns.hh"
54
#include "dnsdist-frontend.hh"
55
#include "dnsdist-healthchecks.hh"
56
#include "dnsdist-lua.hh"
57
#include "dnsdist-lua-hooks.hh"
58
#include "dnsdist-nghttp2.hh"
59
#include "dnsdist-proxy-protocol.hh"
60
#include "dnsdist-random.hh"
61
#include "dnsdist-rings.hh"
62
#include "dnsdist-rules.hh"
63
#include "dnsdist-secpoll.hh"
64
#include "dnsdist-self-answers.hh"
65
#include "dnsdist-snmp.hh"
66
#include "dnsdist-tcp.hh"
67
#include "dnsdist-tcp-downstream.hh"
68
#include "dnsdist-web.hh"
69
#include "dnsdist-xsk.hh"
70

71
#include "base64.hh"
72
#include "capabilities.hh"
73
#include "coverage.hh"
74
#include "delaypipe.hh"
75
#include "doh.hh"
76
#include "dolog.hh"
77
#include "dnsname.hh"
78
#include "ednsoptions.hh"
79
#include "gettime.hh"
80
#include "lock.hh"
81
#include "misc.hh"
82
#include "sstuff.hh"
83
#include "threadname.hh"
84
#include "xsk.hh"
85

86
/* Known sins:
87

88
   Receiver is currently single threaded
89
      not *that* bad actually, but now that we are thread safe, might want to scale
90
*/
91

92
/* the RuleAction plan
93
   Set of Rules, if one matches, it leads to an Action
94
   Both rules and actions could conceivably be Lua based.
95
   On the C++ side, both could be inherited from a class Rule and a class Action,
96
   on the Lua side we can't do that. */
97

98
using std::thread;
99

100
string g_outputBuffer;
101

102
shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
103

104
/* UDP: the grand design. Per socket we listen on for incoming queries there is one thread.
105
   Then we have a bunch of connected sockets for talking to downstream servers.
106
   We send directly to those sockets.
107

108
   For the return path, per downstream server we have a thread that listens to responses.
109

110
   Per socket there is an array of 2^16 states, when we send out a packet downstream, we note
111
   there the original requestor and the original id. The new ID is the offset in the array.
112

113
   When an answer comes in on a socket, we look up the offset by the id, and lob it to the
114
   original requestor.
115

116
   IDs are assigned by atomic increments of the socket offset.
117
 */
118

119
Rings g_rings;
120

121
// we are not willing to receive a bigger UDP response than that, no matter what
122
static constexpr size_t s_maxUDPResponsePacketSize{4096U};
123
static size_t const s_initialUDPPacketBufferSize = s_maxUDPResponsePacketSize + DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE;
124
static_assert(s_initialUDPPacketBufferSize <= UINT16_MAX, "Packet size should fit in a uint16_t");
125

126
static void sendfromto(int sock, const PacketBuffer& buffer, const ComboAddress& from, const ComboAddress& dest)
127
{
2,124✔
128
  const int flags = 0;
2,124✔
129
  if (from.sin4.sin_family == 0) {
2,124✔
130
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
131
    auto ret = sendto(sock, buffer.data(), buffer.size(), flags, reinterpret_cast<const struct sockaddr*>(&dest), dest.getSocklen());
2,119✔
132
    if (ret == -1) {
2,119!
133
      int error = errno;
×
134
      vinfolog("Error sending UDP response to %s: %s", dest.toStringWithPort(), stringerror(error));
×
135
    }
×
136
    return;
2,119✔
137
  }
2,119✔
138

139
  try {
5✔
140
    sendMsgWithOptions(sock, buffer.data(), buffer.size(), &dest, &from, 0, 0);
5✔
141
  }
5✔
142
  catch (const std::exception& exp) {
5✔
143
    vinfolog("Error sending UDP response from %s to %s: %s", from.toStringWithPort(), dest.toStringWithPort(), exp.what());
×
144
  }
×
145
}
5✔
146

147
static void truncateTC(PacketBuffer& packet, size_t maximumSize, unsigned int qnameWireLength, bool addEDNSToSelfGeneratedResponses)
148
{
4✔
149
  try {
4✔
150
    bool hadEDNS = false;
4✔
151
    uint16_t payloadSize = 0;
4✔
152
    uint16_t zValue = 0;
4✔
153

154
    if (addEDNSToSelfGeneratedResponses) {
4!
155
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
156
      hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &zValue);
4✔
157
    }
4✔
158

159
    packet.resize(static_cast<uint16_t>(sizeof(dnsheader) + qnameWireLength + DNS_TYPE_SIZE + DNS_CLASS_SIZE));
4✔
160
    dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
4✔
161
      header.ancount = 0;
4✔
162
      header.arcount = 0;
4✔
163
      header.nscount = 0;
4✔
164
      return true;
4✔
165
    });
4✔
166

167
    if (hadEDNS) {
4✔
168
      addEDNS(packet, maximumSize, (zValue & EDNS_HEADER_FLAG_DO) != 0, payloadSize, 0);
1✔
169
    }
1✔
170
  }
4✔
171
  catch (...) {
4✔
172
    ++dnsdist::metrics::g_stats.truncFail;
×
173
  }
×
174
}
4✔
175

176
#ifndef DISABLE_DELAY_PIPE
177
struct DelayedPacket
178
{
179
  int fd{-1};
180
  PacketBuffer packet;
181
  ComboAddress destination;
182
  ComboAddress origDest;
183
  void operator()() const
184
  {
4✔
185
    sendfromto(fd, packet, origDest, destination);
4✔
186
  }
4✔
187
};
188

189
static std::unique_ptr<DelayPipe<DelayedPacket>> g_delay{nullptr};
190
#endif /* DISABLE_DELAY_PIPE */
191

192
static void doLatencyStats(dnsdist::Protocol protocol, double udiff)
193
{
4,850✔
194
  constexpr auto doAvg = [](pdns::stat_double_t& var, double n, double weight) {
19,400✔
195
    var.store((weight - 1) * var.load() / weight + n / weight);
19,400✔
196
  };
19,400✔
197

198
  if (protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP) {
4,850✔
199
    if (udiff < 1000) {
2,112✔
200
      ++dnsdist::metrics::g_stats.latency0_1;
2,011✔
201
    }
2,011✔
202
    else if (udiff < 10000) {
101✔
203
      ++dnsdist::metrics::g_stats.latency1_10;
93✔
204
    }
93✔
205
    else if (udiff < 50000) {
8!
206
      ++dnsdist::metrics::g_stats.latency10_50;
×
207
    }
×
208
    else if (udiff < 100000) {
8!
209
      ++dnsdist::metrics::g_stats.latency50_100;
×
210
    }
×
211
    else if (udiff < 1000000) {
8!
212
      ++dnsdist::metrics::g_stats.latency100_1000;
8✔
213
    }
8✔
214
    else {
×
215
      ++dnsdist::metrics::g_stats.latencySlow;
×
216
    }
×
217

218
    dnsdist::metrics::g_stats.latencySum += static_cast<unsigned long>(udiff) / 1000;
2,112✔
219
    ++dnsdist::metrics::g_stats.latencyCount;
2,112✔
220

221
    doAvg(dnsdist::metrics::g_stats.latencyAvg100, udiff, 100);
2,112✔
222
    doAvg(dnsdist::metrics::g_stats.latencyAvg1000, udiff, 1000);
2,112✔
223
    doAvg(dnsdist::metrics::g_stats.latencyAvg10000, udiff, 10000);
2,112✔
224
    doAvg(dnsdist::metrics::g_stats.latencyAvg1000000, udiff, 1000000);
2,112✔
225
  }
2,112✔
226
  else if (protocol == dnsdist::Protocol::DoTCP || protocol == dnsdist::Protocol::DNSCryptTCP) {
2,738✔
227
    doAvg(dnsdist::metrics::g_stats.latencyTCPAvg100, udiff, 100);
1,942✔
228
    doAvg(dnsdist::metrics::g_stats.latencyTCPAvg1000, udiff, 1000);
1,942✔
229
    doAvg(dnsdist::metrics::g_stats.latencyTCPAvg10000, udiff, 10000);
1,942✔
230
    doAvg(dnsdist::metrics::g_stats.latencyTCPAvg1000000, udiff, 1000000);
1,942✔
231
  }
1,942✔
232
  else if (protocol == dnsdist::Protocol::DoT) {
796✔
233
    doAvg(dnsdist::metrics::g_stats.latencyDoTAvg100, udiff, 100);
516✔
234
    doAvg(dnsdist::metrics::g_stats.latencyDoTAvg1000, udiff, 1000);
516✔
235
    doAvg(dnsdist::metrics::g_stats.latencyDoTAvg10000, udiff, 10000);
516✔
236
    doAvg(dnsdist::metrics::g_stats.latencyDoTAvg1000000, udiff, 1000000);
516✔
237
  }
516✔
238
  else if (protocol == dnsdist::Protocol::DoH) {
280✔
239
    doAvg(dnsdist::metrics::g_stats.latencyDoHAvg100, udiff, 100);
204✔
240
    doAvg(dnsdist::metrics::g_stats.latencyDoHAvg1000, udiff, 1000);
204✔
241
    doAvg(dnsdist::metrics::g_stats.latencyDoHAvg10000, udiff, 10000);
204✔
242
    doAvg(dnsdist::metrics::g_stats.latencyDoHAvg1000000, udiff, 1000000);
204✔
243
  }
204✔
244
  else if (protocol == dnsdist::Protocol::DoQ) {
76✔
245
    doAvg(dnsdist::metrics::g_stats.latencyDoQAvg100, udiff, 100);
50✔
246
    doAvg(dnsdist::metrics::g_stats.latencyDoQAvg1000, udiff, 1000);
50✔
247
    doAvg(dnsdist::metrics::g_stats.latencyDoQAvg10000, udiff, 10000);
50✔
248
    doAvg(dnsdist::metrics::g_stats.latencyDoQAvg1000000, udiff, 1000000);
50✔
249
  }
50✔
250
  else if (protocol == dnsdist::Protocol::DoH3) {
26!
251
    doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg100, udiff, 100);
26✔
252
    doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg1000, udiff, 1000);
26✔
253
    doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg10000, udiff, 10000);
26✔
254
    doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg1000000, udiff, 1000000);
26✔
255
  }
26✔
256
}
4,850✔
257

258
bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote, bool allowEmptyResponse)
259
{
3,812✔
260
  if (response.size() < sizeof(dnsheader)) {
3,812!
261
    return false;
×
262
  }
×
263

264
  const dnsheader_aligned dnsHeader(response.data());
3,812✔
265
  if (dnsHeader->qr == 0) {
3,812!
266
    ++dnsdist::metrics::g_stats.nonCompliantResponses;
×
267
    if (remote) {
×
268
      ++remote->nonCompliantResponses;
×
269
    }
×
270
    return false;
×
271
  }
×
272

273
  if (dnsHeader->qdcount == 0) {
3,812✔
274
    if ((dnsHeader->rcode != RCode::NoError && dnsHeader->rcode != RCode::NXDomain) || allowEmptyResponse) {
14✔
275
      return true;
10✔
276
    }
10✔
277

278
    ++dnsdist::metrics::g_stats.nonCompliantResponses;
4✔
279
    if (remote) {
4!
280
      ++remote->nonCompliantResponses;
4✔
281
    }
4✔
282
    return false;
4✔
283
  }
14✔
284

285
  uint16_t rqtype{};
3,798✔
286
  uint16_t rqclass{};
3,798✔
287
  DNSName rqname;
3,798✔
288
  try {
3,798✔
289
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
290
    rqname = DNSName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, &rqtype, &rqclass);
3,798✔
291
  }
3,798✔
292
  catch (const std::exception& e) {
3,798✔
293
    if (remote && !response.empty() && static_cast<size_t>(response.size()) > sizeof(dnsheader)) {
×
294
      infolog("Backend %s sent us a response with id %d that did not parse: %s", remote->d_config.remote.toStringWithPort(), ntohs(dnsHeader->id), e.what());
×
295
    }
×
296
    ++dnsdist::metrics::g_stats.nonCompliantResponses;
×
297
    if (remote) {
×
298
      ++remote->nonCompliantResponses;
×
299
    }
×
300
    return false;
×
301
  }
×
302

303
  return rqtype == qtype && rqclass == qclass && rqname == qname;
3,798!
304
}
3,798✔
305

306
static void restoreFlags(struct dnsheader* dnsHeader, uint16_t origFlags)
307
{
4,844✔
308
  static const uint16_t rdMask = 1 << FLAGS_RD_OFFSET;
4,844✔
309
  static const uint16_t cdMask = 1 << FLAGS_CD_OFFSET;
4,844✔
310
  static const uint16_t restoreFlagsMask = UINT16_MAX & ~(rdMask | cdMask);
4,844✔
311
  uint16_t* flags = getFlagsFromDNSHeader(dnsHeader);
4,844✔
312
  /* clear the flags we are about to restore */
313
  *flags &= restoreFlagsMask;
4,844✔
314
  /* only keep the flags we want to restore */
315
  origFlags &= ~restoreFlagsMask;
4,844✔
316
  /* set the saved flags as they were */
317
  *flags |= origFlags;
4,844✔
318
}
4,844✔
319

320
static bool fixUpQueryTurnedResponse(DNSQuestion& dnsQuestion, const uint16_t origFlags)
321
{
440✔
322
  dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [origFlags](dnsheader& header) {
440✔
323
    restoreFlags(&header, origFlags);
440✔
324
    return true;
440✔
325
  });
440✔
326

327
  return addEDNSToQueryTurnedResponse(dnsQuestion);
440✔
328
}
440✔
329

330
static bool fixUpResponse(PacketBuffer& response, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, bool* zeroScope)
331
{
3,910✔
332
  if (response.size() < sizeof(dnsheader)) {
3,910!
333
    return false;
×
334
  }
×
335

336
  dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [origFlags](dnsheader& header) {
3,910✔
337
    restoreFlags(&header, origFlags);
3,910✔
338
    return true;
3,910✔
339
  });
3,910✔
340

341
  if (response.size() == sizeof(dnsheader)) {
3,910✔
342
    return true;
10✔
343
  }
10✔
344

345
  if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_fixupCase) {
3,900✔
346
    const auto& realname = qname.getStorage();
2✔
347
    if (response.size() >= (sizeof(dnsheader) + realname.length())) {
2!
348
      memcpy(&response.at(sizeof(dnsheader)), realname.c_str(), realname.length());
2✔
349
    }
2✔
350
  }
2✔
351

352
  if (ednsAdded || ecsAdded) {
3,900✔
353
    uint16_t optStart{};
204✔
354
    size_t optLen = 0;
204✔
355
    bool last = false;
204✔
356

357
    int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
204✔
358

359
    if (res == 0) {
204✔
360
      if (zeroScope != nullptr) { // this finds if an EDNS Client Subnet scope was set, and if it is 0
59✔
361
        size_t optContentStart = 0;
7✔
362
        uint16_t optContentLen = 0;
7✔
363
        /* we need at least 4 bytes after the option length (family: 2, source prefix-length: 1, scope prefix-length: 1) */
364
        if (isEDNSOptionInOpt(response, optStart, optLen, EDNSOptionCode::ECS, &optContentStart, &optContentLen) && optContentLen >= 4) {
7!
365
          /* see if the EDNS Client Subnet SCOPE PREFIX-LENGTH byte in position 3 is set to 0, which is the only thing
366
             we care about. */
367
          *zeroScope = response.at(optContentStart + 3) == 0;
7✔
368
        }
7✔
369
      }
7✔
370

371
      if (ednsAdded) {
59✔
372
        /* we added the entire OPT RR,
373
           therefore we need to remove it entirely */
374
        if (last) {
41!
375
          /* simply remove the last AR */
376
          response.resize(response.size() - optLen);
41✔
377
          dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [](dnsheader& header) {
41✔
378
            uint16_t arcount = ntohs(header.arcount);
41✔
379
            arcount--;
41✔
380
            header.arcount = htons(arcount);
41✔
381
            return true;
41✔
382
          });
41✔
383
        }
41✔
384
        else {
×
385
          /* Removing an intermediary RR could lead to compression error */
386
          PacketBuffer rewrittenResponse;
×
387
          if (rewriteResponseWithoutEDNS(response, rewrittenResponse) == 0) {
×
388
            response = std::move(rewrittenResponse);
×
389
          }
×
390
          else {
×
391
            warnlog("Error rewriting content");
×
392
          }
×
393
        }
×
394
      }
41✔
395
      else {
18✔
396
        /* the OPT RR was already present, but without ECS,
397
           we need to remove the ECS option if any */
398
        if (last) {
18!
399
          /* nothing after the OPT RR, we can simply remove the
400
             ECS option */
401
          size_t existingOptLen = optLen;
18✔
402
          // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
403
          removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
18✔
404
          response.resize(response.size() - (existingOptLen - optLen));
18✔
405
        }
18✔
406
        else {
×
407
          PacketBuffer rewrittenResponse;
×
408
          /* Removing an intermediary RR could lead to compression error */
409
          if (rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, rewrittenResponse) == 0) {
×
410
            response = std::move(rewrittenResponse);
×
411
          }
×
412
          else {
×
413
            warnlog("Error rewriting content");
×
414
          }
×
415
        }
×
416
      }
18✔
417
    }
59✔
418
  }
204✔
419

420
  return true;
3,900✔
421
}
3,910✔
422

423
#ifdef HAVE_DNSCRYPT
424
static bool encryptResponse(PacketBuffer& response, size_t maximumSize, bool tcp, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery)
425
{
4,865✔
426
  if (dnsCryptQuery) {
4,865✔
427
    int res = dnsCryptQuery->encryptResponse(response, maximumSize, tcp);
34✔
428
    if (res != 0) {
34!
429
      /* dropping response */
430
      vinfolog("Error encrypting the response, dropping.");
×
431
      return false;
×
432
    }
×
433
  }
34✔
434
  return true;
4,865✔
435
}
4,865✔
436
#endif /* HAVE_DNSCRYPT */
437

438
bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, DNSResponse& dnsResponse)
439
{
5,559✔
440
  DNSResponseAction::Action action = DNSResponseAction::Action::None;
5,559✔
441
  std::string ruleresult;
5,559✔
442
  for (const auto& rrule : respRuleActions) {
5,559✔
443
    if (rrule.d_rule->matches(&dnsResponse)) {
634✔
444
      ++rrule.d_rule->d_matches;
286✔
445
      action = (*rrule.d_action)(&dnsResponse, &ruleresult);
286✔
446
      switch (action) {
286✔
447
      case DNSResponseAction::Action::Allow:
168✔
448
        return true;
168✔
449
        break;
×
450
      case DNSResponseAction::Action::Drop:
11✔
451
        return false;
11✔
452
        break;
×
453
      case DNSResponseAction::Action::HeaderModify:
16✔
454
        return true;
16✔
455
        break;
×
456
      case DNSResponseAction::Action::ServFail:
×
457
        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
×
458
          header.rcode = RCode::ServFail;
×
459
          return true;
×
460
        });
×
461
        return true;
×
462
        break;
×
463
      case DNSResponseAction::Action::Truncate:
4✔
464
        if (!dnsResponse.overTCP()) {
4✔
465
          dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
2✔
466
            header.tc = true;
2✔
467
            header.qr = true;
2✔
468
            return true;
2✔
469
          });
2✔
470
          truncateTC(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength(), dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses);
2✔
471
          ++dnsdist::metrics::g_stats.ruleTruncated;
2✔
472
          return true;
2✔
473
        }
2✔
474
        break;
2✔
475
        /* non-terminal actions follow */
476
      case DNSResponseAction::Action::Delay:
4✔
477
        pdns::checked_stoi_into(dnsResponse.ids.delayMsec, ruleresult); // sorry
4✔
478
        break;
4✔
479
      case DNSResponseAction::Action::None:
71✔
480
        break;
71✔
481
      }
286✔
482
    }
286✔
483
  }
634✔
484

485
  return true;
5,362✔
486
}
5,559✔
487

488
bool processResponseAfterRules(PacketBuffer& response, DNSResponse& dnsResponse, [[maybe_unused]] bool muted)
489
{
3,910✔
490
  bool zeroScope = false;
3,910✔
491
  if (!fixUpResponse(response, dnsResponse.ids.qname, dnsResponse.ids.origFlags, dnsResponse.ids.ednsAdded, dnsResponse.ids.ecsAdded, dnsResponse.ids.useZeroScope ? &zeroScope : nullptr)) {
3,910!
492
    return false;
×
493
  }
×
494

495
  if (dnsResponse.ids.packetCache && !dnsResponse.ids.selfGenerated && !dnsResponse.ids.skipCache && (!dnsResponse.ids.forwardedOverUDP || response.size() <= s_maxUDPResponsePacketSize)) {
3,910!
496
    if (!dnsResponse.ids.useZeroScope) {
216✔
497
      /* if the query was not suitable for zero-scope, for
498
         example because it had an existing ECS entry so the hash is
499
         not really 'no ECS', so just insert it for the existing subnet
500
         since:
501
         - we don't have the correct hash for a non-ECS query
502
         - inserting with hash computed before the ECS replacement but with
503
         the subnet extracted _after_ the replacement would not work.
504
      */
505
      zeroScope = false;
205✔
506
    }
205✔
507
    uint32_t cacheKey = dnsResponse.ids.cacheKey;
216✔
508
    if (dnsResponse.ids.protocol == dnsdist::Protocol::DoH && !dnsResponse.ids.forwardedOverUDP) {
216✔
509
      cacheKey = dnsResponse.ids.cacheKeyTCP;
2✔
510
      // disable zeroScope in that case, as we only have the "no-ECS" cache key for UDP
511
      zeroScope = false;
2✔
512
    }
2✔
513
    if (zeroScope) {
216✔
514
      // if zeroScope, pass the pre-ECS hash-key and do not pass the subnet to the cache
515
      cacheKey = dnsResponse.ids.cacheKeyNoECS;
3✔
516
    }
3✔
517
    dnsResponse.ids.packetCache->insert(cacheKey, zeroScope ? boost::none : dnsResponse.ids.subnet, dnsResponse.ids.cacheFlags, dnsResponse.ids.dnssecOK, dnsResponse.ids.qname, dnsResponse.ids.qtype, dnsResponse.ids.qclass, response, dnsResponse.ids.forwardedOverUDP, dnsResponse.getHeader()->rcode, dnsResponse.ids.tempFailureTTL);
216✔
518

519
    const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
216✔
520
    const auto& cacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules);
216✔
521
    if (!applyRulesToResponse(cacheInsertedRespRuleActions, dnsResponse)) {
216!
522
      return false;
×
523
    }
×
524
  }
216✔
525

526
  if (dnsResponse.ids.ttlCap > 0) {
3,910!
527
    dnsdist::PacketMangling::restrictDNSPacketTTLs(dnsResponse.getMutableData(), 0, dnsResponse.ids.ttlCap);
×
528
  }
×
529

530
  if (dnsResponse.ids.d_extendedError) {
3,910✔
531
    dnsdist::edns::addExtendedDNSError(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.d_extendedError->infoCode, dnsResponse.ids.d_extendedError->extraText);
6✔
532
  }
6✔
533

534
#ifdef HAVE_DNSCRYPT
3,910✔
535
  if (!muted) {
3,910✔
536
    if (!encryptResponse(response, dnsResponse.getMaximumSize(), dnsResponse.overTCP(), dnsResponse.ids.dnsCryptQuery)) {
3,906!
537
      return false;
×
538
    }
×
539
  }
3,906✔
540
#endif /* HAVE_DNSCRYPT */
3,910✔
541

542
  return true;
3,910✔
543
}
3,910✔
544

545
bool processResponse(PacketBuffer& response, DNSResponse& dnsResponse, bool muted)
546
{
3,931✔
547
  const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
3,931✔
548
  const auto& respRuleActions = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::ResponseRules);
3,931✔
549

550
  if (!applyRulesToResponse(respRuleActions, dnsResponse)) {
3,931✔
551
    return false;
7✔
552
  }
7✔
553

554
  if (dnsResponse.isAsynchronous()) {
3,924✔
555
    return true;
156✔
556
  }
156✔
557

558
  return processResponseAfterRules(response, dnsResponse, muted);
3,768✔
559
}
3,924✔
560

561
static size_t getInitialUDPPacketBufferSize(bool expectProxyProtocol)
562
{
676✔
563
  static_assert(dnsdist::configuration::s_udpIncomingBufferSize <= s_initialUDPPacketBufferSize, "The incoming buffer size should not be larger than s_initialUDPPacketBufferSize");
676✔
564

565
  const auto& runtimeConfig = dnsdist::configuration::getCurrentRuntimeConfiguration();
676✔
566
  if (!expectProxyProtocol || runtimeConfig.d_proxyProtocolACL.empty()) {
676✔
567
    return s_initialUDPPacketBufferSize;
671✔
568
  }
671✔
569

570
  return s_initialUDPPacketBufferSize + runtimeConfig.d_proxyProtocolMaximumSize;
5✔
571
}
676✔
572

573
static size_t getMaximumIncomingPacketSize(const ClientState& clientState)
574
{
332✔
575
  if (clientState.dnscryptCtx) {
332✔
576
    return getInitialUDPPacketBufferSize(clientState.d_enableProxyProtocol);
4✔
577
  }
4✔
578

579
  const auto& runtimeConfig = dnsdist::configuration::getCurrentRuntimeConfiguration();
328✔
580
  if (!clientState.d_enableProxyProtocol || runtimeConfig.d_proxyProtocolACL.empty()) {
328✔
581
    return dnsdist::configuration::s_udpIncomingBufferSize;
324✔
582
  }
324✔
583

584
  return dnsdist::configuration::s_udpIncomingBufferSize + runtimeConfig.d_proxyProtocolMaximumSize;
4✔
585
}
328✔
586

587
bool sendUDPResponse(int origFD, const PacketBuffer& response, [[maybe_unused]] const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
588
{
2,124✔
589
#ifndef DISABLE_DELAY_PIPE
2,124✔
590
  if (delayMsec > 0 && g_delay != nullptr) {
2,124!
591
    DelayedPacket delayed{origFD, response, origRemote, origDest};
4✔
592
    g_delay->submit(delayed, delayMsec);
4✔
593
    return true;
4✔
594
  }
4✔
595
#endif /* DISABLE_DELAY_PIPE */
2,120✔
596
  // NOLINTNEXTLINE(readability-suspicious-call-argument)
597
  sendfromto(origFD, response, origDest, origRemote);
2,120✔
598
  return true;
2,120✔
599
}
2,124✔
600

601
void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend)
602
{
4,364✔
603
  handleResponseSent(ids.qname, ids.qtype, udiff, client, backend, size, cleartextDH, outgoingProtocol, ids.protocol, fromBackend);
4,364✔
604
}
4,364✔
605

606
void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend)
607
{
4,850✔
608
  if (g_rings.shouldRecordResponses()) {
4,850!
609
    timespec now{};
4,850✔
610
    gettime(&now);
4,850✔
611
    g_rings.insertResponse(now, client, qname, qtype, static_cast<unsigned int>(udiff), size, cleartextDH, backend, outgoingProtocol);
4,850✔
612
  }
4,850✔
613

614
  switch (cleartextDH.rcode) {
4,850✔
615
  case RCode::NXDomain:
36✔
616
    ++dnsdist::metrics::g_stats.frontendNXDomain;
36✔
617
    break;
36✔
618
  case RCode::ServFail:
235✔
619
    if (fromBackend) {
235✔
620
      ++dnsdist::metrics::g_stats.servfailResponses;
162✔
621
    }
162✔
622
    ++dnsdist::metrics::g_stats.frontendServFail;
235✔
623
    break;
235✔
624
  case RCode::NoError:
4,392✔
625
    ++dnsdist::metrics::g_stats.frontendNoError;
4,392✔
626
    break;
4,392✔
627
  }
4,850✔
628

629
  doLatencyStats(incomingProtocol, udiff);
4,850✔
630
}
4,850✔
631

632
static void handleResponseForUDPClient(InternalQueryState& ids, PacketBuffer& response, const std::shared_ptr<DownstreamState>& backend, bool isAsync, bool selfGenerated)
633
{
1,710✔
634
  DNSResponse dnsResponse(ids, response, backend);
1,710✔
635

636
  if (ids.udpPayloadSize > 0 && response.size() > ids.udpPayloadSize) {
1,710!
637
    vinfolog("Got a response of size %d while the initial UDP payload size was %d, truncating", response.size(), ids.udpPayloadSize);
×
638
    truncateTC(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength(), dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses);
×
639
    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
×
640
      header.tc = true;
×
641
      return true;
×
642
    });
×
643
  }
×
644
  else if (dnsResponse.getHeader()->tc && dnsdist::configuration::getCurrentRuntimeConfiguration().d_truncateTC) {
1,710!
645
    truncateTC(response, dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength(), dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses);
2✔
646
  }
2✔
647

648
  /* when the answer is encrypted in place, we need to get a copy
649
     of the original header before encryption to fill the ring buffer */
650
  dnsheader cleartextDH{};
1,710✔
651
  memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
1,710✔
652

653
  if (!isAsync) {
1,710✔
654
    if (!processResponse(response, dnsResponse, ids.cs != nullptr && ids.cs->muted)) {
1,680!
655
      return;
3✔
656
    }
3✔
657

658
    if (dnsResponse.isAsynchronous()) {
1,677✔
659
      return;
27✔
660
    }
27✔
661
  }
1,677✔
662

663
  ++dnsdist::metrics::g_stats.responses;
1,680✔
664
  if (ids.cs != nullptr) {
1,680✔
665
    ++ids.cs->responses;
1,676✔
666
  }
1,676✔
667

668
  bool muted = true;
1,680✔
669
  if (ids.cs != nullptr && !ids.cs->muted && !ids.isXSK()) {
1,680!
670
    sendUDPResponse(ids.cs->udpFD, response, dnsResponse.ids.delayMsec, ids.hopLocal, ids.hopRemote);
1,676✔
671
    muted = false;
1,676✔
672
  }
1,676✔
673

674
  if (!selfGenerated) {
1,680✔
675
    double udiff = ids.queryRealTime.udiff();
1,670✔
676
    if (!muted) {
1,670!
677
      vinfolog("Got answer from %s, relayed to %s (UDP), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
1,670✔
678
    }
1,670✔
679
    else {
×
680
      if (!ids.isXSK()) {
×
681
        vinfolog("Got answer from %s, NOT relayed to %s (UDP) since that frontend is muted, took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
×
682
      }
×
683
      else {
×
684
        vinfolog("Got answer from %s, relayed to %s (UDP via XSK), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
×
685
      }
×
686
    }
×
687

688
    handleResponseSent(ids, udiff, dnsResponse.ids.origRemote, backend->d_config.remote, response.size(), cleartextDH, backend->getProtocol(), true);
1,670✔
689
  }
1,670✔
690
  else {
10✔
691
    handleResponseSent(ids, 0., dnsResponse.ids.origRemote, ComboAddress(), response.size(), cleartextDH, dnsdist::Protocol::DoUDP, false);
10✔
692
  }
10✔
693
}
1,680✔
694

695
bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, InternalQueryState&& ids)
696
{
1,665✔
697

698
  const dnsheader_aligned dnsHeader(response.data());
1,665✔
699
  auto queryId = dnsHeader->id;
1,665✔
700

701
  if (!responseContentMatches(response, ids.qname, ids.qtype, ids.qclass, dss, dnsdist::configuration::getCurrentRuntimeConfiguration().d_allowEmptyResponse)) {
1,665✔
702
    dss->restoreState(queryId, std::move(ids));
3✔
703
    return false;
3✔
704
  }
3✔
705

706
  auto dohUnit = std::move(ids.du);
1,662✔
707
  dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [&ids](dnsheader& header) {
1,662✔
708
    header.id = ids.origID;
1,662✔
709
    return true;
1,662✔
710
  });
1,662✔
711
  ++dss->responses;
1,662✔
712

713
  double udiff = ids.queryRealTime.udiff();
1,662✔
714
  // do that _before_ the processing, otherwise it's not fair to the backend
715
  dss->latencyUsec = (127.0 * dss->latencyUsec / 128.0) + udiff / 128.0;
1,662✔
716
  dss->reportResponse(dnsHeader->rcode);
1,662✔
717

718
  /* don't call processResponse for DOH */
719
  if (dohUnit) {
1,662✔
720
#ifdef HAVE_DNS_OVER_HTTPS
118✔
721
    // DoH query, we cannot touch dohUnit after that
722
    DOHUnitInterface::handleUDPResponse(std::move(dohUnit), std::move(response), std::move(ids), dss);
118✔
723
#endif
118✔
724
    return false;
118✔
725
  }
118✔
726

727
  handleResponseForUDPClient(ids, response, dss, false, false);
1,544✔
728
  return true;
1,544✔
729
}
1,662✔
730

731
// listens on a dedicated socket, lobs answers from downstream servers to original requestors
732
void responderThread(std::shared_ptr<DownstreamState> dss)
733
{
340✔
734
  try {
340✔
735
    setThreadName("dnsdist/respond");
340✔
736
    const size_t initialBufferSize = getInitialUDPPacketBufferSize(false);
340✔
737
    /* allocate one more byte so we can detect truncation */
738
    PacketBuffer response(initialBufferSize + 1);
340✔
739
    uint16_t queryId = 0;
340✔
740
    std::vector<int> sockets;
340✔
741
    sockets.reserve(dss->sockets.size());
340✔
742

743
    for (;;) {
2,009✔
744
      try {
2,009✔
745
        if (dss->isStopped()) {
2,009✔
746
          break;
2✔
747
        }
2✔
748

749
        if (!dss->connected) {
2,007!
750
          /* the sockets are not connected yet, likely because we detected a problem,
751
             tried to reconnect and it failed. We will try to reconnect after the next
752
             successful health-check (unless reconnectOnUp is false), or when trying
753
             to send in the UDP listener thread, but until then we simply need to wait. */
754
          dss->waitUntilConnected();
×
755
          continue;
×
756
        }
×
757

758
        dss->pickSocketsReadyForReceiving(sockets);
2,007✔
759

760
        /* check a second time here because we might have waited quite a bit
761
           since the first check */
762
        if (dss->isStopped()) {
2,007!
763
          break;
×
764
        }
×
765

766
        for (const auto& sockDesc : sockets) {
2,007✔
767
          /* allocate one more byte so we can detect truncation */
768
          // NOLINTNEXTLINE(bugprone-use-after-move): resizing a vector has no preconditions so it is valid to do so after moving it
769
          response.resize(initialBufferSize + 1);
2,007✔
770
          ssize_t got = recv(sockDesc, response.data(), response.size(), 0);
2,007✔
771

772
          if (got == 0 && dss->isStopped()) {
2,007!
773
            break;
2✔
774
          }
2✔
775

776
          if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader) || static_cast<size_t>(got) == (initialBufferSize + 1)) {
2,005!
777
            continue;
2✔
778
          }
2✔
779

780
          response.resize(static_cast<size_t>(got));
2,003✔
781
          const dnsheader_aligned dnsHeader(response.data());
2,003✔
782
          queryId = dnsHeader->id;
2,003✔
783

784
          auto ids = dss->getState(queryId);
2,003✔
785
          if (!ids) {
2,003!
786
            continue;
×
787
          }
×
788

789
          if (!ids->isXSK() && sockDesc != ids->backendFD) {
2,003!
790
            dss->restoreState(queryId, std::move(*ids));
×
791
            continue;
×
792
          }
×
793

794
          if (processResponderPacket(dss, response, std::move(*ids)) && ids->isXSK() && ids->cs->xskInfoResponder) {
2,003!
795
#ifdef HAVE_XSK
×
796
            auto& xskInfo = ids->cs->xskInfoResponder;
×
797
            auto xskPacket = xskInfo->getEmptyFrame();
×
798
            if (!xskPacket) {
×
799
              continue;
×
800
            }
×
801
            xskPacket->setHeader(ids->xskPacketHeader);
×
802
            if (!xskPacket->setPayload(response)) {
×
803
            }
×
804
            if (ids->delayMsec > 0) {
×
805
              xskPacket->addDelay(ids->delayMsec);
×
806
            }
×
807
            xskPacket->updatePacket();
×
808
            xskInfo->pushToSendQueue(*xskPacket);
×
809
            xskInfo->notifyXskSocket();
×
810
#endif /* HAVE_XSK */
×
811
          }
×
812
        }
2,003✔
813
      }
2,007✔
814
      catch (const std::exception& e) {
2,009✔
815
        vinfolog("Got an error in UDP responder thread while parsing a response from %s, id %d: %s", dss->d_config.remote.toStringWithPort(), queryId, e.what());
2!
816
      }
2✔
817
    }
2,009✔
818
  }
340✔
819
  catch (const std::exception& e) {
340✔
820
    errlog("UDP responder thread died because of exception: %s", e.what());
×
821
  }
×
822
  catch (const PDNSException& e) {
340✔
823
    errlog("UDP responder thread died because of PowerDNS exception: %s", e.reason);
×
824
  }
×
825
  catch (...) {
340✔
826
    errlog("UDP responder thread died because of an exception: %s", "unknown");
×
827
  }
×
828
}
340✔
829

830
RecursiveLockGuarded<LuaContext> g_lua{LuaContext()};
831

832
static void spoofResponseFromString(DNSQuestion& dnsQuestion, const string& spoofContent, bool raw)
833
{
52✔
834
  string result;
52✔
835

836
  if (raw) {
52✔
837
    dnsdist::ResponseConfig config;
6✔
838
    std::vector<std::string> raws;
6✔
839
    stringtok(raws, spoofContent, ",");
6✔
840
    dnsdist::self_answers::generateAnswerFromRDataEntries(dnsQuestion, raws, std::nullopt, config);
6✔
841
  }
6✔
842
  else {
46✔
843
    std::vector<std::string> addrs;
46✔
844
    stringtok(addrs, spoofContent, " ,");
46✔
845

846
    if (addrs.size() == 1) {
46✔
847
      dnsdist::ResponseConfig config;
44✔
848
      try {
44✔
849
        ComboAddress spoofAddr(spoofContent);
44✔
850
        dnsdist::self_answers::generateAnswerFromIPAddresses(dnsQuestion, {spoofAddr}, config);
44✔
851
      }
44✔
852
      catch (const PDNSException& e) {
44✔
853
        DNSName cname(spoofContent);
29✔
854
        dnsdist::self_answers::generateAnswerFromCNAME(dnsQuestion, cname, config);
29✔
855
      }
29✔
856
    }
44✔
857
    else {
2✔
858
      dnsdist::ResponseConfig config;
2✔
859
      std::vector<ComboAddress> cas;
2✔
860
      for (const auto& addr : addrs) {
4✔
861
        try {
4✔
862
          cas.emplace_back(addr);
4✔
863
        }
4✔
864
        catch (...) {
4✔
865
        }
×
866
      }
4✔
867
      dnsdist::self_answers::generateAnswerFromIPAddresses(dnsQuestion, cas, config);
2✔
868
    }
2✔
869
  }
46✔
870
}
52✔
871

872
static void spoofPacketFromString(DNSQuestion& dnsQuestion, const string& spoofContent)
873
{
2✔
874
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
875
  dnsdist::self_answers::generateAnswerFromRawPacket(dnsQuestion, PacketBuffer(spoofContent.data(), spoofContent.data() + spoofContent.size()));
2✔
876
}
2✔
877

878
bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dnsQuestion, std::string& ruleresult, bool& drop)
879
{
1,917✔
880
  if (dnsQuestion.isAsynchronous()) {
1,917✔
881
    return false;
202✔
882
  }
202✔
883

884
  auto setRCode = [&dnsQuestion](uint8_t rcode) {
1,715✔
885
    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [rcode](dnsheader& header) {
22✔
886
      header.rcode = rcode;
22✔
887
      header.qr = true;
22✔
888
      return true;
22✔
889
    });
22✔
890
  };
22✔
891

892
  switch (action) {
1,715!
893
  case DNSAction::Action::Allow:
26✔
894
    return true;
26✔
895
    break;
×
896
  case DNSAction::Action::Drop:
25✔
897
    ++dnsdist::metrics::g_stats.ruleDrop;
25✔
898
    drop = true;
25✔
899
    return true;
25✔
900
    break;
×
901
  case DNSAction::Action::Nxdomain:
12✔
902
    setRCode(RCode::NXDomain);
12✔
903
    return true;
12✔
904
    break;
×
905
  case DNSAction::Action::Refused:
8✔
906
    setRCode(RCode::Refused);
8✔
907
    return true;
8✔
908
    break;
×
909
  case DNSAction::Action::ServFail:
2✔
910
    setRCode(RCode::ServFail);
2✔
911
    return true;
2✔
912
    break;
×
913
  case DNSAction::Action::Spoof:
46✔
914
    spoofResponseFromString(dnsQuestion, ruleresult, false);
46✔
915
    return true;
46✔
916
    break;
×
917
  case DNSAction::Action::SpoofPacket:
2✔
918
    spoofPacketFromString(dnsQuestion, ruleresult);
2✔
919
    return true;
2✔
920
    break;
×
921
  case DNSAction::Action::SpoofRaw:
6✔
922
    spoofResponseFromString(dnsQuestion, ruleresult, true);
6✔
923
    return true;
6✔
924
    break;
×
925
  case DNSAction::Action::Truncate:
8✔
926
    if (!dnsQuestion.overTCP()) {
8✔
927
      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
7✔
928
        header.tc = true;
7✔
929
        header.qr = true;
7✔
930
        header.ra = header.rd;
7✔
931
        header.aa = false;
7✔
932
        header.ad = false;
7✔
933
        return true;
7✔
934
      });
7✔
935
      ++dnsdist::metrics::g_stats.ruleTruncated;
7✔
936
      return true;
7✔
937
    }
7✔
938
    break;
1✔
939
  case DNSAction::Action::HeaderModify:
287✔
940
    return true;
287✔
941
    break;
×
942
  case DNSAction::Action::Pool:
416✔
943
    /* we need to keep this because a custom Lua action can return
944
       DNSAction.Spoof, 'poolname' */
945
    dnsQuestion.ids.poolName = ruleresult;
416✔
946
    return true;
416✔
947
    break;
×
948
  case DNSAction::Action::NoRecurse:
×
949
    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
×
950
      header.rd = false;
×
951
      return true;
×
952
    });
×
953
    return true;
×
954
    break;
×
955
    /* non-terminal actions follow */
956
  case DNSAction::Action::Delay:
2✔
957
    pdns::checked_stoi_into(dnsQuestion.ids.delayMsec, ruleresult); // sorry
2✔
958
    break;
2✔
959
  case DNSAction::Action::SetTag:
×
960
    /* unsupported for non-dynamic block */
961
  case DNSAction::Action::None:
875✔
962
    /* fall-through */
963
  case DNSAction::Action::NoOp:
875!
964
    break;
875✔
965
  }
1,715✔
966

967
  /* false means that we don't stop the processing */
968
  return false;
878✔
969
}
1,715✔
970

971
static bool applyRulesChainToQuery(const std::vector<dnsdist::rules::RuleAction>& rules, DNSQuestion& dnsQuestion)
972
{
5,234✔
973
  DNSAction::Action action = DNSAction::Action::None;
5,234✔
974
  string ruleresult;
5,234✔
975
  bool drop = false;
5,234✔
976

977
  for (const auto& rule : rules) {
5,752✔
978
    if (!rule.d_rule->matches(&dnsQuestion)) {
5,752✔
979
      continue;
3,837✔
980
    }
3,837✔
981

982
    rule.d_rule->d_matches++;
1,915✔
983
    action = (*rule.d_action)(&dnsQuestion, &ruleresult);
1,915✔
984
    if (processRulesResult(action, dnsQuestion, ruleresult, drop)) {
1,915✔
985
      break;
835✔
986
    }
835✔
987
  }
1,915✔
988

989
  return !drop;
5,234✔
990
}
5,234✔
991

992
static bool applyRulesToQuery(DNSQuestion& dnsQuestion, const timespec& now)
993
{
5,023✔
994
  if (g_rings.shouldRecordQueries()) {
5,023!
995
    g_rings.insertQuery(now, dnsQuestion.ids.origRemote, dnsQuestion.ids.qname, dnsQuestion.ids.qtype, dnsQuestion.getData().size(), *dnsQuestion.getHeader(), dnsQuestion.getProtocol());
5,023✔
996
  }
5,023✔
997

998
  {
5,023✔
999
    const auto& runtimeConfig = dnsdist::configuration::getCurrentRuntimeConfiguration();
5,023✔
1000
    if (runtimeConfig.d_queryCountConfig.d_enabled) {
5,023!
1001
      string qname = dnsQuestion.ids.qname.toLogString();
×
1002
      bool countQuery{true};
×
1003
      if (runtimeConfig.d_queryCountConfig.d_filter) {
×
1004
        auto lock = g_lua.lock();
×
1005
        std::tie(countQuery, qname) = runtimeConfig.d_queryCountConfig.d_filter(&dnsQuestion);
×
1006
      }
×
1007

1008
      if (countQuery) {
×
1009
        auto records = dnsdist::QueryCount::g_queryCountRecords.write_lock();
×
1010
        if (records->count(qname) == 0) {
×
1011
          (*records)[qname] = 0;
×
1012
        }
×
1013
        (*records)[qname]++;
×
1014
      }
×
1015
    }
×
1016
  }
5,023✔
1017

1018
#ifndef DISABLE_DYNBLOCKS
5,023✔
1019
  const auto defaultDynBlockAction = dnsdist::configuration::getCurrentRuntimeConfiguration().d_dynBlockAction;
5,023✔
1020
  auto setRCode = [&dnsQuestion](uint8_t rcode) {
5,023✔
1021
    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [rcode](dnsheader& header) {
10✔
1022
      header.rcode = rcode;
10✔
1023
      header.qr = true;
10✔
1024
      return true;
10✔
1025
    });
10✔
1026
  };
10✔
1027

1028
  /* the Dynamic Block mechanism supports address and port ranges, so we need to pass the full address and port */
1029
  if (auto* got = dnsdist::DynamicBlocks::getClientAddressDynamicRules().lookup(AddressAndPortRange(dnsQuestion.ids.origRemote, dnsQuestion.ids.origRemote.isIPv4() ? 32 : 128, 16))) {
5,023✔
1030
    auto updateBlockStats = [&got]() {
458✔
1031
      ++dnsdist::metrics::g_stats.dynBlocked;
33✔
1032
      got->second.blocks++;
33✔
1033
    };
33✔
1034

1035
    if (now < got->second.until) {
458✔
1036
      DNSAction::Action action = got->second.action;
77✔
1037
      if (action == DNSAction::Action::None) {
77✔
1038
        action = defaultDynBlockAction;
21✔
1039
      }
21✔
1040

1041
      switch (action) {
77✔
1042
      case DNSAction::Action::NoOp:
43✔
1043
        /* do nothing */
1044
        break;
43✔
1045

1046
      case DNSAction::Action::Nxdomain:
2✔
1047
        vinfolog("Query from %s turned into NXDomain because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
2!
1048
        updateBlockStats();
2✔
1049

1050
        setRCode(RCode::NXDomain);
2✔
1051
        return true;
2✔
1052

1053
      case DNSAction::Action::Refused:
8✔
1054
        vinfolog("Query from %s refused because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
8!
1055
        updateBlockStats();
8✔
1056

1057
        setRCode(RCode::Refused);
8✔
1058
        return true;
8✔
1059

1060
      case DNSAction::Action::Truncate:
2✔
1061
        if (!dnsQuestion.overTCP()) {
2✔
1062
          updateBlockStats();
1✔
1063
          vinfolog("Query from %s truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
1!
1064
          dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
1✔
1065
            header.tc = true;
1✔
1066
            header.qr = true;
1✔
1067
            header.ra = header.rd;
1✔
1068
            header.aa = false;
1✔
1069
            header.ad = false;
1✔
1070
            return true;
1✔
1071
          });
1✔
1072
          return true;
1✔
1073
        }
1✔
1074
        else {
1✔
1075
          vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
1!
1076
        }
1✔
1077
        break;
1✔
1078
      case DNSAction::Action::NoRecurse:
1!
1079
        updateBlockStats();
×
1080
        vinfolog("Query from %s setting rd=0 because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
×
1081
        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
×
1082
          header.rd = false;
×
1083
          return true;
×
1084
        });
×
1085
        return true;
×
1086
      case DNSAction::Action::SetTag: {
2✔
1087
        if (!got->second.tagSettings) {
2!
1088
          vinfolog("Skipping set tag dynamic block for query from %s because of missing options", dnsQuestion.ids.origRemote.toStringWithPort());
×
1089
          break;
×
1090
        }
×
1091
        updateBlockStats();
2✔
1092
        const auto& tagName = got->second.tagSettings->d_name;
2✔
1093
        const auto& tagValue = got->second.tagSettings->d_value;
2✔
1094
        dnsQuestion.setTag(tagName, tagValue);
2✔
1095
        vinfolog("Query from %s setting tag %s to %s because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), tagName, tagValue);
2!
1096
        return true;
2✔
1097
      }
2✔
1098
      default:
20✔
1099
        updateBlockStats();
20✔
1100
        vinfolog("Query from %s dropped because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
20!
1101
        return false;
20✔
1102
      }
77✔
1103
    }
77✔
1104
  }
458✔
1105

1106
  if (auto* got = dnsdist::DynamicBlocks::getSuffixDynamicRules().lookup(dnsQuestion.ids.qname)) {
4,990!
1107
    auto updateBlockStats = [&got]() {
×
1108
      ++dnsdist::metrics::g_stats.dynBlocked;
×
1109
      got->blocks++;
×
1110
    };
×
1111

1112
    if (now < got->until) {
×
1113
      DNSAction::Action action = got->action;
×
1114
      if (action == DNSAction::Action::None) {
×
1115
        action = defaultDynBlockAction;
×
1116
      }
×
1117
      switch (action) {
×
1118
      case DNSAction::Action::NoOp:
×
1119
        /* do nothing */
1120
        break;
×
1121
      case DNSAction::Action::Nxdomain:
×
1122
        vinfolog("Query from %s for %s turned into NXDomain because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
×
1123
        updateBlockStats();
×
1124

1125
        setRCode(RCode::NXDomain);
×
1126
        return true;
×
1127
      case DNSAction::Action::Refused:
×
1128
        vinfolog("Query from %s for %s refused because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
×
1129
        updateBlockStats();
×
1130

1131
        setRCode(RCode::Refused);
×
1132
        return true;
×
1133
      case DNSAction::Action::Truncate:
×
1134
        if (!dnsQuestion.overTCP()) {
×
1135
          updateBlockStats();
×
1136

1137
          vinfolog("Query from %s for %s truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
×
1138
          dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
×
1139
            header.tc = true;
×
1140
            header.qr = true;
×
1141
            header.ra = header.rd;
×
1142
            header.aa = false;
×
1143
            header.ad = false;
×
1144
            return true;
×
1145
          });
×
1146
          return true;
×
1147
        }
×
1148
        else {
×
1149
          vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
×
1150
        }
×
1151
        break;
×
1152
      case DNSAction::Action::NoRecurse:
×
1153
        updateBlockStats();
×
1154
        vinfolog("Query from %s setting rd=0 because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
×
1155
        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
×
1156
          header.rd = false;
×
1157
          return true;
×
1158
        });
×
1159
        return true;
×
1160
      case DNSAction::Action::SetTag: {
×
1161
        if (!got->tagSettings) {
×
1162
          vinfolog("Skipping set tag dynamic block for query from %s because of missing options", dnsQuestion.ids.origRemote.toStringWithPort());
×
1163
          break;
×
1164
        }
×
1165
        updateBlockStats();
×
1166
        const auto& tagName = got->tagSettings->d_name;
×
1167
        const auto& tagValue = got->tagSettings->d_value;
×
1168
        dnsQuestion.setTag(tagName, tagValue);
×
1169
        vinfolog("Query from %s setting tag %s to %s because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), tagName, tagValue);
×
1170
        return true;
×
1171
      }
×
1172
      default:
×
1173
        updateBlockStats();
×
1174
        vinfolog("Query from %s for %s dropped because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
×
1175
        return false;
×
1176
      }
×
1177
    }
×
1178
  }
×
1179
#endif /* DISABLE_DYNBLOCKS */
4,990✔
1180

1181
  const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
4,990✔
1182
  const auto& queryRules = dnsdist::rules::getRuleChain(chains, dnsdist::rules::RuleChain::Rules);
4,990✔
1183
  return applyRulesChainToQuery(queryRules, dnsQuestion);
4,990✔
1184
}
4,990✔
1185

1186
ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& backend, const int socketDesc, const PacketBuffer& request, bool healthCheck)
1187
{
2,825✔
1188
  ssize_t result = 0;
2,825✔
1189

1190
  if (backend->d_config.sourceItf == 0) {
2,825!
1191
    result = send(socketDesc, request.data(), request.size(), 0);
2,825✔
1192
  }
2,825✔
1193
  else {
×
1194
    msghdr msgh{};
×
1195
    iovec iov{};
×
1196
    cmsgbuf_aligned cbuf;
×
1197
    ComboAddress remote(backend->d_config.remote);
×
1198
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
1199
    fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), const_cast<char*>(reinterpret_cast<const char*>(request.data())), request.size(), &remote);
×
1200
    addCMsgSrcAddr(&msgh, &cbuf, &backend->d_config.sourceAddr, static_cast<int>(backend->d_config.sourceItf));
×
1201
    result = sendmsg(socketDesc, &msgh, 0);
×
1202
  }
×
1203

1204
  if (result == -1) {
2,825!
1205
    int savederrno = errno;
×
1206
    vinfolog("Error sending request to backend %s: %s", backend->d_config.remote.toStringWithPort(), stringerror(savederrno));
×
1207

1208
    /* This might sound silly, but on Linux send() might fail with EINVAL
1209
       if the interface the socket was bound to doesn't exist anymore.
1210
       We don't want to reconnect the real socket if the healthcheck failed,
1211
       because it's not using the same socket.
1212
    */
1213
    if (!healthCheck) {
×
1214
      if (savederrno == EINVAL || savederrno == ENODEV || savederrno == ENETUNREACH || savederrno == EHOSTUNREACH || savederrno == EBADF) {
×
1215
        backend->reconnect();
×
1216
      }
×
1217
      backend->reportTimeoutOrError();
×
1218
    }
×
1219
  }
×
1220

1221
  return result;
2,825✔
1222
}
2,825✔
1223

1224
static bool isUDPQueryAcceptable(ClientState& clientState, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, bool& expectProxyProtocol)
1225
{
2,193✔
1226
  if ((msgh->msg_flags & MSG_TRUNC) != 0) {
2,193!
1227
    /* message was too large for our buffer */
1228
    vinfolog("Dropping message too large for our buffer");
×
1229
    ++clientState.nonCompliantQueries;
×
1230
    ++dnsdist::metrics::g_stats.nonCompliantQueries;
×
1231
    return false;
×
1232
  }
×
1233

1234
  expectProxyProtocol = clientState.d_enableProxyProtocol && expectProxyProtocolFrom(remote);
2,193✔
1235
  if (!dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL.match(remote) && !expectProxyProtocol) {
2,193!
1236
    vinfolog("Query from %s dropped because of ACL", remote.toStringWithPort());
1!
1237
    ++dnsdist::metrics::g_stats.aclDrops;
1✔
1238
    return false;
1✔
1239
  }
1✔
1240

1241
  if (HarvestDestinationAddress(msgh, &dest)) {
2,192✔
1242
    /* so it turns out that sometimes the kernel lies to us:
1243
       the address is set to 0.0.0.0:0 which makes our sendfromto() use
1244
       the wrong address. In that case it's better to let the kernel
1245
       do the work by itself and use sendto() instead.
1246
       This is indicated by setting the family to 0 which is acted upon
1247
       in sendUDPResponse() and DelayedPacket::().
1248
    */
1249
    const ComboAddress bogusV4("0.0.0.0:0");
5✔
1250
    const ComboAddress bogusV6("[::]:0");
5✔
1251
    if ((dest.sin4.sin_family == AF_INET && dest == bogusV4) || (dest.sin4.sin_family == AF_INET6 && dest == bogusV6)) {
5!
1252
      dest.sin4.sin_family = 0;
×
1253
    }
×
1254
    else {
5✔
1255
      /* we don't get the port, only the address */
1256
      dest.sin4.sin_port = clientState.local.sin4.sin_port;
5✔
1257
    }
5✔
1258
  }
5✔
1259
  else {
2,187✔
1260
    dest.sin4.sin_family = 0;
2,187✔
1261
  }
2,187✔
1262

1263
  ++clientState.queries;
2,192✔
1264
  ++dnsdist::metrics::g_stats.queries;
2,192✔
1265

1266
  return true;
2,192✔
1267
}
2,193✔
1268

1269
bool checkDNSCryptQuery(const ClientState& clientState, [[maybe_unused]] PacketBuffer& query, [[maybe_unused]] std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, [[maybe_unused]] time_t now, [[maybe_unused]] bool tcp)
1270
{
4,876✔
1271
  if (clientState.dnscryptCtx) {
4,876✔
1272
#ifdef HAVE_DNSCRYPT
55✔
1273
    PacketBuffer response;
55✔
1274
    dnsCryptQuery = std::make_unique<DNSCryptQuery>(clientState.dnscryptCtx);
55✔
1275

1276
    bool decrypted = handleDNSCryptQuery(query, *dnsCryptQuery, tcp, now, response);
55✔
1277

1278
    if (!decrypted) {
55✔
1279
      if (!response.empty()) {
21✔
1280
        query = std::move(response);
19✔
1281
        return true;
19✔
1282
      }
19✔
1283
      throw std::runtime_error("Unable to decrypt DNSCrypt query, dropping.");
2✔
1284
    }
21✔
1285
#endif /* HAVE_DNSCRYPT */
55✔
1286
  }
55✔
1287
  return false;
4,855✔
1288
}
4,876✔
1289

1290
bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState)
1291
{
5,046✔
1292
  if (dnsHeader.qr) { // don't respond to responses
5,046✔
1293
    ++dnsdist::metrics::g_stats.nonCompliantQueries;
5✔
1294
    ++clientState.nonCompliantQueries;
5✔
1295
    return false;
5✔
1296
  }
5✔
1297

1298
  if (dnsHeader.qdcount == 0) {
5,041✔
1299
    ++dnsdist::metrics::g_stats.emptyQueries;
6✔
1300
    if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_dropEmptyQueries) {
6✔
1301
      return false;
2✔
1302
    }
2✔
1303
  }
6✔
1304

1305
  if (dnsHeader.rd) {
5,039✔
1306
    ++dnsdist::metrics::g_stats.rdQueries;
4,555✔
1307
  }
4,555✔
1308

1309
  return true;
5,039✔
1310
}
5,041✔
1311

1312
#if !defined(DISABLE_RECVMMSG) && defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
1313
static void queueResponse(const PacketBuffer& response, const ComboAddress& dest, const ComboAddress& remote, struct mmsghdr& outMsg, struct iovec* iov, cmsgbuf_aligned* cbuf)
1314
{
5✔
1315
  outMsg.msg_len = 0;
5✔
1316
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): API
1317
  fillMSGHdr(&outMsg.msg_hdr, iov, nullptr, 0, const_cast<char*>(reinterpret_cast<const char*>(&response.at(0))), response.size(), const_cast<ComboAddress*>(&remote));
5✔
1318

1319
  if (dest.sin4.sin_family == 0) {
5!
1320
    outMsg.msg_hdr.msg_control = nullptr;
5✔
1321
  }
5✔
1322
  else {
×
1323
    addCMsgSrcAddr(&outMsg.msg_hdr, cbuf, &dest, 0);
×
1324
  }
×
1325
}
5✔
1326
#elif !defined(HAVE_RECVMMSG)
1327
struct mmsghdr
1328
{
1329
  msghdr msg_hdr;
1330
  unsigned int msg_len{0};
1331
};
1332
#endif
1333

1334
/* self-generated responses or cache hits */
1335
static bool prepareOutgoingResponse([[maybe_unused]] const ClientState& clientState, DNSQuestion& dnsQuestion, bool cacheHit)
1336
{
975✔
1337
  std::shared_ptr<DownstreamState> backend{nullptr};
975✔
1338
  DNSResponse dnsResponse(dnsQuestion.ids, dnsQuestion.getMutableData(), backend);
975✔
1339
  dnsResponse.d_incomingTCPState = dnsQuestion.d_incomingTCPState;
975✔
1340
  dnsResponse.ids.selfGenerated = true;
975✔
1341

1342
  const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
975✔
1343
  const auto& cacheHitRespRules = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::CacheHitResponseRules);
975✔
1344
  const auto& selfAnsweredRespRules = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::SelfAnsweredResponseRules);
975✔
1345
  if (!applyRulesToResponse(cacheHit ? cacheHitRespRules : selfAnsweredRespRules, dnsResponse)) {
975✔
1346
    return false;
4✔
1347
  }
4✔
1348

1349
  if (dnsResponse.ids.ttlCap > 0) {
971!
1350
    dnsdist::PacketMangling::restrictDNSPacketTTLs(dnsResponse.getMutableData(), 0, dnsResponse.ids.ttlCap);
×
1351
  }
×
1352

1353
  if (dnsResponse.ids.d_extendedError) {
971✔
1354
    dnsdist::edns::addExtendedDNSError(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.d_extendedError->infoCode, dnsResponse.ids.d_extendedError->extraText);
8✔
1355
  }
8✔
1356

1357
  if (cacheHit) {
971✔
1358
    ++dnsdist::metrics::g_stats.cacheHits;
533✔
1359
  }
533✔
1360

1361
  if (dnsResponse.isAsynchronous()) {
971✔
1362
    return false;
12✔
1363
  }
12✔
1364

1365
#ifdef HAVE_DNSCRYPT
959✔
1366
  if (!clientState.muted) {
959!
1367
    if (!encryptResponse(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnsQuestion.overTCP(), dnsQuestion.ids.dnsCryptQuery)) {
959!
1368
      return false;
×
1369
    }
×
1370
  }
959✔
1371
#endif /* HAVE_DNSCRYPT */
959✔
1372

1373
  return true;
959✔
1374
}
959✔
1375

1376
static ProcessQueryResult handleQueryTurnedIntoSelfAnsweredResponse(DNSQuestion& dnsQuestion)
1377
{
429✔
1378
  fixUpQueryTurnedResponse(dnsQuestion, dnsQuestion.ids.origFlags);
429✔
1379

1380
  if (!prepareOutgoingResponse(*dnsQuestion.ids.cs, dnsQuestion, false)) {
429✔
1381
    return ProcessQueryResult::Drop;
2✔
1382
  }
2✔
1383

1384
  const auto rcode = dnsQuestion.getHeader()->rcode;
427✔
1385
  if (rcode == RCode::NXDomain) {
427✔
1386
    ++dnsdist::metrics::g_stats.ruleNXDomain;
26✔
1387
  }
26✔
1388
  else if (rcode == RCode::Refused) {
401✔
1389
    ++dnsdist::metrics::g_stats.ruleRefused;
102✔
1390
  }
102✔
1391
  else if (rcode == RCode::ServFail) {
299✔
1392
    ++dnsdist::metrics::g_stats.ruleServFail;
6✔
1393
  }
6✔
1394

1395
  ++dnsdist::metrics::g_stats.selfAnswered;
427✔
1396
  ++dnsQuestion.ids.cs->responses;
427✔
1397
  return ProcessQueryResult::SendAnswer;
427✔
1398
}
429✔
1399

1400
static void selectBackendForOutgoingQuery(DNSQuestion& dnsQuestion, const std::shared_ptr<ServerPool>& serverPool, std::shared_ptr<DownstreamState>& selectedBackend)
1401
{
4,544✔
1402
  std::shared_ptr<ServerPolicy> poolPolicy = serverPool->policy;
4,544✔
1403
  const auto& policy = poolPolicy != nullptr ? *poolPolicy : *dnsdist::configuration::getCurrentRuntimeConfiguration().d_lbPolicy;
4,544✔
1404
  const auto servers = serverPool->getServers();
4,544✔
1405
  selectedBackend = policy.getSelectedBackend(*servers, dnsQuestion);
4,544✔
1406
}
4,544✔
1407

1408
ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend)
1409
{
4,976✔
1410
  const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
4,976✔
1411

1412
  try {
4,976✔
1413
    if (dnsQuestion.getHeader()->qr) { // something turned it into a response
4,976✔
1414
      return handleQueryTurnedIntoSelfAnsweredResponse(dnsQuestion);
427✔
1415
    }
427✔
1416
    std::shared_ptr<ServerPool> serverPool = getPool(dnsQuestion.ids.poolName);
4,549✔
1417
    dnsQuestion.ids.packetCache = serverPool->packetCache;
4,549✔
1418
    selectBackendForOutgoingQuery(dnsQuestion, serverPool, selectedBackend);
4,549✔
1419
    bool willBeForwardedOverUDP = !dnsQuestion.overTCP() || dnsQuestion.ids.protocol == dnsdist::Protocol::DoH;
4,549✔
1420
    if (selectedBackend && selectedBackend->isTCPOnly()) {
4,549✔
1421
      willBeForwardedOverUDP = false;
344✔
1422
    }
344✔
1423

1424
    uint32_t allowExpired = selectedBackend ? 0 : dnsdist::configuration::getCurrentRuntimeConfiguration().d_staleCacheEntriesTTL;
4,549✔
1425

1426
    if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache) {
4,549✔
1427
      dnsQuestion.ids.dnssecOK = (dnsdist::getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO) != 0;
779✔
1428
    }
779✔
1429

1430
    if (dnsQuestion.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool->getECS()))) {
4,549✔
1431
      // we special case our cache in case a downstream explicitly gave us a universally valid response with a 0 scope
1432
      // we need ECS parsing (parseECS) to be true so we can be sure that the initial incoming query did not have an existing
1433
      // ECS option, which would make it unsuitable for the zero-scope feature.
1434
      if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dnsQuestion.ids.packetCache->isECSParsingEnabled()) {
127!
1435
        if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyNoECS, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, willBeForwardedOverUDP, allowExpired, false, true, false)) {
24✔
1436

1437
          vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
7!
1438

1439
          if (!prepareOutgoingResponse(*dnsQuestion.ids.cs, dnsQuestion, true)) {
7!
1440
            return ProcessQueryResult::Drop;
×
1441
          }
×
1442

1443
          ++dnsdist::metrics::g_stats.responses;
7✔
1444
          ++dnsQuestion.ids.cs->responses;
7✔
1445
          return ProcessQueryResult::SendAnswer;
7✔
1446
        }
7✔
1447

1448
        if (!dnsQuestion.ids.subnet) {
17✔
1449
          /* there was no existing ECS on the query, enable the zero-scope feature */
1450
          dnsQuestion.ids.useZeroScope = true;
15✔
1451
        }
15✔
1452
      }
17✔
1453

1454
      if (!handleEDNSClientSubnet(dnsQuestion, dnsQuestion.ids.ednsAdded, dnsQuestion.ids.ecsAdded)) {
120!
1455
        vinfolog("Dropping query from %s because we couldn't insert the ECS value", dnsQuestion.ids.origRemote.toStringWithPort());
×
1456
        return ProcessQueryResult::Drop;
×
1457
      }
×
1458
    }
120✔
1459

1460
    if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache) {
4,542✔
1461
      /* First lookup, which takes into account how the protocol over which the query will be forwarded.
1462
         For DoH, this lookup is done with the protocol set to TCP but we will retry over UDP below,
1463
         therefore we do not record a miss for queries received over DoH and forwarded over TCP
1464
         yet, as we will do a second-lookup */
1465
      if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, dnsQuestion.ids.protocol == dnsdist::Protocol::DoH ? &dnsQuestion.ids.cacheKeyTCP : &dnsQuestion.ids.cacheKey, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, dnsQuestion.ids.protocol != dnsdist::Protocol::DoH && willBeForwardedOverUDP, allowExpired, false, true, dnsQuestion.ids.protocol != dnsdist::Protocol::DoH || !willBeForwardedOverUDP)) {
772!
1466

1467
        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [flags = dnsQuestion.ids.origFlags](dnsheader& header) {
494✔
1468
          restoreFlags(&header, flags);
494✔
1469
          return true;
494✔
1470
        });
494✔
1471

1472
        vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
494✔
1473

1474
        if (!prepareOutgoingResponse(*dnsQuestion.ids.cs, dnsQuestion, true)) {
494✔
1475
          return ProcessQueryResult::Drop;
10✔
1476
        }
10✔
1477

1478
        ++dnsdist::metrics::g_stats.responses;
484✔
1479
        ++dnsQuestion.ids.cs->responses;
484✔
1480
        return ProcessQueryResult::SendAnswer;
484✔
1481
      }
494✔
1482
      if (dnsQuestion.ids.protocol == dnsdist::Protocol::DoH && willBeForwardedOverUDP) {
278!
1483
        /* do a second-lookup for responses received over UDP, but we do not want TC=1 answers */
1484
        /* we need to be careful to keep the existing cache-key (TCP) */
1485
        if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKey, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, true, allowExpired, false, false, true)) {
49✔
1486
          if (!prepareOutgoingResponse(*dnsQuestion.ids.cs, dnsQuestion, true)) {
34✔
1487
            return ProcessQueryResult::Drop;
4✔
1488
          }
4✔
1489

1490
          ++dnsdist::metrics::g_stats.responses;
30✔
1491
          ++dnsQuestion.ids.cs->responses;
30✔
1492
          return ProcessQueryResult::SendAnswer;
30✔
1493
        }
34✔
1494
      }
49✔
1495

1496
      vinfolog("Packet cache miss for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
244✔
1497

1498
      ++dnsdist::metrics::g_stats.cacheMisses;
244✔
1499

1500
      // coverity[auto_causes_copy]
1501
      const auto existingPool = dnsQuestion.ids.poolName;
244✔
1502
      const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
244✔
1503
      const auto& cacheMissRuleActions = dnsdist::rules::getRuleChain(chains, dnsdist::rules::RuleChain::CacheMissRules);
244✔
1504

1505
      if (!applyRulesChainToQuery(cacheMissRuleActions, dnsQuestion)) {
244!
1506
        return ProcessQueryResult::Drop;
×
1507
      }
×
1508
      if (dnsQuestion.getHeader()->qr) { // something turned it into a response
244✔
1509
        return handleQueryTurnedIntoSelfAnsweredResponse(dnsQuestion);
2✔
1510
      }
2✔
1511
      /* let's be nice and allow the selection of a different pool,
1512
         but no second cache-lookup for you */
1513
      if (dnsQuestion.ids.poolName != existingPool) {
242✔
1514
        serverPool = getPool(dnsQuestion.ids.poolName);
1✔
1515
        dnsQuestion.ids.packetCache = serverPool->packetCache;
1✔
1516
        selectBackendForOutgoingQuery(dnsQuestion, serverPool, selectedBackend);
1✔
1517
      }
1✔
1518
    }
242✔
1519

1520
    if (!selectedBackend) {
4,012✔
1521
      auto servFailOnNoPolicy = dnsdist::configuration::getCurrentRuntimeConfiguration().d_servFailOnNoPolicy;
27✔
1522
      ++dnsdist::metrics::g_stats.noPolicy;
27✔
1523

1524
      vinfolog("%s query for %s|%s from %s, no downstream server available", servFailOnNoPolicy ? "ServFailed" : "Dropped", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort());
27!
1525
      if (servFailOnNoPolicy) {
27✔
1526
        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
11✔
1527
          header.rcode = RCode::ServFail;
11✔
1528
          header.qr = true;
11✔
1529
          return true;
11✔
1530
        });
11✔
1531

1532
        fixUpQueryTurnedResponse(dnsQuestion, dnsQuestion.ids.origFlags);
11✔
1533

1534
        if (!prepareOutgoingResponse(*dnsQuestion.ids.cs, dnsQuestion, false)) {
11!
1535
          return ProcessQueryResult::Drop;
×
1536
        }
×
1537
        ++dnsdist::metrics::g_stats.responses;
11✔
1538
        ++dnsQuestion.ids.cs->responses;
11✔
1539
        // no response-only statistics counter to update.
1540
        return ProcessQueryResult::SendAnswer;
11✔
1541
      }
11✔
1542

1543
      return ProcessQueryResult::Drop;
16✔
1544
    }
27✔
1545

1546
    /* save the DNS flags as sent to the backend so we can cache the answer with the right flags later */
1547
    dnsQuestion.ids.cacheFlags = *getFlagsFromDNSHeader(dnsQuestion.getHeader().get());
3,985✔
1548

1549
    if (selectedBackend->d_config.useProxyProtocol && dnsQuestion.getProtocol().isEncrypted() && selectedBackend->d_config.d_proxyProtocolAdvertiseTLS) {
3,985✔
1550
      if (!dnsQuestion.proxyProtocolValues) {
24!
1551
        dnsQuestion.proxyProtocolValues = std::make_unique<std::vector<ProxyProtocolValue>>();
×
1552
      }
×
1553
      dnsQuestion.proxyProtocolValues->push_back(ProxyProtocolValue{"", static_cast<uint8_t>(ProxyProtocolValue::Types::PP_TLV_SSL)});
24✔
1554
    }
24✔
1555

1556
    selectedBackend->incQueriesCount();
3,985✔
1557
    return ProcessQueryResult::PassToBackend;
3,985✔
1558
  }
4,012✔
1559
  catch (const std::exception& e) {
4,976✔
1560
    vinfolog("Got an error while parsing a %s query (after applying rules)  from %s, id %d: %s", (dnsQuestion.overTCP() ? "TCP" : "UDP"), dnsQuestion.ids.origRemote.toStringWithPort(), queryId, e.what());
6!
1561
  }
6✔
1562
  return ProcessQueryResult::Drop;
6✔
1563
}
4,976✔
1564

1565
class UDPTCPCrossQuerySender : public TCPQuerySender
1566
{
1567
public:
1568
  UDPTCPCrossQuerySender() = default;
673✔
1569
  UDPTCPCrossQuerySender(const UDPTCPCrossQuerySender&) = delete;
1570
  UDPTCPCrossQuerySender& operator=(const UDPTCPCrossQuerySender&) = delete;
1571
  UDPTCPCrossQuerySender(UDPTCPCrossQuerySender&&) = default;
1572
  UDPTCPCrossQuerySender& operator=(UDPTCPCrossQuerySender&&) = default;
1573
  ~UDPTCPCrossQuerySender() override = default;
1574

1575
  [[nodiscard]] bool active() const override
1576
  {
140✔
1577
    return true;
140✔
1578
  }
140✔
1579

1580
  void handleResponse(const struct timeval& now, TCPResponse&& response) override
1581
  {
166✔
1582
    (void)now;
166✔
1583
    if (!response.d_ds && !response.d_idstate.selfGenerated) {
166!
1584
      throw std::runtime_error("Passing a cross-protocol answer originated from UDP without a valid downstream");
×
1585
    }
×
1586

1587
    auto& ids = response.d_idstate;
166✔
1588

1589
    handleResponseForUDPClient(ids, response.d_buffer, response.d_ds, response.isAsync(), response.d_idstate.selfGenerated);
166✔
1590
  }
166✔
1591

1592
  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
1593
  {
×
1594
    return handleResponse(now, std::move(response));
×
1595
  }
×
1596

1597
  void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
1598
  {
12✔
1599
    // nothing to do
1600
  }
12✔
1601
};
1602

1603
class UDPCrossProtocolQuery : public CrossProtocolQuery
1604
{
1605
public:
1606
  UDPCrossProtocolQuery(PacketBuffer&& buffer_, InternalQueryState&& ids_, std::shared_ptr<DownstreamState> backend) :
1607
    CrossProtocolQuery(InternalQuery(std::move(buffer_), std::move(ids_)), backend)
1608
  {
195✔
1609
    auto& ids = query.d_idstate;
195✔
1610
    const auto& buffer = query.d_buffer;
195✔
1611

1612
    if (ids.udpPayloadSize == 0) {
195✔
1613
      uint16_t zValue = 0;
167✔
1614
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
1615
      getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(buffer.data()), buffer.size(), &ids.udpPayloadSize, &zValue);
167✔
1616
      if (ids.udpPayloadSize < 512) {
167!
1617
        ids.udpPayloadSize = 512;
167✔
1618
      }
167✔
1619
    }
167✔
1620
  }
195✔
1621
  UDPCrossProtocolQuery(const UDPCrossProtocolQuery&) = delete;
1622
  UDPCrossProtocolQuery& operator=(const UDPCrossProtocolQuery&) = delete;
1623
  UDPCrossProtocolQuery(UDPCrossProtocolQuery&&) = delete;
1624
  UDPCrossProtocolQuery& operator=(UDPCrossProtocolQuery&&) = delete;
1625
  ~UDPCrossProtocolQuery() override = default;
1626

1627
  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
1628
  {
178✔
1629
    return s_sender;
178✔
1630
  }
178✔
1631

1632
private:
1633
  static std::shared_ptr<UDPTCPCrossQuerySender> s_sender;
1634
};
1635

1636
std::shared_ptr<UDPTCPCrossQuerySender> UDPCrossProtocolQuery::s_sender = std::make_shared<UDPTCPCrossQuerySender>();
1637

1638
std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion);
1639
std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion)
1640
{
63✔
1641
  dnsQuestion.ids.origID = dnsQuestion.getHeader()->id;
63✔
1642
  return std::make_unique<UDPCrossProtocolQuery>(std::move(dnsQuestion.getMutableData()), std::move(dnsQuestion.ids), nullptr);
63✔
1643
}
63✔
1644

1645
ProcessQueryResult processQuery(DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend)
1646
{
5,031✔
1647
  const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
5,031✔
1648

1649
  try {
5,031✔
1650
    /* we need an accurate ("real") value for the response and
1651
       to store into the IDS, but not for insertion into the
1652
       rings for example */
1653
    timespec now{};
5,031✔
1654
    gettime(&now);
5,031✔
1655

1656
    if ((dnsQuestion.ids.qtype == QType::AXFR || dnsQuestion.ids.qtype == QType::IXFR) && (dnsQuestion.getProtocol() == dnsdist::Protocol::DoH || dnsQuestion.getProtocol() == dnsdist::Protocol::DoQ || dnsQuestion.getProtocol() == dnsdist::Protocol::DoH3)) {
5,031✔
1657
      dnsQuestion.editHeader([](dnsheader& header) {
8✔
1658
        header.rcode = RCode::NotImp;
8✔
1659
        header.qr = true;
8✔
1660
        return true;
8✔
1661
      });
8✔
1662
      return processQueryAfterRules(dnsQuestion, selectedBackend);
8✔
1663
    }
8✔
1664

1665
    if (!applyRulesToQuery(dnsQuestion, now)) {
5,023✔
1666
      return ProcessQueryResult::Drop;
45✔
1667
    }
45✔
1668

1669
    if (dnsQuestion.isAsynchronous()) {
4,978✔
1670
      return ProcessQueryResult::Asynchronous;
202✔
1671
    }
202✔
1672

1673
    return processQueryAfterRules(dnsQuestion, selectedBackend);
4,776✔
1674
  }
4,978✔
1675
  catch (const std::exception& e) {
5,031✔
1676
    vinfolog("Got an error while parsing a %s query from %s, id %d: %s", (dnsQuestion.overTCP() ? "TCP" : "UDP"), dnsQuestion.ids.origRemote.toStringWithPort(), queryId, e.what());
×
1677
  }
×
1678
  return ProcessQueryResult::Drop;
×
1679
}
5,031✔
1680

1681
bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend)
1682
{
1,671✔
1683
  bool doh = dnsQuestion.ids.du != nullptr;
1,671✔
1684

1685
  bool failed = false;
1,671✔
1686
  if (downstream->d_config.useProxyProtocol) {
1,671✔
1687
    try {
23✔
1688
      addProxyProtocol(dnsQuestion, &dnsQuestion.ids.d_proxyProtocolPayloadSize);
23✔
1689
    }
23✔
1690
    catch (const std::exception& e) {
23✔
1691
      vinfolog("Adding proxy protocol payload to %s query from %s failed: %s", (dnsQuestion.ids.du ? "DoH" : ""), dnsQuestion.ids.origDest.toStringWithPort(), e.what());
2!
1692
      return false;
2✔
1693
    }
2✔
1694
  }
23✔
1695

1696
  if (doh && !dnsQuestion.ids.d_packet) {
1,669!
1697
    dnsQuestion.ids.d_packet = std::make_unique<PacketBuffer>(query);
118✔
1698
  }
118✔
1699

1700
  try {
1,669✔
1701
    int descriptor = downstream->pickSocketForSending();
1,669✔
1702
    if (actuallySend) {
1,669!
1703
      dnsQuestion.ids.backendFD = descriptor;
1,669✔
1704
    }
1,669✔
1705
    dnsQuestion.ids.origID = queryID;
1,669✔
1706
    dnsQuestion.ids.forwardedOverUDP = true;
1,669✔
1707

1708
    vinfolog("Got query for %s|%s from %s%s, relayed to %s%s", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), (doh ? " (https)" : ""), downstream->getNameWithAddr(), actuallySend ? "" : " (xsk)");
1,669!
1709

1710
    /* make a copy since we cannot touch dnsQuestion.ids after the move */
1711
    auto proxyProtocolPayloadSize = dnsQuestion.ids.d_proxyProtocolPayloadSize;
1,669✔
1712
    auto idOffset = downstream->saveState(std::move(dnsQuestion.ids));
1,669✔
1713
    /* set the correct ID */
1714
    memcpy(&query.at(proxyProtocolPayloadSize), &idOffset, sizeof(idOffset));
1,669✔
1715

1716
    if (!actuallySend) {
1,669!
1717
      return true;
×
1718
    }
×
1719

1720
    /* you can't touch ids or du after this line, unless the call returned a non-negative value,
1721
       because it might already have been freed */
1722
    ssize_t ret = udpClientSendRequestToBackend(downstream, descriptor, query);
1,669✔
1723

1724
    if (ret < 0) {
1,669!
1725
      failed = true;
×
1726
    }
×
1727

1728
    if (failed) {
1,669!
1729
      /* clear up the state. In the very unlikely event it was reused
1730
         in the meantime, so be it. */
1731
      auto cleared = downstream->getState(idOffset);
×
1732
      if (cleared) {
×
1733
        dnsQuestion.ids.du = std::move(cleared->du);
×
1734
      }
×
1735
      ++dnsdist::metrics::g_stats.downstreamSendErrors;
×
1736
      ++downstream->sendErrors;
×
1737
      return false;
×
1738
    }
×
1739
  }
1,669✔
1740
  catch (const std::exception& e) {
1,669✔
1741
    throw;
×
1742
  }
×
1743

1744
  return true;
1,669✔
1745
}
1,669✔
1746

1747
static void processUDPQuery(ClientState& clientState, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, PacketBuffer& query, std::vector<mmsghdr>* responsesVect, unsigned int* queuedResponses, struct iovec* respIOV, cmsgbuf_aligned* respCBuf)
1748
{
2,193✔
1749
  assert(responsesVect == nullptr || (queuedResponses != nullptr && respIOV != nullptr && respCBuf != nullptr));
2,193✔
1750
  uint16_t queryId = 0;
×
1751
  InternalQueryState ids;
2,193✔
1752
  ids.cs = &clientState;
2,193✔
1753
  ids.origRemote = remote;
2,193✔
1754
  ids.hopRemote = remote;
2,193✔
1755
  ids.protocol = dnsdist::Protocol::DoUDP;
2,193✔
1756

1757
  try {
2,193✔
1758
    bool expectProxyProtocol = false;
2,193✔
1759
    if (!isUDPQueryAcceptable(clientState, msgh, remote, dest, expectProxyProtocol)) {
2,193✔
1760
      return;
1✔
1761
    }
1✔
1762
    /* dest might have been updated, if we managed to harvest the destination address */
1763
    if (dest.sin4.sin_family != 0) {
2,192✔
1764
      ids.origDest = dest;
5✔
1765
      ids.hopLocal = dest;
5✔
1766
    }
5✔
1767
    else {
2,187✔
1768
      /* if we have not been able to harvest the destination address,
1769
         we do NOT want to update dest or hopLocal, to let the kernel
1770
         pick the less terrible option, but we want to update origDest
1771
         which is used by rules and actions to at least the correct
1772
         address family */
1773
      ids.origDest = clientState.local;
2,187✔
1774
      ids.hopLocal.sin4.sin_family = 0;
2,187✔
1775
    }
2,187✔
1776

1777
    std::vector<ProxyProtocolValue> proxyProtocolValues;
2,192✔
1778
    if (expectProxyProtocol && !handleProxyProtocol(remote, false, dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
2,192✔
1779
      return;
1✔
1780
    }
1✔
1781

1782
    ids.queryRealTime.start();
2,191✔
1783

1784
    auto dnsCryptResponse = checkDNSCryptQuery(clientState, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
2,191✔
1785
    if (dnsCryptResponse) {
2,191✔
1786
      sendUDPResponse(clientState.udpFD, query, 0, dest, remote);
19✔
1787
      return;
19✔
1788
    }
19✔
1789

1790
    {
2,172✔
1791
      /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
1792
      const dnsheader_aligned dnsHeader(query.data());
2,172✔
1793
      queryId = ntohs(dnsHeader->id);
2,172✔
1794

1795
      if (!checkQueryHeaders(*dnsHeader, clientState)) {
2,172✔
1796
        return;
1✔
1797
      }
1✔
1798

1799
      if (dnsHeader->qdcount == 0) {
2,171✔
1800
        dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
1✔
1801
          header.rcode = RCode::NotImp;
1✔
1802
          header.qr = true;
1✔
1803
          return true;
1✔
1804
        });
1✔
1805

1806
        sendUDPResponse(clientState.udpFD, query, 0, dest, remote);
1✔
1807
        return;
1✔
1808
      }
1✔
1809
    }
2,171✔
1810

1811
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
1812
    ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
2,170✔
1813
    if (ids.dnsCryptQuery) {
2,170✔
1814
      ids.protocol = dnsdist::Protocol::DNSCryptUDP;
19✔
1815
    }
19✔
1816
    DNSQuestion dnsQuestion(ids, query);
2,170✔
1817
    const uint16_t* flags = getFlagsFromDNSHeader(dnsQuestion.getHeader().get());
2,170✔
1818
    ids.origFlags = *flags;
2,170✔
1819

1820
    if (!proxyProtocolValues.empty()) {
2,170✔
1821
      dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
6✔
1822
    }
6✔
1823

1824
    std::shared_ptr<DownstreamState> backend{nullptr};
2,170✔
1825
    auto result = processQuery(dnsQuestion, backend);
2,170✔
1826

1827
    if (result == ProcessQueryResult::Drop || result == ProcessQueryResult::Asynchronous) {
2,170✔
1828
      return;
65✔
1829
    }
65✔
1830

1831
    // the buffer might have been invalidated by now (resized)
1832
    const auto dnsHeader = dnsQuestion.getHeader();
2,105✔
1833
    if (result == ProcessQueryResult::SendAnswer) {
2,105✔
1834
#ifndef DISABLE_RECVMMSG
433✔
1835
#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
433✔
1836
      if (dnsQuestion.ids.delayMsec == 0 && responsesVect != nullptr) {
433!
1837
        queueResponse(query, dest, remote, (*responsesVect)[*queuedResponses], respIOV, respCBuf);
5✔
1838
        (*queuedResponses)++;
5✔
1839
        handleResponseSent(dnsQuestion.ids.qname, dnsQuestion.ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
5✔
1840
        return;
5✔
1841
      }
5✔
1842
#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
428✔
1843
#endif /* DISABLE_RECVMMSG */
428✔
1844
      /* we use dest, always, because we don't want to use the listening address to send a response since it could be 0.0.0.0 */
1845
      sendUDPResponse(clientState.udpFD, query, dnsQuestion.ids.delayMsec, dest, remote);
428✔
1846

1847
      handleResponseSent(dnsQuestion.ids.qname, dnsQuestion.ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
428✔
1848
      return;
428✔
1849
    }
433✔
1850

1851
    if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
1,672!
1852
      return;
×
1853
    }
×
1854

1855
    if (backend->isTCPOnly()) {
1,672✔
1856
      std::string proxyProtocolPayload;
132✔
1857
      /* we need to do this _before_ creating the cross protocol query because
1858
         after that the buffer will have been moved */
1859
      if (backend->d_config.useProxyProtocol) {
132✔
1860
        proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
1✔
1861
      }
1✔
1862

1863
      ids.origID = dnsHeader->id;
132✔
1864
      auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), backend);
132✔
1865
      cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
132✔
1866

1867
      backend->passCrossProtocolQuery(std::move(cpq));
132✔
1868
      return;
132✔
1869
    }
132✔
1870

1871
    assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query);
1,540✔
1872
  }
1,540✔
1873
  catch (const std::exception& e) {
2,193✔
1874
    vinfolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", ids.origRemote.toStringWithPort(), queryId, e.what());
4!
1875
  }
4✔
1876
}
2,193✔
1877

1878
#ifdef HAVE_XSK
1879
namespace dnsdist::xsk
1880
{
1881
bool XskProcessQuery(ClientState& clientState, XskPacket& packet)
1882
{
×
1883
  uint16_t queryId = 0;
×
1884
  const auto& remote = packet.getFromAddr();
×
1885
  const auto& dest = packet.getToAddr();
×
1886
  InternalQueryState ids;
×
1887
  ids.cs = &clientState;
×
1888
  ids.origRemote = remote;
×
1889
  ids.hopRemote = remote;
×
1890
  ids.origDest = dest;
×
1891
  ids.hopLocal = dest;
×
1892
  ids.protocol = dnsdist::Protocol::DoUDP;
×
1893
  ids.xskPacketHeader = packet.cloneHeaderToPacketBuffer();
×
1894

1895
  try {
×
1896
    bool expectProxyProtocol = false;
×
1897
    if (!XskIsQueryAcceptable(packet, clientState, expectProxyProtocol)) {
×
1898
      return false;
×
1899
    }
×
1900

1901
    auto query = packet.clonePacketBuffer();
×
1902
    std::vector<ProxyProtocolValue> proxyProtocolValues;
×
1903
    if (expectProxyProtocol && !handleProxyProtocol(remote, false, dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
×
1904
      return false;
×
1905
    }
×
1906

1907
    ids.queryRealTime.start();
×
1908

1909
    auto dnsCryptResponse = checkDNSCryptQuery(clientState, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
×
1910
    if (dnsCryptResponse) {
×
1911
      packet.setPayload(query);
×
1912
      return true;
×
1913
    }
×
1914

1915
    {
×
1916
      /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
1917
      dnsheader_aligned dnsHeader(query.data());
×
1918
      queryId = ntohs(dnsHeader->id);
×
1919

1920
      if (!checkQueryHeaders(*dnsHeader, clientState)) {
×
1921
        return false;
×
1922
      }
×
1923

1924
      if (dnsHeader->qdcount == 0) {
×
1925
        dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
×
1926
          header.rcode = RCode::NotImp;
×
1927
          header.qr = true;
×
1928
          return true;
×
1929
        });
×
1930
        packet.setPayload(query);
×
1931
        return true;
×
1932
      }
×
1933
    }
×
1934

1935
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
1936
    ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
×
1937
    if (ids.origDest.sin4.sin_family == 0) {
×
1938
      ids.origDest = clientState.local;
×
1939
    }
×
1940
    if (ids.dnsCryptQuery) {
×
1941
      ids.protocol = dnsdist::Protocol::DNSCryptUDP;
×
1942
    }
×
1943
    DNSQuestion dnsQuestion(ids, query);
×
1944
    if (!proxyProtocolValues.empty()) {
×
1945
      dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
×
1946
    }
×
1947
    std::shared_ptr<DownstreamState> backend{nullptr};
×
1948
    auto result = processQuery(dnsQuestion, backend);
×
1949

1950
    if (result == ProcessQueryResult::Drop) {
×
1951
      return false;
×
1952
    }
×
1953

1954
    if (result == ProcessQueryResult::SendAnswer) {
×
1955
      packet.setPayload(query);
×
1956
      if (dnsQuestion.ids.delayMsec > 0) {
×
1957
        packet.addDelay(dnsQuestion.ids.delayMsec);
×
1958
      }
×
1959
      const auto dnsHeader = dnsQuestion.getHeader();
×
1960
      handleResponseSent(ids.qname, ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
×
1961
      return true;
×
1962
    }
×
1963

1964
    if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
×
1965
      return false;
×
1966
    }
×
1967

1968
    // the buffer might have been invalidated by now (resized)
1969
    const auto dnsHeader = dnsQuestion.getHeader();
×
1970
    if (backend->isTCPOnly()) {
×
1971
      std::string proxyProtocolPayload;
×
1972
      /* we need to do this _before_ creating the cross protocol query because
1973
         after that the buffer will have been moved */
1974
      if (backend->d_config.useProxyProtocol) {
×
1975
        proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
×
1976
      }
×
1977

1978
      ids.origID = dnsHeader->id;
×
1979
      auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), backend);
×
1980
      cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
×
1981

1982
      backend->passCrossProtocolQuery(std::move(cpq));
×
1983
      return false;
×
1984
    }
×
1985

1986
    if (backend->d_xskInfos.empty()) {
×
1987
      assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query, true);
×
1988
      return false;
×
1989
    }
×
1990

1991
    assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query, false);
×
1992
    auto sourceAddr = backend->pickSourceAddressForSending();
×
1993
    packet.setAddr(sourceAddr, backend->d_config.sourceMACAddr, backend->d_config.remote, backend->d_config.destMACAddr);
×
1994
    packet.setPayload(query);
×
1995
    packet.rewrite();
×
1996
    return true;
×
1997
  }
×
1998
  catch (const std::exception& e) {
×
1999
    vinfolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
×
2000
  }
×
2001
  return false;
×
2002
}
×
2003

2004
}
2005
#endif /* HAVE_XSK */
2006

2007
#ifndef DISABLE_RECVMMSG
2008
#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
2009
static void MultipleMessagesUDPClientThread(ClientState* clientState)
2010
{
1✔
2011
  struct MMReceiver
1✔
2012
  {
1✔
2013
    PacketBuffer packet;
1✔
2014
    ComboAddress remote;
1✔
2015
    ComboAddress dest;
1✔
2016
    iovec iov{};
1✔
2017
    /* used by HarvestDestinationAddress */
2018
    cmsgbuf_aligned cbuf{};
1✔
2019
  };
1✔
2020
  const size_t vectSize = dnsdist::configuration::getImmutableConfiguration().d_udpVectorSize;
1✔
2021

2022
  if (vectSize > std::numeric_limits<uint16_t>::max()) {
1!
2023
    throw std::runtime_error("The value of setUDPMultipleMessagesVectorSize is too high, the maximum value is " + std::to_string(std::numeric_limits<uint16_t>::max()));
×
2024
  }
×
2025

2026
  auto recvData = std::vector<MMReceiver>(vectSize);
1✔
2027
  auto msgVec = std::vector<mmsghdr>(vectSize);
1✔
2028
  auto outMsgVec = std::vector<mmsghdr>(vectSize);
1✔
2029

2030
  /* the actual buffer is larger because:
2031
     - we may have to add EDNS and/or ECS
2032
     - we use it for self-generated responses (from rule or cache)
2033
     but we only accept incoming payloads up to that size
2034
  */
2035
  const size_t initialBufferSize = getInitialUDPPacketBufferSize(clientState->d_enableProxyProtocol);
1✔
2036
  const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*clientState);
1✔
2037

2038
  /* initialize the structures needed to receive our messages */
2039
  for (size_t idx = 0; idx < vectSize; idx++) {
11✔
2040
    recvData[idx].remote.sin4.sin_family = clientState->local.sin4.sin_family;
10✔
2041
    recvData[idx].packet.resize(initialBufferSize);
10✔
2042
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
2043
    fillMSGHdr(&msgVec[idx].msg_hdr, &recvData[idx].iov, &recvData[idx].cbuf, sizeof(recvData[idx].cbuf), reinterpret_cast<char*>(recvData[idx].packet.data()), maxIncomingPacketSize, &recvData[idx].remote);
10✔
2044
  }
10✔
2045

2046
  /* go now */
2047
  for (;;) {
8✔
2048

2049
    /* reset the IO vector, since it's also used to send the vector of responses
2050
       to avoid having to copy the data around */
2051
    for (size_t idx = 0; idx < vectSize; idx++) {
88✔
2052
      recvData[idx].packet.resize(initialBufferSize);
80✔
2053
      recvData[idx].iov.iov_base = &recvData[idx].packet.at(0);
80✔
2054
      recvData[idx].iov.iov_len = recvData[idx].packet.size();
80✔
2055
    }
80✔
2056

2057
    /* block until we have at least one message ready, but return
2058
       as many as possible to save the syscall costs */
2059
    int msgsGot = recvmmsg(clientState->udpFD, msgVec.data(), vectSize, MSG_WAITFORONE | MSG_TRUNC, nullptr);
8✔
2060

2061
    if (msgsGot <= 0) {
8!
2062
      vinfolog("Getting UDP messages via recvmmsg() failed with: %s", stringerror());
×
2063
      continue;
×
2064
    }
×
2065

2066
    unsigned int msgsToSend = 0;
8✔
2067

2068
    /* process the received messages */
2069
    for (int msgIdx = 0; msgIdx < msgsGot; msgIdx++) {
15✔
2070
      const struct msghdr* msgh = &msgVec[msgIdx].msg_hdr;
7✔
2071
      unsigned int got = msgVec[msgIdx].msg_len;
7✔
2072
      const ComboAddress& remote = recvData[msgIdx].remote;
7✔
2073

2074
      if (static_cast<size_t>(got) < sizeof(struct dnsheader)) {
7!
2075
        ++dnsdist::metrics::g_stats.nonCompliantQueries;
×
2076
        ++clientState->nonCompliantQueries;
×
2077
        continue;
×
2078
      }
×
2079

2080
      recvData[msgIdx].packet.resize(got);
7✔
2081
      processUDPQuery(*clientState, msgh, remote, recvData[msgIdx].dest, recvData[msgIdx].packet, &outMsgVec, &msgsToSend, &recvData[msgIdx].iov, &recvData[msgIdx].cbuf);
7✔
2082
    }
7✔
2083

2084
    /* immediate (not delayed or sent to a backend) responses (mostly from a rule, dynamic block
2085
       or the cache) can be sent in batch too */
2086

2087
    if (msgsToSend > 0 && msgsToSend <= static_cast<unsigned int>(msgsGot)) {
8!
2088
      int sent = sendmmsg(clientState->udpFD, outMsgVec.data(), msgsToSend, 0);
5✔
2089

2090
      if (sent < 0 || static_cast<unsigned int>(sent) != msgsToSend) {
5!
2091
        vinfolog("Error sending responses with sendmmsg() (%d on %u): %s", sent, msgsToSend, stringerror());
×
2092
      }
×
2093
    }
5✔
2094
  }
8✔
2095
}
1✔
2096
#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
2097
#endif /* DISABLE_RECVMMSG */
2098

2099
// listens to incoming queries, sends out to downstream servers, noting the intended return path
2100
static void udpClientThread(std::vector<ClientState*> states)
2101
{
332✔
2102
  try {
332✔
2103
    setThreadName("dnsdist/udpClie");
332✔
2104
#ifndef DISABLE_RECVMMSG
332✔
2105
#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
332✔
2106
    if (dnsdist::configuration::getImmutableConfiguration().d_udpVectorSize > 1) {
332✔
2107
      MultipleMessagesUDPClientThread(states.at(0));
1✔
2108
    }
1✔
2109
    else
331✔
2110
#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
331✔
2111
#endif /* DISABLE_RECVMMSG */
331✔
2112
    {
331✔
2113
      /* the actual buffer is larger because:
2114
         - we may have to add EDNS and/or ECS
2115
         - we use it for self-generated responses (from rule or cache)
2116
         but we only accept incoming payloads up to that size
2117
      */
2118
      struct UDPStateParam
331✔
2119
      {
331✔
2120
        ClientState* cs{nullptr};
331✔
2121
        size_t maxIncomingPacketSize{0};
331✔
2122
        int socket{-1};
331✔
2123
      };
331✔
2124
      const size_t initialBufferSize = getInitialUDPPacketBufferSize(true);
331✔
2125
      PacketBuffer packet(initialBufferSize);
331✔
2126

2127
      msghdr msgh{};
331✔
2128
      iovec iov{};
331✔
2129
      ComboAddress remote;
331✔
2130
      ComboAddress dest;
331✔
2131

2132
      auto handleOnePacket = [&packet, &iov, &msgh, &remote, &dest, initialBufferSize](const UDPStateParam& param) {
2,517✔
2133
        packet.resize(initialBufferSize);
2,517✔
2134
        iov.iov_base = &packet.at(0);
2,517✔
2135
        iov.iov_len = packet.size();
2,517✔
2136

2137
        ssize_t got = recvmsg(param.socket, &msgh, 0);
2,517✔
2138

2139
        if (got < 0 || static_cast<size_t>(got) < sizeof(struct dnsheader)) {
2,517!
2140
          ++dnsdist::metrics::g_stats.nonCompliantQueries;
×
2141
          ++param.cs->nonCompliantQueries;
×
2142
          return;
×
2143
        }
×
2144

2145
        packet.resize(static_cast<size_t>(got));
2,517✔
2146

2147
        processUDPQuery(*param.cs, &msgh, remote, dest, packet, nullptr, nullptr, nullptr, nullptr);
2,517✔
2148
      };
2,517✔
2149

2150
      std::vector<UDPStateParam> params;
331✔
2151
      for (auto& state : states) {
331✔
2152
        const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*state);
331✔
2153
        params.emplace_back(UDPStateParam{state, maxIncomingPacketSize, state->udpFD});
331✔
2154
      }
331✔
2155

2156
      if (params.size() == 1) {
331!
2157
        const auto& param = params.at(0);
331✔
2158
        remote.sin4.sin_family = param.cs->local.sin4.sin_family;
331✔
2159
        /* used by HarvestDestinationAddress */
2160
        cmsgbuf_aligned cbuf;
331✔
2161
        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
2162
        fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param.maxIncomingPacketSize, &remote);
331✔
2163
        while (true) {
2,517✔
2164
          try {
2,517✔
2165
            handleOnePacket(param);
2,517✔
2166
          }
2,517✔
2167
          catch (const std::bad_alloc& e) {
2,517✔
2168
            /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
2169
               in which case we DO NOT want to log (as it would trigger another memory allocation attempt
2170
               that might throw as well) but wait a bit (one millisecond) and then try to recover */
2171
            usleep(1000);
×
2172
          }
×
2173
        }
2,517✔
2174
      }
331✔
2175
      else {
×
2176
        auto callback = [&remote, &msgh, &iov, &packet, &handleOnePacket, initialBufferSize](int socket, FDMultiplexer::funcparam_t& funcparam) {
×
NEW
2177
          (void)socket;
×
2178
          const auto* param = boost::any_cast<const UDPStateParam*>(funcparam);
×
2179
          try {
×
2180
            remote.sin4.sin_family = param->cs->local.sin4.sin_family;
×
2181
            packet.resize(initialBufferSize);
×
2182
            /* used by HarvestDestinationAddress */
2183
            cmsgbuf_aligned cbuf;
×
2184
            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
2185
            fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param->maxIncomingPacketSize, &remote);
×
2186
            handleOnePacket(*param);
×
2187
          }
×
2188
          catch (const std::bad_alloc& e) {
×
2189
            /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
2190
               in which case we DO NOT want to log (as it would trigger another memory allocation attempt
2191
               that might throw as well) but wait a bit (one millisecond) and then try to recover */
2192
            usleep(1000);
×
2193
          }
×
2194
        };
×
2195
        auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
×
2196
        for (const auto& param : params) {
×
2197
          mplexer->addReadFD(param.socket, callback, &param);
×
2198
        }
×
2199

2200
        timeval now{};
×
2201
        while (true) {
×
2202
          mplexer->run(&now, -1);
×
2203
        }
×
2204
      }
×
2205
    }
331✔
2206
  }
332✔
2207
  catch (const std::exception& e) {
332✔
2208
    errlog("UDP client thread died because of exception: %s", e.what());
×
2209
  }
×
2210
  catch (const PDNSException& e) {
332✔
2211
    errlog("UDP client thread died because of PowerDNS exception: %s", e.reason);
×
2212
  }
×
2213
  catch (...) {
332✔
2214
    errlog("UDP client thread died because of an exception: %s", "unknown");
×
2215
  }
×
2216
}
332✔
2217

2218
static void maintThread()
2219
{
332✔
2220
  setThreadName("dnsdist/main");
332✔
2221
  constexpr int interval = 1;
332✔
2222
  size_t counter = 0;
332✔
2223
  int32_t secondsToWaitLog = 0;
332✔
2224

2225
  for (;;) {
1,019✔
2226
    std::this_thread::sleep_for(std::chrono::seconds(interval));
1,019✔
2227

2228
    {
1,019✔
2229
      auto lua = g_lua.lock();
1,019✔
2230
      try {
1,019✔
2231
        auto maintenanceCallback = lua->readVariable<boost::optional<std::function<void()>>>("maintenance");
1,019✔
2232
        if (maintenanceCallback) {
1,019✔
2233
          (*maintenanceCallback)();
317✔
2234
        }
317✔
2235
        dnsdist::lua::hooks::runMaintenanceHooks(*lua);
1,019✔
2236
#if !defined(DISABLE_DYNBLOCKS)
1,019✔
2237
        dnsdist::DynamicBlocks::runRegisteredGroups(*lua);
1,019✔
2238
#endif /* DISABLE_DYNBLOCKS */
1,019✔
2239
        secondsToWaitLog = 0;
1,019✔
2240
      }
1,019✔
2241
      catch (const std::exception& e) {
1,019✔
2242
        if (secondsToWaitLog <= 0) {
×
2243
          warnlog("Error during execution of maintenance function(s): %s", e.what());
×
2244
          secondsToWaitLog = 61;
×
2245
        }
×
2246
        secondsToWaitLog -= interval;
×
2247
      }
×
2248
    }
1,019✔
2249

2250
    counter++;
1,019✔
2251
    if (counter >= dnsdist::configuration::getCurrentRuntimeConfiguration().d_cacheCleaningDelay) {
687✔
2252
      /* keep track, for each cache, of whether we should keep
2253
       expired entries */
2254
      std::map<std::shared_ptr<DNSDistPacketCache>, bool> caches;
10✔
2255

2256
      /* gather all caches actually used by at least one pool, and see
2257
         if something prevents us from cleaning the expired entries */
2258
      const auto& pools = dnsdist::configuration::getCurrentRuntimeConfiguration().d_pools;
10✔
2259
      for (const auto& entry : pools) {
10✔
2260
        const auto& pool = entry.second;
10✔
2261

2262
        auto packetCache = pool->packetCache;
10✔
2263
        if (!packetCache) {
10!
2264
          continue;
×
2265
        }
×
2266

2267
        auto pair = caches.insert({packetCache, false});
10✔
2268
        auto& iter = pair.first;
10✔
2269
        /* if we need to keep stale data for this cache (ie, not clear
2270
           expired entries when at least one pool using this cache
2271
           has all its backends down) */
2272
        if (packetCache->keepStaleData() && !iter->second) {
10!
2273
          /* so far all pools had at least one backend up */
2274
          if (pool->countServers(true) == 0) {
4!
2275
            iter->second = true;
4✔
2276
          }
4✔
2277
        }
4✔
2278
      }
10✔
2279

2280
      const time_t now = time(nullptr);
10✔
2281
      for (const auto& pair : caches) {
10✔
2282
        /* shall we keep expired entries ? */
2283
        if (pair.second) {
10✔
2284
          continue;
4✔
2285
        }
4✔
2286
        const auto& packetCache = pair.first;
6✔
2287
        size_t upTo = (packetCache->getMaxEntries() * (100 - dnsdist::configuration::getCurrentRuntimeConfiguration().d_cacheCleaningPercentage)) / 100;
6✔
2288
        packetCache->purgeExpired(upTo, now);
6✔
2289
      }
6✔
2290
      counter = 0;
10✔
2291
    }
10✔
2292
  }
687✔
2293
}
332✔
2294

2295
#ifndef DISABLE_DYNBLOCKS
2296
static void dynBlockMaintenanceThread()
2297
{
332✔
2298
  setThreadName("dnsdist/dynBloc");
332✔
2299

2300
  DynBlockMaintenance::run();
332✔
2301
}
332✔
2302
#endif
2303

2304
#ifndef DISABLE_SECPOLL
2305
static void secPollThread()
2306
{
×
2307
  setThreadName("dnsdist/secpoll");
×
2308

2309
  for (;;) {
×
2310
    const auto& runtimeConfig = dnsdist::configuration::getCurrentRuntimeConfiguration();
×
2311

2312
    try {
×
2313
      dnsdist::secpoll::doSecPoll(runtimeConfig.d_secPollSuffix);
×
2314
    }
×
2315
    catch (...) {
×
2316
    }
×
2317
    // coverity[store_truncates_time_t]
2318
    std::this_thread::sleep_for(std::chrono::seconds(runtimeConfig.d_secPollInterval));
×
2319
  }
×
2320
}
×
2321
#endif /* DISABLE_SECPOLL */
2322

2323
static void healthChecksThread()
2324
{
332✔
2325
  setThreadName("dnsdist/healthC");
332✔
2326

2327
  constexpr int intervalUsec = 1000 * 1000;
332✔
2328
  struct timeval lastRound
332✔
2329
  {
332✔
2330
    .tv_sec = 0,
332✔
2331
    .tv_usec = 0
332✔
2332
  };
332✔
2333

2334
  for (;;) {
1,350✔
2335
    timeval now{};
1,350✔
2336
    gettimeofday(&now, nullptr);
1,350✔
2337
    auto elapsedTimeUsec = uSec(now - lastRound);
1,350✔
2338
    if (elapsedTimeUsec < intervalUsec) {
1,350✔
2339
      usleep(intervalUsec - elapsedTimeUsec);
1,018✔
2340
      gettimeofday(&lastRound, nullptr);
1,018✔
2341
    }
1,018✔
2342
    else {
332✔
2343
      lastRound = now;
332✔
2344
    }
332✔
2345

2346
    std::unique_ptr<FDMultiplexer> mplexer{nullptr};
1,350✔
2347
    // this points to the actual shared_ptrs!
2348
    // coverity[auto_causes_copy]
2349
    const auto servers = dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends;
1,350✔
2350
    for (const auto& dss : servers) {
1,350✔
2351
      dss->updateStatisticsInfo();
1,310✔
2352

2353
      dss->handleUDPTimeouts();
1,310✔
2354

2355
      if (!dss->healthCheckRequired()) {
1,310✔
2356
        continue;
346✔
2357
      }
346✔
2358

2359
      if (!mplexer) {
964✔
2360
        mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(servers.size()));
909✔
2361
      }
909✔
2362

2363
      if (!queueHealthCheck(mplexer, dss)) {
964✔
2364
        dss->submitHealthCheckResult(false, false);
8✔
2365
      }
8✔
2366
    }
964✔
2367

2368
    if (mplexer) {
1,350✔
2369
      handleQueuedHealthChecks(*mplexer);
909✔
2370
    }
909✔
2371
  }
1,350✔
2372
}
332✔
2373

2374
static void bindAny([[maybe_unused]] int addressFamily, int sock)
2375
{
767✔
2376
  __attribute__((unused)) int one = 1;
767✔
2377

2378
#ifdef IP_FREEBIND
767✔
2379
  if (setsockopt(sock, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) {
767!
2380
    warnlog("Warning: IP_FREEBIND setsockopt failed: %s", stringerror());
×
2381
  }
×
2382
#endif
767✔
2383

2384
#ifdef IP_BINDANY
2385
  if (addressFamily == AF_INET) {
2386
    if (setsockopt(sock, IPPROTO_IP, IP_BINDANY, &one, sizeof(one)) < 0) {
2387
      warnlog("Warning: IP_BINDANY setsockopt failed: %s", stringerror());
2388
    }
2389
  }
2390
#endif
2391
#ifdef IPV6_BINDANY
2392
  if (addressFamily == AF_INET6) {
2393
    if (setsockopt(sock, IPPROTO_IPV6, IPV6_BINDANY, &one, sizeof(one)) < 0) {
2394
      warnlog("Warning: IPV6_BINDANY setsockopt failed: %s", stringerror());
2395
    }
2396
  }
2397
#endif
2398
#ifdef SO_BINDANY
2399
  if (setsockopt(sock, SOL_SOCKET, SO_BINDANY, &one, sizeof(one)) < 0) {
2400
    warnlog("Warning: SO_BINDANY setsockopt failed: %s", stringerror());
2401
  }
2402
#endif
2403
}
767✔
2404

2405
static void dropGroupPrivs(gid_t gid)
2406
{
×
2407
  if (gid != 0) {
×
2408
    if (setgid(gid) == 0) {
×
2409
      if (setgroups(0, nullptr) < 0) {
×
2410
        warnlog("Warning: Unable to drop supplementary gids: %s", stringerror());
×
2411
      }
×
2412
    }
×
2413
    else {
×
2414
      warnlog("Warning: Unable to set group ID to %d: %s", gid, stringerror());
×
2415
    }
×
2416
  }
×
2417
}
×
2418

2419
static void dropUserPrivs(uid_t uid)
2420
{
×
2421
  if (uid != 0) {
×
2422
    if (setuid(uid) < 0) {
×
2423
      warnlog("Warning: Unable to set user ID to %d: %s", uid, stringerror());
×
2424
    }
×
2425
  }
×
2426
}
×
2427

2428
static void checkFileDescriptorsLimits(size_t udpBindsCount, size_t tcpBindsCount)
2429
{
332✔
2430
  const auto& immutableConfig = dnsdist::configuration::getImmutableConfiguration();
332✔
2431
  /* stdin, stdout, stderr */
2432
  rlim_t requiredFDsCount = 3;
332✔
2433
  const auto& backends = dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends;
332✔
2434
  /* UDP sockets to backends */
2435
  size_t backendUDPSocketsCount = 0;
332✔
2436
  for (const auto& backend : backends) {
366✔
2437
    backendUDPSocketsCount += backend->sockets.size();
366✔
2438
  }
366✔
2439
  requiredFDsCount += backendUDPSocketsCount;
332✔
2440
  /* TCP sockets to backends */
2441
  if (immutableConfig.d_maxTCPClientThreads > 0) {
332!
2442
    requiredFDsCount += (backends.size() * immutableConfig.d_maxTCPClientThreads);
332✔
2443
  }
332✔
2444
  /* listening sockets */
2445
  requiredFDsCount += udpBindsCount;
332✔
2446
  requiredFDsCount += tcpBindsCount;
332✔
2447
  /* number of TCP connections currently served, assuming 1 connection per worker thread which is of course not right */
2448
  if (immutableConfig.d_maxTCPClientThreads > 0) {
332!
2449
    requiredFDsCount += immutableConfig.d_maxTCPClientThreads;
332✔
2450
    /* max pipes for communicating between TCP acceptors and client threads */
2451
    requiredFDsCount += (immutableConfig.d_maxTCPClientThreads * 2);
332✔
2452
  }
332✔
2453
  /* max TCP queued connections */
2454
  requiredFDsCount += immutableConfig.d_maxTCPQueuedConnections;
332✔
2455
  /* DelayPipe pipe */
2456
  requiredFDsCount += 2;
332✔
2457
  /* syslog socket */
2458
  requiredFDsCount++;
332✔
2459
  /* webserver main socket */
2460
  requiredFDsCount++;
332✔
2461
  /* console main socket */
2462
  requiredFDsCount++;
332✔
2463
  /* carbon export */
2464
  requiredFDsCount++;
332✔
2465
  /* history file */
2466
  requiredFDsCount++;
332✔
2467
  rlimit resourceLimits{};
332✔
2468
  getrlimit(RLIMIT_NOFILE, &resourceLimits);
332✔
2469
  if (resourceLimits.rlim_cur <= requiredFDsCount) {
332✔
2470
    warnlog("Warning, this configuration can use more than %d file descriptors, web server and console connections not included, and the current limit is %d.", std::to_string(requiredFDsCount), std::to_string(resourceLimits.rlim_cur));
2✔
2471
#ifdef HAVE_SYSTEMD
2✔
2472
    warnlog("You can increase this value by using LimitNOFILE= in the systemd unit file or ulimit.");
2✔
2473
#else
2474
    warnlog("You can increase this value by using ulimit.");
2475
#endif
2476
  }
2✔
2477
}
332✔
2478

2479
static void setupLocalSocket(ClientState& clientState, const ComboAddress& addr, int& socket, bool tcp, bool warn)
2480
{
767✔
2481
  const auto& immutableConfig = dnsdist::configuration::getImmutableConfiguration();
767✔
2482
  static bool s_warned_ipv6_recvpktinfo = false;
767✔
2483
  (void)warn;
767✔
2484
  socket = SSocket(addr.sin4.sin_family, !tcp ? SOCK_DGRAM : SOCK_STREAM, 0);
767✔
2485

2486
  if (tcp) {
767✔
2487
    SSetsockopt(socket, SOL_SOCKET, SO_REUSEADDR, 1);
411✔
2488
#ifdef TCP_DEFER_ACCEPT
411✔
2489
    SSetsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
411✔
2490
#endif
411✔
2491
    if (clientState.fastOpenQueueSize > 0) {
411!
2492
#ifdef TCP_FASTOPEN
×
2493
      SSetsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, clientState.fastOpenQueueSize);
×
2494
#ifdef TCP_FASTOPEN_KEY
×
2495
      if (!immutableConfig.d_tcpFastOpenKey.empty()) {
×
2496
        auto res = setsockopt(socket, IPPROTO_IP, TCP_FASTOPEN_KEY, immutableConfig.d_tcpFastOpenKey.data(), immutableConfig.d_tcpFastOpenKey.size() * sizeof(immutableConfig.d_tcpFastOpenKey[0]));
×
2497
        if (res == -1) {
×
2498
          throw runtime_error("setsockopt for level IPPROTO_TCP and opname TCP_FASTOPEN_KEY failed: " + stringerror());
×
2499
        }
×
2500
      }
×
2501
#endif /* TCP_FASTOPEN_KEY */
×
2502
#else /* TCP_FASTOPEN */
2503
      if (warn) {
2504
        warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", addr.toStringWithPort());
2505
      }
2506
#endif /* TCP_FASTOPEN */
2507
    }
×
2508
  }
411✔
2509

2510
  if (addr.sin4.sin_family == AF_INET6) {
767✔
2511
    SSetsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, 1);
4✔
2512
  }
4✔
2513

2514
  bindAny(addr.sin4.sin_family, socket);
767✔
2515

2516
  if (!tcp && IsAnyAddress(addr)) {
767✔
2517
    int one = 1;
7✔
2518
    (void)setsockopt(socket, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
7✔
2519
#ifdef IPV6_RECVPKTINFO
7✔
2520
    if (addr.isIPv6() && setsockopt(socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) < 0 && !s_warned_ipv6_recvpktinfo) {
7!
2521
      warnlog("Warning: IPV6_RECVPKTINFO setsockopt failed: %s", stringerror());
×
2522
      s_warned_ipv6_recvpktinfo = true;
×
2523
    }
×
2524
#endif
7✔
2525
  }
7✔
2526

2527
  if (clientState.reuseport) {
767✔
2528
    if (!setReusePort(socket)) {
4!
2529
      if (warn) {
×
2530
        /* no need to warn again if configured but support is not available, we already did for UDP */
2531
        warnlog("SO_REUSEPORT has been configured on local address '%s' but is not supported", addr.toStringWithPort());
×
2532
      }
×
2533
    }
×
2534
  }
4✔
2535

2536
  const bool isQUIC = clientState.doqFrontend != nullptr || clientState.doh3Frontend != nullptr;
767✔
2537
  if (isQUIC) {
767✔
2538
    /* disable fragmentation and force PMTU discovery for QUIC-enabled sockets */
2539
    try {
24✔
2540
      setSocketForcePMTU(socket, addr.sin4.sin_family);
24✔
2541
    }
24✔
2542
    catch (const std::exception& e) {
24✔
2543
      warnlog("Failed to set IP_MTU_DISCOVER on QUIC server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
×
2544
    }
×
2545
  }
24✔
2546
  else if (!tcp && !clientState.dnscryptCtx) {
743✔
2547
    /* Only set this on IPv4 UDP sockets.
2548
       Don't set it for DNSCrypt binds. DNSCrypt pads queries for privacy
2549
       purposes, so we do receive large, sometimes fragmented datagrams. */
2550
    try {
328✔
2551
      setSocketIgnorePMTU(socket, addr.sin4.sin_family);
328✔
2552
    }
328✔
2553
    catch (const std::exception& e) {
328✔
2554
      warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
×
2555
    }
×
2556
  }
328✔
2557

2558
  if (!tcp) {
767✔
2559
    if (immutableConfig.d_socketUDPSendBuffer > 0) {
356!
2560
      try {
×
2561
        setSocketSendBuffer(socket, immutableConfig.d_socketUDPSendBuffer);
×
2562
      }
×
2563
      catch (const std::exception& e) {
×
2564
        warnlog(e.what());
×
2565
      }
×
2566
    }
×
2567
    else {
356✔
2568
      try {
356✔
2569
        auto result = raiseSocketSendBufferToMax(socket);
356✔
2570
        if (result > 0) {
356!
2571
          infolog("Raised send buffer to %u for local address '%s'", result, addr.toStringWithPort());
×
2572
        }
×
2573
      }
356✔
2574
      catch (const std::exception& e) {
356✔
2575
        warnlog(e.what());
×
2576
      }
×
2577
    }
356✔
2578

2579
    if (immutableConfig.d_socketUDPRecvBuffer > 0) {
356!
2580
      try {
×
2581
        setSocketReceiveBuffer(socket, immutableConfig.d_socketUDPRecvBuffer);
×
2582
      }
×
2583
      catch (const std::exception& e) {
×
2584
        warnlog(e.what());
×
2585
      }
×
2586
    }
×
2587
    else {
356✔
2588
      try {
356✔
2589
        auto result = raiseSocketReceiveBufferToMax(socket);
356✔
2590
        if (result > 0) {
356!
2591
          infolog("Raised receive buffer to %u for local address '%s'", result, addr.toStringWithPort());
×
2592
        }
×
2593
      }
356✔
2594
      catch (const std::exception& e) {
356✔
2595
        warnlog(e.what());
×
2596
      }
×
2597
    }
356✔
2598
  }
356✔
2599

2600
  const std::string& itf = clientState.interface;
767✔
2601
  if (!itf.empty()) {
767!
2602
#ifdef SO_BINDTODEVICE
×
2603
    int res = setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, itf.c_str(), itf.length());
×
2604
    if (res != 0) {
×
2605
      warnlog("Error setting up the interface on local address '%s': %s", addr.toStringWithPort(), stringerror());
×
2606
    }
×
2607
#else
2608
    if (warn) {
2609
      warnlog("An interface has been configured on local address '%s' but SO_BINDTODEVICE is not supported", addr.toStringWithPort());
2610
    }
2611
#endif
2612
  }
×
2613

2614
#ifdef HAVE_EBPF
767✔
2615
  /* for now eBPF filtering is not enabled on QUIC sockets because the eBPF code tries
2616
     to parse the QNAME from the payload for all UDP datagrams, which obviously does not
2617
     work well for these. */
2618
  if (!isQUIC && g_defaultBPFFilter && !g_defaultBPFFilter->isExternal()) {
767!
2619
    clientState.attachFilter(g_defaultBPFFilter, socket);
6✔
2620
    vinfolog("Attaching default BPF Filter to %s frontend %s", (!tcp ? std::string("UDP") : std::string("TCP")), addr.toStringWithPort());
6!
2621
  }
6✔
2622
#endif /* HAVE_EBPF */
767✔
2623

2624
  SBind(socket, addr);
767✔
2625

2626
  if (tcp) {
767✔
2627
    SListen(socket, clientState.tcpListenQueueSize);
411✔
2628

2629
    if (clientState.tlsFrontend != nullptr) {
411✔
2630
      infolog("Listening on %s for TLS", addr.toStringWithPort());
25✔
2631
    }
25✔
2632
    else if (clientState.dohFrontend != nullptr) {
386✔
2633
      infolog("Listening on %s for DoH", addr.toStringWithPort());
54✔
2634
    }
54✔
2635
    else if (clientState.dnscryptCtx != nullptr) {
332✔
2636
      infolog("Listening on %s for DNSCrypt", addr.toStringWithPort());
4✔
2637
    }
4✔
2638
    else {
328✔
2639
      infolog("Listening on %s", addr.toStringWithPort());
328✔
2640
    }
328✔
2641
  }
411✔
2642
  else {
356✔
2643
    if (clientState.doqFrontend != nullptr) {
356✔
2644
      infolog("Listening on %s for DoQ", addr.toStringWithPort());
14✔
2645
    }
14✔
2646
    else if (clientState.doh3Frontend != nullptr) {
342✔
2647
      infolog("Listening on %s for DoH3", addr.toStringWithPort());
10✔
2648
    }
10✔
2649
#ifdef HAVE_XSK
332✔
2650
    else if (clientState.xskInfo != nullptr) {
332!
2651
      infolog("Listening on %s (XSK-enabled)", addr.toStringWithPort());
×
2652
    }
×
2653
#endif
356✔
2654
  }
356✔
2655
}
767✔
2656

2657
static void setUpLocalBind(ClientState& cstate)
2658
{
767✔
2659
  /* skip some warnings if there is an identical UDP context */
2660
  bool warn = !cstate.tcp || cstate.tlsFrontend != nullptr || cstate.dohFrontend != nullptr;
767✔
2661
  int& descriptor = !cstate.tcp ? cstate.udpFD : cstate.tcpFD;
767✔
2662
  (void)warn;
767✔
2663

2664
  setupLocalSocket(cstate, cstate.local, descriptor, cstate.tcp, warn);
767✔
2665

2666
  for (auto& [addr, socket] : cstate.d_additionalAddresses) {
767!
2667
    setupLocalSocket(cstate, addr, socket, true, false);
×
2668
  }
×
2669

2670
  if (cstate.tlsFrontend != nullptr) {
767✔
2671
    if (!cstate.tlsFrontend->setupTLS()) {
25!
2672
      errlog("Error while setting up TLS on local address '%s', exiting", cstate.local.toStringWithPort());
×
2673
      _exit(EXIT_FAILURE);
×
2674
    }
×
2675
  }
25✔
2676

2677
  if (cstate.dohFrontend != nullptr) {
767✔
2678
    cstate.dohFrontend->setup();
54✔
2679
  }
54✔
2680
  if (cstate.doqFrontend != nullptr) {
767✔
2681
    cstate.doqFrontend->setup();
14✔
2682
  }
14✔
2683
  if (cstate.doh3Frontend != nullptr) {
767✔
2684
    cstate.doh3Frontend->setup();
10✔
2685
  }
10✔
2686

2687
  cstate.ready = true;
767✔
2688
}
767✔
2689

2690
struct CommandLineParameters
2691
{
2692
  vector<string> locals;
2693
  vector<string> remotes;
2694
  bool checkConfig{false};
2695
  bool beClient{false};
2696
  bool beSupervised{false};
2697
  string command;
2698
  string config;
2699
  string uid;
2700
  string gid;
2701
};
2702

2703
static void usage()
2704
{
×
2705
  cout << endl;
×
2706
  cout << "Syntax: dnsdist [-C,--config file] [-c,--client [IP[:PORT]]]\n";
×
2707
  cout << "[-e,--execute cmd] [-h,--help] [-l,--local addr]\n";
×
2708
  cout << "[-v,--verbose] [--check-config] [--version]\n";
×
2709
  cout << "\n";
×
2710
  cout << "-a,--acl netmask      Add this netmask to the ACL\n";
×
2711
  cout << "-C,--config file      Load configuration from 'file'\n";
×
2712
  cout << "-c,--client           Operate as a client, connect to dnsdist. This reads\n";
×
2713
  cout << "                      controlSocket from your configuration file, but also\n";
×
2714
  cout << "                      accepts an IP:PORT argument\n";
×
2715
#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
×
2716
  cout << "-k,--setkey KEY       Use KEY for encrypted communication to dnsdist. This\n";
×
2717
  cout << "                      is similar to setting setKey in the configuration file.\n";
×
2718
  cout << "                      NOTE: this will leak this key in your shell's history\n";
×
2719
  cout << "                      and in the systems running process list.\n";
×
2720
#endif
×
2721
  cout << "--check-config        Validate the configuration file and exit. The exit-code\n";
×
2722
  cout << "                      reflects the validation, 0 is OK, 1 means an error.\n";
×
2723
  cout << "                      Any errors are printed as well.\n";
×
2724
  cout << "-e,--execute cmd      Connect to dnsdist and execute 'cmd'\n";
×
2725
  cout << "-g,--gid gid          Change the process group ID after binding sockets\n";
×
2726
  cout << "-h,--help             Display this helpful message\n";
×
2727
  cout << "-l,--local address    Listen on this local address\n";
×
2728
  cout << "--supervised          Don't open a console, I'm supervised\n";
×
2729
  cout << "                        (use with e.g. systemd and daemontools)\n";
×
2730
  cout << "--disable-syslog      Don't log to syslog, only to stdout\n";
×
2731
  cout << "                        (use with e.g. systemd)\n";
×
2732
  cout << "--log-timestamps      Prepend timestamps to messages logged to stdout.\n";
×
2733
  cout << "-u,--uid uid          Change the process user ID after binding sockets\n";
×
2734
  cout << "-v,--verbose          Enable verbose mode\n";
×
2735
  cout << "-V,--version          Show dnsdist version information and exit\n";
×
2736
}
×
2737

2738
/* g++ defines __SANITIZE_THREAD__
2739
   clang++ supports the nice __has_feature(thread_sanitizer),
2740
   let's merge them */
2741
#if defined(__has_feature)
2742
#if __has_feature(thread_sanitizer)
2743
#define __SANITIZE_THREAD__ 1
2744
#endif
2745
#if __has_feature(address_sanitizer)
2746
#define __SANITIZE_ADDRESS__ 1
2747
#endif
2748
#endif
2749

2750
#if defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE)
2751
#include <sanitizer/lsan_interface.h>
2752
#endif
2753

2754
#if defined(COVERAGE) || (defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE))
2755
static void cleanupLuaObjects()
2756
{
669✔
2757
  /* when our coverage mode is enabled, we need to make sure
2758
     that the Lua objects are destroyed before the Lua contexts. */
2759
  dnsdist::configuration::updateRuntimeConfiguration([](dnsdist::configuration::RuntimeConfiguration& config) {
669✔
2760
    config.d_ruleChains = dnsdist::rules::RuleChains();
669✔
2761
    config.d_lbPolicy = std::make_shared<ServerPolicy>();
669✔
2762
    config.d_pools.clear();
669✔
2763
    config.d_backends.clear();
669✔
2764
  });
669✔
2765
  dnsdist::webserver::clearWebHandlers();
669✔
2766
  dnsdist::lua::hooks::clearMaintenanceHooks();
669✔
2767
}
669✔
2768
#endif /* defined(COVERAGE) || (defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE)) */
2769

2770
#if defined(COVERAGE)
2771
static void sigTermHandler(int)
2772
{
332✔
2773
  cleanupLuaObjects();
332✔
2774
  pdns::coverage::dumpCoverageData();
332✔
2775
  _exit(EXIT_SUCCESS);
332✔
2776
}
332✔
2777
#else
2778
static void sigTermHandler([[maybe_unused]] int sig)
2779
{
2780
#if !defined(__SANITIZE_THREAD__)
2781
  /* TSAN is rightfully unhappy about this:
2782
     WARNING: ThreadSanitizer: signal-unsafe call inside of a signal
2783
     This is not a real problem for us, as the worst case is that
2784
     we crash trying to exit, but let's try to avoid the warnings
2785
     in our tests.
2786
  */
2787
  if (dnsdist::logging::LoggingConfiguration::getSyslog()) {
2788
    syslog(LOG_INFO, "Exiting on user request");
2789
  }
2790
  std::cout << "Exiting on user request" << std::endl;
2791
#endif /* __SANITIZE_THREAD__ */
2792
#if defined(__SANITIZE_ADDRESS__) && defined(HAVE_LEAK_SANITIZER_INTERFACE)
2793
  if (dnsdist::g_asyncHolder) {
2794
    dnsdist::g_asyncHolder->stop();
2795
  }
2796

2797
  for (auto& backend : dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends) {
2798
    backend->stop();
2799
  }
2800

2801
  {
2802
    auto lock = g_lua.lock();
2803
    cleanupLuaObjects();
2804
    *lock = LuaContext();
2805
  }
2806
  __lsan_do_leak_check();
2807
#endif /* __SANITIZE_ADDRESS__ && HAVE_LEAK_SANITIZER_INTERFACE */
2808
  _exit(EXIT_SUCCESS);
2809
}
2810
#endif /* COVERAGE */
2811

2812
static void reportFeatures()
2813
{
×
2814
#ifdef LUAJIT_VERSION
×
2815
  cout << "dnsdist " << VERSION << " (" << LUA_RELEASE << " [" << LUAJIT_VERSION << "])" << endl;
×
2816
#else
2817
  cout << "dnsdist " << VERSION << " (" << LUA_RELEASE << ")" << endl;
2818
#endif
2819
  cout << "Enabled features: ";
×
2820
#ifdef HAVE_XSK
×
2821
  cout << "AF_XDP ";
×
2822
#endif
×
2823
#ifdef HAVE_CDB
×
2824
  cout << "cdb ";
×
2825
#endif
×
2826
#ifdef HAVE_DNS_OVER_QUIC
×
2827
  cout << "dns-over-quic ";
×
2828
#endif
×
2829
#ifdef HAVE_DNS_OVER_HTTP3
×
2830
  cout << "dns-over-http3 ";
×
2831
#endif
×
2832
#ifdef HAVE_DNS_OVER_TLS
×
2833
  cout << "dns-over-tls(";
×
2834
#ifdef HAVE_GNUTLS
×
2835
  cout << "gnutls";
×
2836
#ifdef HAVE_LIBSSL
×
2837
  cout << " ";
×
2838
#endif
×
2839
#endif /* HAVE_GNUTLS */
×
2840
#ifdef HAVE_LIBSSL
×
2841
  cout << "openssl";
×
2842
#endif
×
2843
  cout << ") ";
×
2844
#endif /* HAVE_DNS_OVER_TLS */
×
2845
#ifdef HAVE_DNS_OVER_HTTPS
×
2846
  cout << "dns-over-https(";
×
2847
#ifdef HAVE_LIBH2OEVLOOP
×
2848
  cout << "h2o";
×
2849
#endif /* HAVE_LIBH2OEVLOOP */
×
2850
#if defined(HAVE_LIBH2OEVLOOP) && defined(HAVE_NGHTTP2)
×
2851
  cout << " ";
×
2852
#endif /* defined(HAVE_LIBH2OEVLOOP) && defined(HAVE_NGHTTP2) */
×
2853
#ifdef HAVE_NGHTTP2
×
2854
  cout << "nghttp2";
×
2855
#endif /* HAVE_NGHTTP2 */
×
2856
  cout << ") ";
×
2857
#endif /* HAVE_DNS_OVER_HTTPS */
×
2858
#ifdef HAVE_DNSCRYPT
×
2859
  cout << "dnscrypt ";
×
2860
#endif
×
2861
#ifdef HAVE_EBPF
×
2862
  cout << "ebpf ";
×
2863
#endif
×
2864
#ifdef HAVE_FSTRM
×
2865
  cout << "fstrm ";
×
2866
#endif
×
2867
#ifdef HAVE_IPCIPHER
×
2868
  cout << "ipcipher ";
×
2869
#endif
×
2870
#ifdef HAVE_LIBEDIT
×
2871
  cout << "libedit ";
×
2872
#endif
×
2873
#ifdef HAVE_LIBSODIUM
×
2874
  cout << "libsodium ";
×
2875
#endif
×
2876
#ifdef HAVE_LMDB
×
2877
  cout << "lmdb ";
×
2878
#endif
×
2879
#ifndef DISABLE_PROTOBUF
×
2880
  cout << "protobuf ";
×
2881
#endif
×
2882
#ifdef HAVE_RE2
×
2883
  cout << "re2 ";
×
2884
#endif
×
2885
#ifndef DISABLE_RECVMMSG
×
2886
#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
×
2887
  cout << "recvmmsg/sendmmsg ";
×
2888
#endif
×
2889
#endif /* DISABLE_RECVMMSG */
×
2890
#ifdef HAVE_NET_SNMP
×
2891
  cout << "snmp ";
×
2892
#endif
×
2893
#ifdef HAVE_SYSTEMD
×
2894
  cout << "systemd";
×
2895
#endif
×
2896
  cout << endl;
×
2897
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
2898
#ifdef DNSDIST_CONFIG_ARGS
×
2899
#define double_escape(s) #s
×
2900
#define escape_quotes(s) double_escape(s)
×
2901
  // NOLINTEND(cppcoreguidelines-macro-usage)
2902
  cout << "Configured with: " << escape_quotes(DNSDIST_CONFIG_ARGS) << endl;
×
2903
#undef escape_quotes
×
2904
#undef double_escape
×
2905
#endif
×
2906
}
×
2907

2908
static void parseParameters(int argc, char** argv, CommandLineParameters& cmdLine, ComboAddress& clientAddress)
2909
{
673✔
2910
  const std::array<struct option, 16> longopts{{{"acl", required_argument, nullptr, 'a'},
673✔
2911
                                                {"check-config", no_argument, nullptr, 1},
673✔
2912
                                                {"client", no_argument, nullptr, 'c'},
673✔
2913
                                                {"config", required_argument, nullptr, 'C'},
673✔
2914
                                                {"disable-syslog", no_argument, nullptr, 2},
673✔
2915
                                                {"execute", required_argument, nullptr, 'e'},
673✔
2916
                                                {"gid", required_argument, nullptr, 'g'},
673✔
2917
                                                {"help", no_argument, nullptr, 'h'},
673✔
2918
                                                {"local", required_argument, nullptr, 'l'},
673✔
2919
                                                {"log-timestamps", no_argument, nullptr, 4},
673✔
2920
                                                {"setkey", required_argument, nullptr, 'k'},
673✔
2921
                                                {"supervised", no_argument, nullptr, 3},
673✔
2922
                                                {"uid", required_argument, nullptr, 'u'},
673✔
2923
                                                {"verbose", no_argument, nullptr, 'v'},
673✔
2924
                                                {"version", no_argument, nullptr, 'V'},
673✔
2925
                                                {nullptr, 0, nullptr, 0}}};
673✔
2926
  int longindex = 0;
673✔
2927
  string optstring;
673✔
2928
  dnsdist::configuration::RuntimeConfiguration newConfig;
673✔
2929

2930
  while (true) {
3,749✔
2931
    // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
2932
    int gotChar = getopt_long(argc, argv, "a:cC:e:g:hk:l:u:vV", longopts.data(), &longindex);
3,749✔
2933
    if (gotChar == -1) {
3,749✔
2934
      break;
673✔
2935
    }
673✔
2936
    switch (gotChar) {
3,076!
2937
    case 1:
341✔
2938
      cmdLine.checkConfig = true;
341✔
2939
      break;
341✔
2940
    case 2:
×
2941
      dnsdist::logging::LoggingConfiguration::setSyslog(false);
×
2942
      break;
×
2943
    case 3:
664✔
2944
      cmdLine.beSupervised = true;
664✔
2945
      break;
664✔
2946
    case 4:
×
2947
      dnsdist::logging::LoggingConfiguration::setLogTimestamps(true);
×
2948
      break;
×
2949
    case 'C':
673✔
2950
      cmdLine.config = optarg;
673✔
2951
      break;
673✔
2952
    case 'c':
2✔
2953
      cmdLine.beClient = true;
2✔
2954
      break;
2✔
2955
    case 'e':
×
2956
      cmdLine.command = optarg;
×
2957
      break;
×
2958
    case 'g':
×
2959
      cmdLine.gid = optarg;
×
2960
      break;
×
2961
    case 'h':
×
2962
      cout << "dnsdist " << VERSION << endl;
×
2963
      usage();
×
2964
      cout << "\n";
×
2965
      // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
2966
      exit(EXIT_SUCCESS);
×
2967
      break;
×
2968
    case 'a':
670✔
2969
      optstring = optarg;
670✔
2970
      newConfig.d_ACL.addMask(optstring);
670✔
2971
      break;
670✔
2972
    case 'k':
×
2973
#if defined HAVE_LIBSODIUM || defined(HAVE_LIBCRYPTO)
×
2974
    {
×
2975
      std::string consoleKey;
×
2976
      if (B64Decode(string(optarg), consoleKey) < 0) {
×
2977
        cerr << "Unable to decode key '" << optarg << "'." << endl;
×
2978
        // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
2979
        exit(EXIT_FAILURE);
×
2980
      }
×
2981
      dnsdist::configuration::updateRuntimeConfiguration([&consoleKey](dnsdist::configuration::RuntimeConfiguration& config) {
×
2982
        config.d_consoleKey = std::move(consoleKey);
×
2983
      });
×
2984
    }
×
2985
#else
2986
      cerr << "dnsdist has been built without libsodium or libcrypto, -k/--setkey is unsupported." << endl;
2987
      // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
2988
      exit(EXIT_FAILURE);
2989
#endif
2990
    break;
×
2991
    case 'l':
642✔
2992
      cmdLine.locals.push_back(boost::trim_copy(string(optarg)));
642✔
2993
      break;
642✔
2994
    case 'u':
×
2995
      cmdLine.uid = optarg;
×
2996
      break;
×
2997
    case 'v':
84✔
2998
      newConfig.d_verbose = true;
84✔
2999
      break;
84✔
3000
    case 'V':
×
3001
      reportFeatures();
×
3002
      // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
3003
      exit(EXIT_SUCCESS);
×
3004
      break;
×
3005
    case '?':
×
3006
      // getopt_long printed an error message.
3007
      usage();
×
3008
      // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
3009
      exit(EXIT_FAILURE);
×
3010
      break;
×
3011
    }
3,076✔
3012
  }
3,076✔
3013

3014
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): argv
3015
  argv += optind;
673✔
3016

3017
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): argv
3018
  for (const auto* ptr = argv; *ptr != nullptr; ++ptr) {
673!
3019
    if (cmdLine.beClient) {
×
3020
      clientAddress = ComboAddress(*ptr, 5199);
×
3021
    }
×
3022
    else {
×
3023
      cmdLine.remotes.emplace_back(*ptr);
×
3024
    }
×
3025
  }
×
3026

3027
  dnsdist::configuration::updateRuntimeConfiguration([&newConfig](dnsdist::configuration::RuntimeConfiguration& config) {
673✔
3028
    config = std::move(newConfig);
673✔
3029
  });
673✔
3030
}
673✔
3031
static void setupPools()
3032
{
332✔
3033
  bool precompute = false;
332✔
3034
  if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_lbPolicy->getName() == "chashed") {
332✔
3035
    precompute = true;
1✔
3036
  }
1✔
3037
  else {
331✔
3038
    for (const auto& entry : dnsdist::configuration::getCurrentRuntimeConfiguration().d_pools) {
364✔
3039
      if (entry.second->policy != nullptr && entry.second->policy->getName() == "chashed") {
364!
3040
        precompute = true;
×
3041
        break;
×
3042
      }
×
3043
    }
364✔
3044
  }
331✔
3045
  if (precompute) {
332✔
3046
    vinfolog("Pre-computing hashes for consistent hash load-balancing policy");
1!
3047
    // pre compute hashes
3048
    for (const auto& backend : dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends) {
2✔
3049
      if (backend->d_config.d_weight < 100) {
2!
3050
        vinfolog("Warning, the backend '%s' has a very low weight (%d), which will not yield a good distribution of queries with the 'chashed' policy. Please consider raising it to at least '100'.", backend->getName(), backend->d_config.d_weight);
×
3051
      }
×
3052

3053
      backend->hash();
2✔
3054
    }
2✔
3055
  }
1✔
3056
}
332✔
3057

3058
static void dropPrivileges(const CommandLineParameters& cmdLine)
3059
{
332✔
3060
  uid_t newgid = getegid();
332✔
3061
  gid_t newuid = geteuid();
332✔
3062

3063
  if (!cmdLine.gid.empty()) {
332!
3064
    newgid = strToGID(cmdLine.gid);
×
3065
  }
×
3066

3067
  if (!cmdLine.uid.empty()) {
332!
3068
    newuid = strToUID(cmdLine.uid);
×
3069
  }
×
3070

3071
  bool retainedCapabilities = true;
332✔
3072
  if (!dnsdist::configuration::getImmutableConfiguration().d_capabilitiesToRetain.empty() && (getegid() != newgid || geteuid() != newuid)) {
332!
3073
    retainedCapabilities = keepCapabilitiesAfterSwitchingIDs();
×
3074
  }
×
3075

3076
  if (getegid() != newgid) {
332!
3077
    if (running_in_service_mgr()) {
×
3078
      errlog("--gid/-g set on command-line, but dnsdist was started as a systemd service. Use the 'Group' setting in the systemd unit file to set the group to run as");
×
3079
      _exit(EXIT_FAILURE);
×
3080
    }
×
3081
    dropGroupPrivs(newgid);
×
3082
  }
×
3083

3084
  if (geteuid() != newuid) {
332!
3085
    if (running_in_service_mgr()) {
×
3086
      errlog("--uid/-u set on command-line, but dnsdist was started as a systemd service. Use the 'User' setting in the systemd unit file to set the user to run as");
×
3087
      _exit(EXIT_FAILURE);
×
3088
    }
×
3089
    dropUserPrivs(newuid);
×
3090
  }
×
3091

3092
  if (retainedCapabilities) {
332!
3093
    dropCapabilitiesAfterSwitchingIDs();
332✔
3094
  }
332✔
3095

3096
  try {
332✔
3097
    /* we might still have capabilities remaining,
3098
       for example if we have been started as root
3099
       without --uid or --gid (please don't do that)
3100
       or as an unprivileged user with ambient
3101
       capabilities like CAP_NET_BIND_SERVICE.
3102
    */
3103
    dropCapabilities(dnsdist::configuration::getImmutableConfiguration().d_capabilitiesToRetain);
332✔
3104
  }
332✔
3105
  catch (const std::exception& e) {
332✔
3106
    warnlog("%s", e.what());
×
3107
  }
×
3108
}
332✔
3109

3110
static void initFrontends(const CommandLineParameters& cmdLine)
3111
{
332✔
3112
  auto frontends = dnsdist::configuration::getImmutableConfiguration().d_frontends;
332✔
3113

3114
  if (!cmdLine.locals.empty()) {
332✔
3115
    for (auto it = frontends.begin(); it != frontends.end();) {
433✔
3116
      /* DoH, DoT and DNSCrypt frontends are separate */
3117
      if ((*it)->dohFrontend == nullptr && (*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->doqFrontend == nullptr && (*it)->doh3Frontend == nullptr) {
112✔
3118
        it = frontends.erase(it);
8✔
3119
      }
8✔
3120
      else {
104✔
3121
        ++it;
104✔
3122
      }
104✔
3123
    }
112✔
3124

3125
    for (const auto& loc : cmdLine.locals) {
321✔
3126
      /* UDP */
3127
      frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), false, false, 0, "", std::set<int>{}, true));
321✔
3128
      /* TCP */
3129
      frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), true, false, 0, "", std::set<int>{}, true));
321✔
3130
    }
321✔
3131
  }
321✔
3132

3133
  if (frontends.empty()) {
332!
3134
    /* UDP */
3135
    frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), false, false, 0, "", std::set<int>{}, true));
×
3136
    /* TCP */
3137
    frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), true, false, 0, "", std::set<int>{}, true));
×
3138
  }
×
3139

3140
  dnsdist::configuration::updateImmutableConfiguration([&frontends](dnsdist::configuration::ImmutableConfiguration& config) {
332✔
3141
    config.d_frontends = std::move(frontends);
332✔
3142
  });
332✔
3143
}
332✔
3144

3145
namespace dnsdist
3146
{
3147
static void startFrontends()
3148
{
332✔
3149
#ifdef HAVE_XSK
332✔
3150
  for (auto& xskContext : dnsdist::xsk::g_xsk) {
332!
3151
    std::thread xskThread(dnsdist::xsk::XskRouter, std::move(xskContext));
×
3152
    xskThread.detach();
×
3153
  }
×
3154
#endif /* HAVE_XSK */
332✔
3155

3156
  std::vector<ClientState*> tcpStates;
332✔
3157
  std::vector<ClientState*> udpStates;
332✔
3158
  for (const auto& clientState : dnsdist::getFrontends()) {
767✔
3159
#ifdef HAVE_XSK
767✔
3160
    if (clientState->xskInfo) {
767!
3161
      dnsdist::xsk::addDestinationAddress(clientState->local);
×
3162

3163
      std::thread xskCT(dnsdist::xsk::XskClientThread, clientState.get());
×
3164
      if (!clientState->cpus.empty()) {
×
3165
        mapThreadToCPUList(xskCT.native_handle(), clientState->cpus);
×
3166
      }
×
3167
      xskCT.detach();
×
3168
    }
×
3169
#endif /* HAVE_XSK */
767✔
3170

3171
    if (clientState->dohFrontend != nullptr && clientState->dohFrontend->d_library == "h2o") {
767✔
3172
#ifdef HAVE_DNS_OVER_HTTPS
23✔
3173
#ifdef HAVE_LIBH2OEVLOOP
23✔
3174
      std::thread dotThreadHandle(dohThread, clientState.get());
23✔
3175
      if (!clientState->cpus.empty()) {
23!
3176
        mapThreadToCPUList(dotThreadHandle.native_handle(), clientState->cpus);
×
3177
      }
×
3178
      dotThreadHandle.detach();
23✔
3179
#endif /* HAVE_LIBH2OEVLOOP */
23✔
3180
#endif /* HAVE_DNS_OVER_HTTPS */
23✔
3181
      continue;
23✔
3182
    }
23✔
3183
    if (clientState->doqFrontend != nullptr) {
744✔
3184
#ifdef HAVE_DNS_OVER_QUIC
14✔
3185
      std::thread doqThreadHandle(doqThread, clientState.get());
14✔
3186
      if (!clientState->cpus.empty()) {
14!
3187
        mapThreadToCPUList(doqThreadHandle.native_handle(), clientState->cpus);
×
3188
      }
×
3189
      doqThreadHandle.detach();
14✔
3190
#endif /* HAVE_DNS_OVER_QUIC */
14✔
3191
      continue;
14✔
3192
    }
14✔
3193
    if (clientState->doh3Frontend != nullptr) {
730✔
3194
#ifdef HAVE_DNS_OVER_HTTP3
10✔
3195
      std::thread doh3ThreadHandle(doh3Thread, clientState.get());
10✔
3196
      if (!clientState->cpus.empty()) {
10!
3197
        mapThreadToCPUList(doh3ThreadHandle.native_handle(), clientState->cpus);
×
3198
      }
×
3199
      doh3ThreadHandle.detach();
10✔
3200
#endif /* HAVE_DNS_OVER_HTTP3 */
10✔
3201
      continue;
10✔
3202
    }
10✔
3203
    if (clientState->udpFD >= 0) {
720✔
3204
#ifdef USE_SINGLE_ACCEPTOR_THREAD
3205
      udpStates.push_back(clientState.get());
3206
#else /* USE_SINGLE_ACCEPTOR_THREAD */
3207
      std::thread udpClientThreadHandle(udpClientThread, std::vector<ClientState*>{clientState.get()});
332✔
3208
      if (!clientState->cpus.empty()) {
332!
3209
        mapThreadToCPUList(udpClientThreadHandle.native_handle(), clientState->cpus);
×
3210
      }
×
3211
      udpClientThreadHandle.detach();
332✔
3212
#endif /* USE_SINGLE_ACCEPTOR_THREAD */
332✔
3213
    }
332✔
3214
    else if (clientState->tcpFD >= 0) {
388!
3215
#ifdef USE_SINGLE_ACCEPTOR_THREAD
3216
      tcpStates.push_back(clientState.get());
3217
#else /* USE_SINGLE_ACCEPTOR_THREAD */
3218
      std::thread tcpAcceptorThreadHandle(tcpAcceptorThread, std::vector<ClientState*>{clientState.get()});
388✔
3219
      if (!clientState->cpus.empty()) {
388!
3220
        mapThreadToCPUList(tcpAcceptorThreadHandle.native_handle(), clientState->cpus);
×
3221
      }
×
3222
      tcpAcceptorThreadHandle.detach();
388✔
3223
#endif /* USE_SINGLE_ACCEPTOR_THREAD */
388✔
3224
    }
388✔
3225
  }
720✔
3226
#ifdef USE_SINGLE_ACCEPTOR_THREAD
3227
  if (!udpStates.empty()) {
3228
    std::thread udpThreadHandle(udpClientThread, udpStates);
3229
    udpThreadHandle.detach();
3230
  }
3231
  if (!tcpStates.empty()) {
3232
    g_tcpclientthreads = std::make_unique<TCPClientCollection>(1, tcpStates);
3233
  }
3234
#endif /* USE_SINGLE_ACCEPTOR_THREAD */
3235
}
332✔
3236
}
3237

3238
struct ListeningSockets
3239
{
3240
  Socket d_consoleSocket{-1};
3241
  Socket d_webServerSocket{-1};
3242
};
3243

3244
static ListeningSockets initListeningSockets()
3245
{
332✔
3246
  ListeningSockets result;
332✔
3247
  const auto& currentConfig = dnsdist::configuration::getCurrentRuntimeConfiguration();
332✔
3248

3249
  if (currentConfig.d_consoleEnabled) {
332✔
3250
    const auto& local = currentConfig.d_consoleServerAddress;
64✔
3251
    try {
64✔
3252
      result.d_consoleSocket = Socket(local.sin4.sin_family, SOCK_STREAM, 0);
64✔
3253
      result.d_consoleSocket.bind(local, true);
64✔
3254
      result.d_consoleSocket.listen(5);
64✔
3255
    }
64✔
3256
    catch (const std::exception& exp) {
64✔
3257
      errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), exp.what());
×
3258
    }
×
3259
  }
64✔
3260

3261
  if (currentConfig.d_webServerAddress) {
332✔
3262
    const auto& local = *currentConfig.d_webServerAddress;
45✔
3263
    try {
45✔
3264
      result.d_webServerSocket = Socket(local.sin4.sin_family, SOCK_STREAM, 0);
45✔
3265
      result.d_webServerSocket.bind(local, true);
45✔
3266
      result.d_webServerSocket.listen(5);
45✔
3267
    }
45✔
3268
    catch (const std::exception& exp) {
45✔
3269
      errlog("Unable to bind to web server socket on %s: %s", local.toStringWithPort(), exp.what());
×
3270
    }
×
3271
  }
45✔
3272

3273
  return result;
332✔
3274
}
332✔
3275

3276
static std::optional<std::string> lookForTentativeConfigurationFileWithExtension(const std::string& configurationFile, const std::string& extension)
3277
{
20✔
3278
  auto dotPos = configurationFile.rfind('.');
20✔
3279
  if (dotPos == std::string::npos) {
20!
3280
    return std::nullopt;
×
3281
  }
×
3282
  auto tentativeFile = configurationFile.substr(0, dotPos + 1) + extension;
20✔
3283
  if (!std::filesystem::exists(tentativeFile)) {
20!
3284
    return std::nullopt;
×
3285
  }
×
3286
  return tentativeFile;
20✔
3287
}
20✔
3288

3289
static bool loadConfigurationFromFile(const std::string& configurationFile, bool isClient, bool configCheck)
3290
{
673✔
3291
  if (boost::ends_with(configurationFile, ".yml")) {
673!
3292
    if (auto tentativeLuaConfFile = lookForTentativeConfigurationFileWithExtension(configurationFile, "lua")) {
×
3293
      vinfolog("Loading configuration from auto-discovered Lua file %s", *tentativeLuaConfFile);
×
3294
      dnsdist::configuration::lua::loadLuaConfigurationFile(*(g_lua.lock()), *tentativeLuaConfFile, configCheck);
×
3295
    }
×
3296
    vinfolog("Loading configuration from YAML file %s", configurationFile);
×
3297
    return dnsdist::configuration::yaml::loadConfigurationFromFile(configurationFile, isClient, configCheck);
×
3298
  }
×
3299
  if (boost::ends_with(configurationFile, ".lua")) {
673✔
3300
    vinfolog("Loading configuration from Lua file %s", configurationFile);
20✔
3301
    dnsdist::configuration::lua::loadLuaConfigurationFile(*(g_lua.lock()), configurationFile, configCheck);
20✔
3302
    if (auto tentativeYamlConfFile = lookForTentativeConfigurationFileWithExtension(configurationFile, "yml")) {
20!
3303
      vinfolog("Loading configuration from auto-discovered YAML file %s", *tentativeYamlConfFile);
20✔
3304
      return dnsdist::configuration::yaml::loadConfigurationFromFile(*tentativeYamlConfFile, isClient, configCheck);
20✔
3305
    }
20✔
3306
  }
20✔
3307
  else {
653✔
3308
    vinfolog("Loading configuration from Lua file %s", configurationFile);
653✔
3309
    dnsdist::configuration::lua::loadLuaConfigurationFile(*(g_lua.lock()), configurationFile, configCheck);
653✔
3310
  }
653✔
3311
  return true;
653✔
3312
}
673✔
3313

3314
int main(int argc, char** argv)
3315
{
673✔
3316
  try {
673✔
3317
    CommandLineParameters cmdLine{};
673✔
3318
    size_t udpBindsCount = 0;
673✔
3319
    size_t tcpBindsCount = 0;
673✔
3320

3321
    dnsdist::console::setupCompletion();
673✔
3322

3323
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): SIG_IGN macro
3324
    signal(SIGPIPE, SIG_IGN);
673✔
3325
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): SIG_IGN macro
3326
    signal(SIGCHLD, SIG_IGN);
673✔
3327
    signal(SIGTERM, sigTermHandler);
673✔
3328

3329
    openlog("dnsdist", LOG_PID | LOG_NDELAY, LOG_DAEMON);
673✔
3330

3331
#ifdef HAVE_LIBSODIUM
673✔
3332
    if (sodium_init() == -1) {
673!
3333
      cerr << "Unable to initialize crypto library" << endl;
×
3334
      // NOLINTNEXTLINE(concurrency-mt-unsafe): only on thread at this point
3335
      exit(EXIT_FAILURE);
×
3336
    }
×
3337
#endif
673✔
3338
    dnsdist::initRandom();
673✔
3339
    dnsdist::configuration::updateImmutableConfiguration([](dnsdist::configuration::ImmutableConfiguration& config) {
673✔
3340
      config.d_hashPerturbation = dnsdist::getRandomValue(0xffffffff);
673✔
3341
    });
673✔
3342

3343
#ifdef HAVE_XSK
673✔
3344
    try {
673✔
3345
      dnsdist::xsk::clearDestinationAddresses();
673✔
3346
    }
673✔
3347
    catch (const std::exception& exp) {
673✔
3348
      /* silently handle failures: at this point we don't even know if XSK is enabled,
3349
         and we might not have the correct map (not the default one). */
3350
    }
673✔
3351
#endif /* HAVE_XSK */
673✔
3352

3353
    ComboAddress clientAddress = ComboAddress();
673✔
3354
    cmdLine.config = SYSCONFDIR "/dnsdist.conf";
673✔
3355

3356
    parseParameters(argc, argv, cmdLine, clientAddress);
673✔
3357

3358
    dnsdist::configuration::updateRuntimeConfiguration([](dnsdist::configuration::RuntimeConfiguration& config) {
673✔
3359
      config.d_lbPolicy = std::make_shared<ServerPolicy>("leastOutstanding", leastOutstanding, false);
673✔
3360
    });
673✔
3361

3362
    if (cmdLine.beClient || !cmdLine.command.empty()) {
673!
3363
      dnsdist::lua::setupLua(*(g_lua.lock()), true, false);
2✔
3364
      if (!loadConfigurationFromFile(cmdLine.config, true, false)) {
2!
3365
#ifdef COVERAGE
×
3366
        exit(EXIT_FAILURE);
×
3367
#else
3368
        _exit(EXIT_FAILURE);
3369
#endif
3370
      }
×
3371
      if (clientAddress != ComboAddress()) {
2!
3372
        dnsdist::configuration::updateRuntimeConfiguration([&clientAddress](dnsdist::configuration::RuntimeConfiguration& config) {
×
3373
          config.d_consoleServerAddress = clientAddress;
×
3374
        });
×
3375
      }
×
3376
      dnsdist::console::doClient(cmdLine.command);
2✔
3377
#ifdef COVERAGE
2✔
3378
      exit(EXIT_SUCCESS);
2✔
3379
#else
3380
      _exit(EXIT_SUCCESS);
3381
#endif
3382
    }
2✔
3383

3384
    dnsdist::configuration::updateRuntimeConfiguration([](dnsdist::configuration::RuntimeConfiguration& config) {
671✔
3385
      auto& acl = config.d_ACL;
671✔
3386
      if (acl.empty()) {
671✔
3387
        for (const auto& addr : {"127.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "169.254.0.0/16", "192.168.0.0/16", "172.16.0.0/12", "::1/128", "fc00::/7", "fe80::/10"}) {
63✔
3388
          acl.addMask(addr);
63✔
3389
        }
63✔
3390
      }
7✔
3391
      for (const auto& mask : {"127.0.0.1/8", "::1/128"}) {
1,342✔
3392
        config.d_consoleACL.addMask(mask);
1,342✔
3393
      }
1,342✔
3394
      config.d_webServerACL.toMasks("127.0.0.1, ::1");
671✔
3395
    });
671✔
3396

3397
    dnsdist::webserver::registerBuiltInWebHandlers();
671✔
3398

3399
    if (cmdLine.checkConfig) {
671✔
3400
      dnsdist::lua::setupLua(*(g_lua.lock()), false, true);
339✔
3401
      if (!loadConfigurationFromFile(cmdLine.config, false, true)) {
339!
3402
#ifdef COVERAGE
×
3403
        exit(EXIT_FAILURE);
×
3404
#else
3405
        _exit(EXIT_FAILURE);
3406
#endif
3407
      }
×
3408
      // No exception was thrown
3409
      infolog("Configuration '%s' OK!", cmdLine.config);
339✔
3410
#ifdef COVERAGE
339✔
3411
      cleanupLuaObjects();
339✔
3412
      exit(EXIT_SUCCESS);
339✔
3413
#else
3414
      _exit(EXIT_SUCCESS);
3415
#endif
3416
    }
339✔
3417

3418
    infolog("dnsdist %s comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2", VERSION);
332✔
3419

3420
    dnsdist::g_asyncHolder = std::make_unique<dnsdist::AsynchronousHolder>();
332✔
3421

3422
    /* create the default pool no matter what */
3423
    createPoolIfNotExists("");
332✔
3424

3425
    dnsdist::lua::setupLua(*(g_lua.lock()), false, false);
332✔
3426
    if (!loadConfigurationFromFile(cmdLine.config, false, false)) {
332!
3427
#ifdef COVERAGE
×
3428
      exit(EXIT_FAILURE);
×
3429
#else
3430
      _exit(EXIT_FAILURE);
3431
#endif
3432
    }
×
3433

3434
    setupPools();
332✔
3435

3436
    initFrontends(cmdLine);
332✔
3437

3438
    for (const auto& frontend : dnsdist::getFrontends()) {
767✔
3439
      if (!frontend->tcp) {
767✔
3440
        ++udpBindsCount;
356✔
3441
      }
356✔
3442
      else {
411✔
3443
        ++tcpBindsCount;
411✔
3444
      }
411✔
3445
    }
767✔
3446

3447
    dnsdist::configuration::setImmutableConfigurationDone();
332✔
3448

3449
    {
332✔
3450
      const auto& immutableConfig = dnsdist::configuration::getImmutableConfiguration();
332✔
3451
      setTCPDownstreamMaxIdleConnectionsPerBackend(immutableConfig.d_outgoingTCPMaxIdlePerBackend);
332✔
3452
      setTCPDownstreamMaxIdleTime(immutableConfig.d_outgoingTCPMaxIdleTime);
332✔
3453
      setTCPDownstreamCleanupInterval(immutableConfig.d_outgoingTCPCleanupInterval);
332✔
3454
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
332✔
3455
      setDoHDownstreamMaxIdleConnectionsPerBackend(immutableConfig.d_outgoingDoHMaxIdlePerBackend);
332✔
3456
      setDoHDownstreamMaxIdleTime(immutableConfig.d_outgoingDoHMaxIdleTime);
332✔
3457
      setDoHDownstreamCleanupInterval(immutableConfig.d_outgoingDoHCleanupInterval);
332✔
3458
#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
332✔
3459
    }
332✔
3460

3461
    {
332✔
3462
      const auto& config = dnsdist::configuration::getImmutableConfiguration();
332✔
3463
      g_rings.init(config.d_ringsCapacity, config.d_ringsNumberOfShards, config.d_ringsNbLockTries, config.d_ringsRecordQueries, config.d_ringsRecordResponses);
332✔
3464
    }
332✔
3465

3466
    for (const auto& frontend : dnsdist::getFrontends()) {
767✔
3467
      setUpLocalBind(*frontend);
767✔
3468
    }
767✔
3469

3470
    {
332✔
3471
      std::string acls;
332✔
3472
      auto aclEntries = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL.toStringVector();
332✔
3473
      for (const auto& aclEntry : aclEntries) {
419✔
3474
        if (!acls.empty()) {
419✔
3475
          acls += ", ";
87✔
3476
        }
87✔
3477
        acls += aclEntry;
419✔
3478
      }
419✔
3479
      infolog("ACL allowing queries from: %s", acls);
332✔
3480
    }
332✔
3481
    {
332✔
3482
      std::string acls;
332✔
3483
      auto aclEntries = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleACL.toStringVector();
332✔
3484
      for (const auto& entry : aclEntries) {
658✔
3485
        if (!acls.empty()) {
658✔
3486
          acls += ", ";
326✔
3487
        }
326✔
3488
        acls += entry;
658✔
3489
      }
658✔
3490
      infolog("Console ACL allowing connections from: %s", acls.c_str());
332✔
3491
    }
332✔
3492

3493
    auto listeningSockets = initListeningSockets();
332✔
3494

3495
#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
332✔
3496
    if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleEnabled && dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey.empty()) {
332✔
3497
      warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
1✔
3498
    }
1✔
3499
#endif
332✔
3500

3501
    dropPrivileges(cmdLine);
332✔
3502

3503
    /* this need to be done _after_ dropping privileges */
3504
#ifndef DISABLE_DELAY_PIPE
332✔
3505
    g_delay = std::make_unique<DelayPipe<DelayedPacket>>();
332✔
3506
#endif /* DISABLE_DELAY_PIPE */
332✔
3507

3508
#if defined(HAVE_NET_SNMP)
332✔
3509
    if (dnsdist::configuration::getImmutableConfiguration().d_snmpEnabled) {
332✔
3510
      g_snmpAgent = std::make_unique<DNSDistSNMPAgent>("dnsdist", dnsdist::configuration::getImmutableConfiguration().d_snmpDaemonSocketPath);
1✔
3511
      g_snmpAgent->run();
1✔
3512
    }
1✔
3513
#endif /* HAVE_NET_SNMP */
332✔
3514

3515
    /* we need to create the TCP worker threads before the
3516
       acceptor ones, otherwise we might crash when processing
3517
       the first TCP query */
3518
#ifndef USE_SINGLE_ACCEPTOR_THREAD
332✔
3519
    const auto maxTCPClientThreads = dnsdist::configuration::getImmutableConfiguration().d_maxTCPClientThreads;
332✔
3520
    /* the limit is completely arbitrary: hopefully high enough not to trigger too many false positives
3521
       but low enough to be useful */
3522
    if (maxTCPClientThreads >= 50U) {
332!
3523
      warnlog("setMaxTCPClientThreads(%d) might create a large number of TCP connections to backends, and is probably not needed, please consider lowering it", maxTCPClientThreads);
×
3524
    }
×
3525
    g_tcpclientthreads = std::make_unique<TCPClientCollection>(maxTCPClientThreads, std::vector<ClientState*>());
332✔
3526
#endif
332✔
3527

3528
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
332✔
3529
    initDoHWorkers();
332✔
3530
#endif
332✔
3531

3532
    if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleEnabled) {
332✔
3533
      std::thread consoleControlThread(dnsdist::console::controlThread, std::move(listeningSockets.d_consoleSocket));
64✔
3534
      consoleControlThread.detach();
64✔
3535
    }
64✔
3536
    if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_webServerAddress) {
332✔
3537
      std::thread webServerThread(dnsdist::webserver::WebserverThread, std::move(listeningSockets.d_webServerSocket));
45✔
3538
      webServerThread.detach();
45✔
3539
    }
45✔
3540

3541
    for (const auto& backend : dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends) {
366✔
3542
      if (backend->connected) {
366✔
3543
        backend->start();
338✔
3544
      }
338✔
3545
    }
366✔
3546

3547
    if (!cmdLine.remotes.empty()) {
332!
3548
      for (const auto& address : cmdLine.remotes) {
×
3549
        DownstreamState::Config config;
×
3550
        config.remote = ComboAddress(address, 53);
×
3551
        auto ret = std::make_shared<DownstreamState>(std::move(config), nullptr, true);
×
3552
        addServerToPool("", ret);
×
3553
        ret->start();
×
3554
        dnsdist::configuration::updateRuntimeConfiguration([&ret](dnsdist::configuration::RuntimeConfiguration& runtimeConfig) {
×
3555
          runtimeConfig.d_backends.push_back(std::move(ret));
×
3556
        });
×
3557
      }
×
3558
    }
×
3559

3560
    if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends.empty()) {
332✔
3561
      errlog("No downstream servers defined: all packets will get dropped");
5✔
3562
      // you might define them later, but you need to know
3563
    }
5✔
3564

3565
    checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount);
332✔
3566

3567
    {
332✔
3568
      // coverity[auto_causes_copy]
3569
      const auto states = dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends; // it is a copy, but the internal shared_ptrs are the real deal
332✔
3570
      auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states.size()));
332✔
3571
      for (auto& dss : states) {
366✔
3572

3573
        if (dss->d_config.availability == DownstreamState::Availability::Auto || dss->d_config.availability == DownstreamState::Availability::Lazy) {
366✔
3574
          if (dss->d_config.availability == DownstreamState::Availability::Auto) {
299✔
3575
            dss->d_nextCheck = dss->d_config.checkInterval;
296✔
3576
          }
296✔
3577

3578
          if (!queueHealthCheck(mplexer, dss, true)) {
299!
3579
            dss->submitHealthCheckResult(true, false);
×
3580
            dss->setUpStatus(false);
×
3581
            warnlog("Marking downstream %s as 'down'", dss->getNameWithAddr());
×
3582
          }
×
3583
        }
299✔
3584
      }
366✔
3585
      handleQueuedHealthChecks(*mplexer, true);
332✔
3586
    }
332✔
3587

3588
    dnsdist::startFrontends();
332✔
3589

3590
    dnsdist::ServiceDiscovery::run();
332✔
3591

3592
#ifndef DISABLE_CARBON
332✔
3593
    dnsdist::Carbon::run(dnsdist::configuration::getCurrentRuntimeConfiguration().d_carbonEndpoints);
332✔
3594
#endif /* DISABLE_CARBON */
332✔
3595

3596
    thread stattid(maintThread);
332✔
3597
    stattid.detach();
332✔
3598

3599
    thread healththread(healthChecksThread);
332✔
3600

3601
#ifndef DISABLE_DYNBLOCKS
332✔
3602
    thread dynBlockMaintThread(dynBlockMaintenanceThread);
332✔
3603
    dynBlockMaintThread.detach();
332✔
3604
#endif /* DISABLE_DYNBLOCKS */
332✔
3605

3606
#ifndef DISABLE_SECPOLL
332✔
3607
    if (!dnsdist::configuration::getCurrentRuntimeConfiguration().d_secPollSuffix.empty()) {
332!
3608
      thread secpollthread(secPollThread);
×
3609
      secpollthread.detach();
×
3610
    }
×
3611
#endif /* DISABLE_SECPOLL */
332✔
3612

3613
    if (cmdLine.beSupervised) {
332!
3614
#ifdef HAVE_SYSTEMD
332✔
3615
      sd_notify(0, "READY=1");
332✔
3616
#endif
332✔
3617
      healththread.join();
332✔
3618
    }
332✔
3619
    else {
×
3620
      healththread.detach();
×
3621
      dnsdist::console::doConsole();
×
3622
    }
×
3623
#ifdef COVERAGE
332✔
3624
    cleanupLuaObjects();
332✔
3625
    exit(EXIT_SUCCESS);
332✔
3626
#else
3627
    _exit(EXIT_SUCCESS);
3628
#endif
3629
  }
332✔
3630
  catch (const LuaContext::ExecutionErrorException& e) {
673✔
3631
    try {
2✔
3632
      errlog("Fatal Lua error: %s", e.what());
2✔
3633
      std::rethrow_if_nested(e);
2✔
3634
    }
2✔
3635
    catch (const std::exception& ne) {
2✔
3636
      errlog("Details: %s", ne.what());
×
3637
    }
×
3638
    catch (const PDNSException& ae) {
2✔
3639
      errlog("Fatal pdns error: %s", ae.reason);
1✔
3640
    }
1✔
3641
#ifdef COVERAGE
2✔
3642
    cleanupLuaObjects();
2✔
3643
    exit(EXIT_FAILURE);
2✔
3644
#else
3645
    _exit(EXIT_FAILURE);
3646
#endif
3647
  }
2✔
3648
  catch (const std::exception& e) {
673✔
3649
    errlog("Fatal error: %s", e.what());
1✔
3650
#ifdef COVERAGE
1✔
3651
    cleanupLuaObjects();
1✔
3652
    exit(EXIT_FAILURE);
1✔
3653
#else
3654
    _exit(EXIT_FAILURE);
3655
#endif
3656
  }
1✔
3657
  catch (const PDNSException& ae) {
673✔
3658
    errlog("Fatal pdns error: %s", ae.reason);
×
3659
#ifdef COVERAGE
×
3660
    cleanupLuaObjects();
×
3661
    exit(EXIT_FAILURE);
×
3662
#else
3663
    _exit(EXIT_FAILURE);
3664
#endif
3665
  }
×
3666
}
673✔
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