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

PowerDNS / pdns / 18743945403

23 Oct 2025 09:29AM UTC coverage: 65.845% (+0.02%) from 65.829%
18743945403

Pull #16356

github

web-flow
Merge 8a2027ef1 into efa3637e8
Pull Request #16356: auth 5.0: backport "pdnsutil: fix b2b-migrate to from sql to non-sql"

42073 of 92452 branches covered (45.51%)

Branch coverage included in aggregate %.

128008 of 165855 relevant lines covered (77.18%)

6379935.17 hits per line

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

87.79
/pdns/recursordist/syncres.hh
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
#pragma once
23
#include <string>
24
#include <atomic>
25
#include "utility.hh"
26
#include "dns.hh"
27
#include "qtype.hh"
28
#include <vector>
29
#include <set>
30
#include <unordered_set>
31
#include <map>
32
#include <cmath>
33
#include <iostream>
34
#include <utility>
35
#include "misc.hh"
36
#include "lwres.hh"
37
#include <boost/optional.hpp>
38
#include <boost/utility.hpp>
39
#include "circular_buffer.hh"
40
#include "sstuff.hh"
41
#include "recursor_cache.hh"
42
#include "mtasker.hh"
43
#include "iputils.hh"
44
#include "validate-recursor.hh"
45
#include "ednssubnet.hh"
46
#include "filterpo.hh"
47
#include "negcache.hh"
48
#include "proxy-protocol.hh"
49
#include "sholder.hh"
50
#include "histogram.hh"
51
#include "stat_t.hh"
52
#include "tcpiohandler.hh"
53
#include "rec-eventtrace.hh"
54
#include "logr.hh"
55
#include "rec-tcounters.hh"
56
#include "ednsextendederror.hh"
57
#include "protozero-trace.hh"
58

59
#ifdef HAVE_CONFIG_H
60
#include "config.h"
61
#endif
62

63
#include <boost/uuid/uuid.hpp>
64
#ifdef HAVE_FSTRM
65
#include "fstrm_logger.hh"
66
#endif /* HAVE_FSTRM */
67

68
extern GlobalStateHolder<SuffixMatchNode> g_xdnssec;
69
extern GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
70
extern GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
71
extern GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
72

73
enum class AdditionalMode : uint8_t; // defined in rec-lua-conf.hh
74

75
class RecursorLua4;
76

77
using NsSet = std::unordered_map<DNSName, pair<vector<ComboAddress>, bool>>;
78

79
extern std::unique_ptr<NegCache> g_negCache;
80

81
class SyncRes : public boost::noncopyable
82
{
83
public:
84
  enum LogMode
85
  {
86
    LogNone,
87
    Log,
88
    Store
89
  };
90
  using asyncresolve_t = std::function<LWResult::Result(const ComboAddress&, const DNSName&, int, bool, bool, int, struct timeval*, boost::optional<Netmask>&, const ResolveContext&, LWResult*, bool*)>;
91

92
  enum class HardenNXD
93
  {
94
    No,
95
    DNSSEC,
96
    Yes
97
  };
98

99
  struct Context
100
  {
101
    boost::optional<EDNSExtendedError> extendedError;
102
    vState state{vState::Indeterminate};
103
  };
104

105
  vState getDSRecords(const DNSName& zone, dsset_t& dsSet, bool onlyTA, unsigned int depth, const string& prefix, bool bogusOnNXD = true, bool* foundCut = nullptr);
106

107
  class AuthDomain
108
  {
109
  public:
110
    using records_t = multi_index_container<
111
      DNSRecord,
112
      indexed_by<
113
        ordered_non_unique<
114
          composite_key<DNSRecord,
115
                        member<DNSRecord, DNSName, &DNSRecord::d_name>,
116
                        member<DNSRecord, uint16_t, &DNSRecord::d_type>>,
117
          composite_key_compare<std::less<>, std::less<>>>>>;
118

119
    records_t d_records;
120
    vector<ComboAddress> d_servers;
121
    DNSName d_name;
122
    bool d_rdForward{false};
123

124
    bool operator==(const AuthDomain& rhs) const;
125

126
    [[nodiscard]] std::string print(const std::string& indent = "",
127
                                    const std::string& indentLevel = "  ") const;
128

129
    int getRecords(const DNSName& qname, QType qtype, std::vector<DNSRecord>& records) const;
130
    [[nodiscard]] bool isAuth() const
131
    {
2,002✔
132
      return d_servers.empty();
2,002✔
133
    }
2,002✔
134
    [[nodiscard]] bool isForward() const
135
    {
×
136
      return !isAuth();
×
137
    }
×
138
    [[nodiscard]] bool shouldRecurse() const
139
    {
897✔
140
      return d_rdForward;
897✔
141
    }
897✔
142
    [[nodiscard]] const DNSName& getName() const
143
    {
157✔
144
      return d_name;
157✔
145
    }
157✔
146

147
  private:
148
    void addSOA(std::vector<DNSRecord>& records) const;
149
  };
150

151
  using domainmap_t = std::unordered_map<DNSName, AuthDomain>;
152

153
  struct ThreadLocalStorage
154
  {
155
    std::shared_ptr<domainmap_t> domainmap;
156
  };
157

158
  static void setDefaultLogMode(LogMode logmode)
159
  {
156✔
160
    s_lm = logmode;
156✔
161
  }
156✔
162

163
  OptLog LogObject(const string& prefix);
164

165
  static uint64_t doEDNSDump(int fileDesc);
166
  static uint64_t doDumpNSSpeeds(int fileDesc);
167
  static uint64_t doDumpThrottleMap(int fileDesc);
168
  static uint64_t doDumpFailedServers(int fileDesc);
169
  static uint64_t doDumpNonResolvingNS(int fileDesc);
170
  static uint64_t doDumpSavedParentNSSets(int fileDesc);
171
  static uint64_t doDumpDoTProbeMap(int fileDesc);
172

173
  static size_t getNSSpeedTable(size_t maxSize, std::string& ret);
174
  static size_t putIntoNSSpeedTable(const std::string& ret);
175

176
  static int getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth, Logr::log_t);
177
  static void addDontQuery(const std::string& mask)
178
  {
336✔
179
    if (!s_dontQuery) {
336✔
180
      s_dontQuery = std::make_unique<NetmaskGroup>();
16✔
181
    }
16✔
182
    s_dontQuery->addMask(mask);
336✔
183
  }
336✔
184
  static void addDontQuery(const Netmask& mask)
185
  {
2✔
186
    if (!s_dontQuery) {
2!
187
      s_dontQuery = std::make_unique<NetmaskGroup>();
2✔
188
    }
2✔
189
    s_dontQuery->addMask(mask);
2✔
190
  }
2✔
191
  static void clearDontQuery()
192
  {
612✔
193
    s_dontQuery = nullptr;
612✔
194
  }
612✔
195
  static void parseEDNSSubnetAllowlist(const std::string& alist);
196
  static void parseEDNSSubnetAddFor(const std::string& subnetlist);
197
  static void addEDNSLocalSubnet(const std::string& subnet)
198
  {
1,230✔
199
    s_ednslocalsubnets.addMask(subnet);
1,230✔
200
  }
1,230✔
201
  static void addEDNSRemoteSubnet(const std::string& subnet)
202
  {
10✔
203
    s_ednsremotesubnets.addMask(subnet);
10✔
204
  }
10✔
205
  static void addEDNSDomain(const DNSName& domain)
206
  {
16✔
207
    s_ednsdomains.add(domain);
16✔
208
  }
16✔
209
  static void clearEDNSLocalSubnets()
210
  {
618✔
211
    s_ednslocalsubnets.clear();
618✔
212
  }
618✔
213
  static void clearEDNSRemoteSubnets()
214
  {
612✔
215
    s_ednsremotesubnets.clear();
612✔
216
  }
612✔
217
  static void clearEDNSDomains()
218
  {
612✔
219
    s_ednsdomains = SuffixMatchNode();
612✔
220
  }
612✔
221

222
  static void pruneNSSpeeds(time_t limit);
223
  static uint64_t getNSSpeedsSize();
224
  static void submitNSSpeed(const DNSName& server, const ComboAddress& address, int usec, const struct timeval& now);
225
  static void clearNSSpeeds();
226
  static float getNSSpeed(const DNSName& server, const ComboAddress& address);
227

228
  struct EDNSStatus
229
  {
230
    EDNSStatus(const ComboAddress& arg) :
231
      address(arg) {}
459✔
232
    ComboAddress address;
233
    time_t ttd{0};
234
    enum EDNSMode : uint8_t
235
    {
236
      EDNSOK = 0,
237
      EDNSIGNORANT = 1,
238
      NOEDNS = 2
239
    } mode{EDNSOK};
240

241
    [[nodiscard]] std::string toString() const
242
    {
×
243
      const std::array<std::string, 3> modes = {"OK", "Ignorant", "No"};
×
244
      auto umode = static_cast<unsigned int>(mode);
×
245
      if (umode >= modes.size()) {
×
246
        return "?";
×
247
      }
×
248
      return modes.at(umode);
×
249
    }
×
250
  };
251

252
  static EDNSStatus::EDNSMode getEDNSStatus(const ComboAddress& server);
253
  static uint64_t getEDNSStatusesSize();
254
  static void clearEDNSStatuses();
255
  static void pruneEDNSStatuses(time_t cutoff);
256

257
  static uint64_t getThrottledServersSize();
258
  static void pruneThrottledServers(time_t now);
259
  static void clearThrottle();
260
  static bool isThrottled(time_t now, const ComboAddress& server, const DNSName& target, QType qtype);
261
  static bool isThrottled(time_t now, const ComboAddress& server);
262

263
  enum class ThrottleReason : uint8_t
264
  {
265
    None,
266
    ServerDown,
267
    PermanentError,
268
    Timeout,
269
    ParseError,
270
    RCodeServFail,
271
    RCodeRefused,
272
    RCodeOther,
273
    TCPTruncate,
274
    Lame,
275
  };
276
  static void doThrottle(time_t now, const ComboAddress& server, time_t duration, unsigned int tries, ThrottleReason reason);
277
  static void doThrottle(time_t now, const ComboAddress& server, const DNSName& name, QType qtype, time_t duration, unsigned int tries, ThrottleReason reason);
278
  static void unThrottle(const ComboAddress& server, const DNSName& qname, QType qtype);
279

280
  static uint64_t getFailedServersSize();
281
  static void clearFailedServers();
282
  static void pruneFailedServers(time_t cutoff);
283
  static unsigned long getServerFailsCount(const ComboAddress& server);
284

285
  static void clearNonResolvingNS();
286
  static uint64_t getNonResolvingNSSize();
287
  static void pruneNonResolving(time_t cutoff);
288

289
  static void clearSaveParentsNSSets();
290
  static size_t getSaveParentsNSSetsSize();
291
  static void pruneSaveParentsNSSets(time_t now);
292

293
  static void pruneDoTProbeMap(time_t cutoff);
294

295
  static void setDomainMap(std::shared_ptr<domainmap_t> newMap)
296
  {
1,541✔
297
    t_sstorage.domainmap = std::move(newMap);
1,541✔
298
  }
1,541✔
299
  static std::shared_ptr<domainmap_t> getDomainMap()
300
  {
×
301
    return t_sstorage.domainmap;
×
302
  }
×
303
  static bool isRecursiveForward(const DNSName& qname);
304

305
  static void setECSScopeZeroAddress(const Netmask& scopeZeroMask)
306
  {
787✔
307
    s_ecsScopeZero.setSource(scopeZeroMask);
787✔
308
  }
787✔
309

310
  static void clearECSStats()
311
  {
962✔
312
    s_ecsqueries.store(0);
962✔
313
    s_ecsresponses.store(0);
962✔
314

315
    for (size_t idx = 0; idx < 32; idx++) {
31,746✔
316
      SyncRes::s_ecsResponsesBySubnetSize4[idx].store(0);
30,784✔
317
    }
30,784✔
318

319
    for (size_t idx = 0; idx < 128; idx++) {
124,098✔
320
      SyncRes::s_ecsResponsesBySubnetSize6[idx].store(0);
123,136✔
321
    }
123,136✔
322
  }
962✔
323

324
  explicit SyncRes(const struct timeval& now);
325

326
  int beginResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret, unsigned int depth = 0);
327
  bool tryDoT(const DNSName& qname, QType qtype, const DNSName& nsName, ComboAddress address, time_t);
328

329
  void setId(int threadid)
330
  {
3,155✔
331
    if (doLog()) {
3,155✔
332
      d_prefix = "[" + std::to_string(threadid) + "] ";
934✔
333
    }
934✔
334
  }
3,155✔
335

336
  void setId(const string& prefix)
337
  {
15✔
338
    if (doLog()) {
15!
339
      d_prefix = "[" + prefix + "] ";
×
340
    }
×
341
  }
15✔
342

343
  void setLogMode(LogMode logmode)
344
  {
554✔
345
    d_lm = logmode;
554✔
346
  }
554✔
347

348
  bool doLog() const
349
  {
180,930✔
350
    return d_lm != LogNone;
180,930✔
351
  }
180,930✔
352

353
  bool setCacheOnly(bool state = true)
354
  {
88,768✔
355
    bool old = d_cacheonly;
88,768✔
356
    d_cacheonly = state;
88,768✔
357
    return old;
88,768✔
358
  }
88,768✔
359

360
  bool setRefreshAlmostExpired(bool doit)
361
  {
272✔
362
    auto old = d_refresh;
272✔
363
    d_refresh = doit;
272✔
364
    return old;
272✔
365
  }
272✔
366

367
  bool setQNameMinimization(bool state = true)
368
  {
11,086✔
369
    auto old = d_qNameMinimization;
11,086✔
370
    d_qNameMinimization = state;
11,086✔
371
    return old;
11,086✔
372
  }
11,086✔
373

374
  bool setQMFallbackMode(bool state = true)
375
  {
23✔
376
    auto old = d_qNameMinimizationFallbackMode;
23✔
377
    d_qNameMinimizationFallbackMode = state;
23✔
378
    return old;
23✔
379
  }
23✔
380

381
  bool getQMFallbackMode() const
382
  {
48,735✔
383
    return d_qNameMinimizationFallbackMode;
48,735✔
384
  }
48,735✔
385

386
  void setDoEDNS0(bool state = true)
387
  {
802✔
388
    d_doEDNS0 = state;
802✔
389
  }
802✔
390

391
  void setDoDNSSEC(bool state = true)
392
  {
3,700✔
393
    d_doDNSSEC = state;
3,700✔
394
  }
3,700✔
395

396
  void setDNSSECValidationRequested(bool requested = true)
397
  {
3,785✔
398
    d_DNSSECValidationRequested = requested;
3,785✔
399
  }
3,785✔
400

401
  bool isDNSSECValidationRequested() const
402
  {
2,798✔
403
    return d_DNSSECValidationRequested;
2,798✔
404
  }
2,798✔
405

406
  bool shouldValidate() const
407
  {
54,654✔
408
    return d_DNSSECValidationRequested && !d_wasOutOfBand;
54,654✔
409
  }
54,654✔
410

411
  void setWantsRPZ(bool state = true)
412
  {
3,062✔
413
    d_wantsRPZ = state;
3,062✔
414
  }
3,062✔
415

416
  bool getWantsRPZ() const
417
  {
×
418
    return d_wantsRPZ;
×
419
  }
×
420

421
  string getTrace() const
422
  {
24✔
423
    return d_trace.str();
24✔
424
  }
24✔
425

426
  bool getQNameMinimization() const
427
  {
22,822✔
428
    return d_qNameMinimization;
22,822✔
429
  }
22,822✔
430

431
  void setLuaEngine(shared_ptr<RecursorLua4> pdl)
432
  {
438✔
433
    d_pdl = std::move(pdl);
438✔
434
  }
438✔
435

436
  bool wasVariable() const
437
  {
7,452✔
438
    return d_wasVariable;
7,452✔
439
  }
7,452✔
440

441
  bool wasOutOfBand() const
442
  {
3,061✔
443
    return d_wasOutOfBand;
3,061✔
444
  }
3,061✔
445

446
  struct timeval getNow() const
447
  {
3,266✔
448
    return d_now;
3,266✔
449
  }
3,266✔
450

451
  // For debugging purposes
452
  void setNow(const struct timeval& tval)
453
  {
38✔
454
    d_now = tval;
38✔
455
  }
38✔
456

457
  void setQuerySource(const ComboAddress& requestor, const boost::optional<const EDNSSubnetOpts&>& incomingECS);
458
  void setQuerySource(const Netmask& netmask);
459

460
  void setInitialRequestId(const boost::optional<const boost::uuids::uuid&>& initialRequestId)
461
  {
3,150✔
462
    d_initialRequestId = initialRequestId;
3,150✔
463
  }
3,150✔
464

465
  void setOutgoingProtobufServers(std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& servers)
466
  {
3,155✔
467
    d_outgoingProtobufServers = servers;
3,155✔
468
  }
3,155✔
469

470
#ifdef HAVE_FSTRM
471
  void setFrameStreamServers(std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& servers)
472
  {
3,151✔
473
    d_frameStreamServers = servers;
3,151✔
474
  }
3,151✔
475
#endif /* HAVE_FSTRM */
476

477
  void setAsyncCallback(asyncresolve_t func)
478
  {
814✔
479
    d_asyncResolve = std::move(func);
814✔
480
  }
814✔
481

482
  vState getValidationState() const
483
  {
8,664✔
484
    return d_queryValidationState;
8,664✔
485
  }
8,664✔
486

487
  [[nodiscard]] bool getDNSSECLimitHit() const
488
  {
303✔
489
    return d_validationContext.d_limitHit;
303✔
490
  }
303✔
491

492
  void setQueryReceivedOverTCP(bool tcp)
493
  {
3,153✔
494
    d_queryReceivedOverTCP = tcp;
3,153✔
495
  }
3,153✔
496

497
  static bool isUnsupported(QType qtype)
498
  {
8,275✔
499
    auto qcode = qtype.getCode();
8,275✔
500
    // rfc6895 section 3.1, note ANY is 255 and falls outside the range
501
    if (qcode >= QType::rfc6895MetaLowerBound && qcode <= QType::rfc6895MetaUpperBound) {
8,275✔
502
      return true;
18✔
503
    }
18✔
504
    switch (qcode) {
8,257✔
505
      // Internal types
506
    case QType::ENT: // aka TYPE0
×
507
    case QType::ADDR:
×
508
      // RFC
509
    case QType::rfc6895Reserved:
2✔
510
      // Other
511
    case QType::RRSIG:
6✔
512
    case QType::NSEC3: // We use the same logic as for an auth: NSEC is queryable, NSEC3 not
10✔
513
    case QType::OPT:
12✔
514
      return true;
12✔
515
    }
8,257✔
516
    return false;
8,241✔
517
  }
8,257✔
518

519
  static bool answerIsNOData(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records);
520

521
  static thread_local ThreadLocalStorage t_sstorage;
522

523
  static pdns::stat_t s_ecsqueries;
524
  static pdns::stat_t s_ecsresponses;
525
  static std::map<uint8_t, pdns::stat_t> s_ecsResponsesBySubnetSize4;
526
  static std::map<uint8_t, pdns::stat_t> s_ecsResponsesBySubnetSize6;
527

528
  static string s_serverID;
529
  static unsigned int s_minimumTTL;
530
  static unsigned int s_minimumECSTTL;
531
  static unsigned int s_maxqperq;
532
  static unsigned int s_maxnsperresolve;
533
  static unsigned int s_maxnsaddressqperq;
534
  static unsigned int s_maxtotusec;
535
  static unsigned int s_maxdepth;
536
  static unsigned int s_maxnegttl;
537
  static unsigned int s_maxbogusttl;
538
  static unsigned int s_maxcachettl;
539
  static unsigned int s_packetcachettl;
540
  static unsigned int s_packetcacheservfailttl;
541
  static unsigned int s_packetcachenegativettl;
542
  static unsigned int s_serverdownmaxfails;
543
  static unsigned int s_serverdownthrottletime;
544
  static unsigned int s_nonresolvingnsmaxfails;
545
  static unsigned int s_nonresolvingnsthrottletime;
546
  static unsigned int s_unthrottle_n;
547
  static unsigned int s_ecscachelimitttl;
548
  static unsigned int s_maxvalidationsperq;
549
  static unsigned int s_maxnsec3iterationsperq;
550
  static uint8_t s_ecsipv4limit;
551
  static uint8_t s_ecsipv6limit;
552
  static uint8_t s_ecsipv4cachelimit;
553
  static uint8_t s_ecsipv6cachelimit;
554
  static bool s_ecsipv4nevercache;
555
  static bool s_ecsipv6nevercache;
556

557
  static bool s_doIPv4;
558
  static bool s_doIPv6;
559
  static bool s_noEDNSPing;
560
  static bool s_noEDNS;
561
  static bool s_rootNXTrust;
562
  static bool s_qnameminimization;
563
  static HardenNXD s_hardenNXD;
564
  static unsigned int s_refresh_ttlperc;
565
  static unsigned int s_locked_ttlperc;
566
  static int s_tcp_fast_open;
567
  static bool s_tcp_fast_open_connect;
568
  static bool s_dot_to_port_853;
569
  static unsigned int s_max_busy_dot_probes;
570
  static unsigned int s_max_CNAMES_followed;
571
  static unsigned int s_max_minimize_count;
572
  static unsigned int s_minimize_one_label;
573

574
  static const int event_trace_to_pb = 1;
575
  static const int event_trace_to_log = 2;
576
  static const int event_trace_to_ot = 4;
577
  static int s_event_trace_enabled;
578
  static bool s_save_parent_ns_set;
579
  static bool s_addExtendedResolutionDNSErrors;
580

581
  static bool eventTraceEnabled(int flag)
582
  {
6,520✔
583
    return (s_event_trace_enabled & flag) != 0;
6,520✔
584
  }
6,520✔
585
  std::unordered_map<std::string, bool> d_discardedPolicies;
586
  DNSFilterEngine::Policy d_appliedPolicy;
587
  std::unordered_set<std::string> d_policyTags;
588
  boost::optional<string> d_routingTag;
589
  ComboAddress d_fromAuthIP;
590
  RecEventTrace d_eventTrace;
591
  pdns::trace::InitialSpanInfo d_otTrace;
592
  std::shared_ptr<Logr::Logger> d_slog = g_slog->withName("syncres");
593
  boost::optional<EDNSExtendedError> d_extendedError;
594

595
  unsigned int d_authzonequeries;
596
  unsigned int d_outqueries;
597
  unsigned int d_tcpoutqueries;
598
  unsigned int d_dotoutqueries;
599
  unsigned int d_throttledqueries;
600
  unsigned int d_timeouts;
601
  unsigned int d_unreachables;
602
  unsigned int d_totUsec;
603
  unsigned int d_maxdepth{0};
604
  // Initialized ony once, as opposed to d_now which gets updated after outgoing requests
605
  struct timeval d_fixednow;
606

607
private:
608
  ComboAddress d_requestor;
609
  ComboAddress d_cacheRemote;
610

611
  static NetmaskGroup s_ednslocalsubnets;
612
  static NetmaskGroup s_ednsremotesubnets;
613
  static SuffixMatchNode s_ednsdomains;
614
  static EDNSSubnetOpts s_ecsScopeZero;
615
  static LogMode s_lm;
616
  static std::unique_ptr<NetmaskGroup> s_dontQuery;
617

618
  struct GetBestNSAnswer
619
  {
620
    DNSName qname;
621
    set<pair<DNSName, DNSName>> bestns;
622
    uint8_t qtype;
623
    bool operator<(const GetBestNSAnswer& bestAnswer) const
624
    {
16,087✔
625
      return std::tie(qtype, qname, bestns) < std::tie(bestAnswer.qtype, bestAnswer.qname, bestAnswer.bestns);
16,087✔
626
    }
16,087✔
627
  };
628

629
  using zonesStates_t = std::map<DNSName, vState>;
630
  enum StopAtDelegation
631
  {
632
    DontStop,
633
    Stop,
634
    Stopped
635
  };
636

637
  void resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMode mode, std::vector<DNSRecord>& additionals, unsigned int depth, bool& additionalsNotInCache);
638
  void addAdditionals(QType qtype, const vector<DNSRecord>& start, vector<DNSRecord>& additionals, std::set<std::pair<DNSName, QType>>& uniqueCalls, std::set<std::tuple<DNSName, QType, QType>>& uniqueResults, unsigned int depth, unsigned int additionaldepth, bool& additionalsNotInCache);
639
  bool addAdditionals(QType qtype, vector<DNSRecord>& ret, unsigned int depth);
640

641
  void updateQueryCounts(const string& prefix, const DNSName& qname, const ComboAddress& address, bool doTCP, bool doDoT);
642
  static bool doDoTtoAuth(const DNSName& nameServer);
643
  int doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, const DNSName& qname, QType qtype, vector<DNSRecord>& ret,
644
                  unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, Context& context, StopAtDelegation* stopAtDelegation,
645
                  std::map<DNSName, std::vector<ComboAddress>>* fallback);
646
  void ednsStats(boost::optional<Netmask>& ednsmask, const DNSName& qname, const string& prefix);
647
  void incTimeoutStats(const ComboAddress& remoteIP);
648
  void checkTotalTime(const DNSName& qname, QType qtype, boost::optional<EDNSExtendedError>& extendedError) const;
649
  bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool sendRDQuery, bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle = false);
650
  bool processAnswer(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask>& ednsmask, bool sendRDQuery, NsSet& nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state, const ComboAddress& remoteIP);
651

652
  int doResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context);
653
  int doResolveNoQNameMinimization(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context, bool* fromCache = nullptr, StopAtDelegation* stopAtDelegation = nullptr);
654
  bool doOOBResolve(const AuthDomain& domain, const DNSName& qname, QType qtype, vector<DNSRecord>& ret, int& res);
655
  bool doOOBResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res);
656
  static bool isRecursiveForwardOrAuth(const DNSName& qname);
657
  static bool isForwardOrAuth(const DNSName& qname);
658
  static domainmap_t::const_iterator getBestAuthZone(DNSName* qname);
659
  bool doCNAMECacheCheck(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse, bool checkForDups);
660
  bool doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context);
661
  void getBestNSFromCache(const DNSName& qname, QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain = boost::none);
662
  DNSName getBestNSNamesFromCache(const DNSName& qname, QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere);
663

664
  vector<std::pair<DNSName, float>> shuffleInSpeedOrder(const DNSName& qname, NsSet& nameservers, const string& prefix);
665
  vector<ComboAddress> shuffleForwardSpeed(const DNSName& qname, const vector<ComboAddress>& rnameservers, const string& prefix, bool wasRd);
666
  static bool moreSpecificThan(const DNSName& lhs, const DNSName& rhs);
667
  void selectNSOnSpeed(const DNSName& qname, const string& prefix, vector<ComboAddress>& ret);
668
  vector<ComboAddress> getAddrs(const DNSName& qname, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS);
669

670
  bool nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers);
671
  bool nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress&);
672
  void checkMaxQperQ(const DNSName& qname) const;
673
  bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, QType qtype, bool pierceDontQuery);
674

675
  vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<std::pair<DNSName, float>>::const_iterator& tns, unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly, unsigned int& nretrieveAddressesForNS);
676

677
  void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
678
  void sanitizeRecordsPass2(const std::string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, std::unordered_set<DNSName>& allowedAnswerNames, std::unordered_set<DNSName>& allowedAdditionals, bool cnameSeen, bool isNXDomain, bool isNXQType, std::vector<bool>& skipvec, unsigned int& skipCount);
679
  /* This function will check whether the answer should have the AA bit set, and will set if it should be set and isn't.
680
     This is unfortunately needed to deal with very crappy so-called DNS servers */
681
  void fixupAnswer(const std::string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
682
  void rememberParentSetIfNeeded(const DNSName& domain, const vector<DNSRecord>& newRecords, unsigned int depth, const string& prefix);
683
  RCode::rcodes_ updateCacheFromRecords(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>&, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool sendRDQuery, const ComboAddress& remoteIP);
684
  bool processRecords(const std::string& prefix, const DNSName& qname, QType qtype, const DNSName& auth, LWResult& lwr, bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, bool needWildcardProof, bool gatherwildcardProof, unsigned int wildcardLabelsCount, int& rcode, bool& negIndicHasSignatures, unsigned int depth);
685

686
  bool doSpecialNamesResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret);
687

688
  LWResult::Result asyncresolveWrapper(const ComboAddress& address, bool ednsMANDATORY, const DNSName& domain, const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const;
689

690
  boost::optional<Netmask> getEDNSSubnetMask(const DNSName& name, const ComboAddress& rem);
691

692
  static bool validationEnabled();
693
  uint32_t computeLowestTTD(const std::vector<DNSRecord>& records, const MemRecursorCache::SigRecsVec& signatures, uint32_t signaturesTTL, const MemRecursorCache::AuthRecsVec& authorityRecs) const;
694
  void updateValidationState(const DNSName& qname, vState& state, vState stateUpdate, const string& prefix);
695
  vState validateRecordsWithSigs(unsigned int depth, const string& prefix, const DNSName& qname, QType qtype, const DNSName& name, QType type, const std::vector<DNSRecord>& records, const MemRecursorCache::SigRecsVec& signatures);
696
  vState validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const MemRecursorCache::SigRecsVec& signatures, unsigned int depth, const string& prefix);
697
  vState getDNSKeys(const DNSName& signer, skeyset_t& keys, bool& servFailOccurred, unsigned int depth, const string& prefix);
698
  dState getDenialValidationState(const NegCache::NegCacheEntry& negEntry, dState expectedState, bool referralToUnsigned, const string& prefix);
699
  void updateDenialValidationState(const DNSName& qname, vState& neValidationState, const DNSName& neName, vState& state, dState denialState, dState expectedState, bool isDS, unsigned int depth, const string& prefix);
700
  void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& negEntry, const DNSName& qname, QType qtype, int res, vState& state, unsigned int depth, const string& prefix);
701
  vState getTA(const DNSName& zone, dsset_t& dsSet, const string& prefix);
702
  vState getValidationStatus(const DNSName& name, bool wouldBeValid, bool typeIsDS, unsigned int depth, const string& prefix);
703
  void updateValidationStatusInCache(const DNSName& qname, QType qtype, bool aaFlag, vState newState) const;
704
  void initZoneCutsFromTA(const DNSName& from, const string& prefix);
705
  size_t countSupportedDS(const dsset_t& dsSet, const string& prefix);
706

707
  void handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, QType qtype, std::vector<DNSRecord>& ret, int& rcode, unsigned int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state);
708

709
  void handlePolicyHit(const std::string& prefix, const DNSName& qname, QType qtype, vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth);
710
  unsigned int getAdjustedRecursionBound() const;
711

712
  void checkWildcardProof(const DNSName& qname, const QType& qtype, DNSRecord& rec, const LWResult& lwr, vState& state, unsigned int depth, const std::string& prefix, unsigned int wildcardLabelsCount);
713

714
  void setUpdatingRootNS()
715
  {
250✔
716
    d_updatingRootNS = true;
250✔
717
  }
250✔
718

719
  std::string getPrefix(unsigned int depth) const
720
  {
66,134✔
721
    if (!doLog()) {
66,136✔
722
      return "";
59,640✔
723
    }
59,640✔
724
    auto prefix = d_prefix;
2,147,490,136✔
725
    prefix.append(depth, ' ');
2,147,490,136✔
726
    return prefix;
2,147,490,136✔
727
  }
66,134✔
728

729
  zonesStates_t d_cutStates;
730
  ostringstream d_trace;
731
  shared_ptr<RecursorLua4> d_pdl;
732
  boost::optional<Netmask> d_outgoingECSNetwork;
733
  std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> d_outgoingProtobufServers;
734
  std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> d_frameStreamServers;
735
  boost::optional<const boost::uuids::uuid&> d_initialRequestId;
736
  pdns::validation::ValidationContext d_validationContext;
737
  asyncresolve_t d_asyncResolve{nullptr};
738
  // d_now is initialized in the constructor and updates after outgoing requests in lwres.cc:asyncresolve
739
  struct timeval d_now;
740
  /* if the client is asking for a DS that does not exist, we need to provide the SOA along with the NSEC(3) proof
741
     and we might not have it if we picked up the proof from a delegation */
742
  DNSName d_externalDSQuery;
743
  string d_prefix;
744
  vState d_queryValidationState{vState::Indeterminate};
745

746
  /* When d_cacheonly is set to true, we will only check the cache.
747
   * This is set when the RD bit is unset in the incoming query
748
   */
749
  bool d_cacheonly;
750
  bool d_doDNSSEC;
751
  bool d_DNSSECValidationRequested{false};
752
  bool d_doEDNS0{true};
753
  bool d_requireAuthData{true};
754
  bool d_updatingRootNS{false};
755
  bool d_wantsRPZ{true};
756
  bool d_wasOutOfBand{false};
757
  bool d_wasVariable{false};
758
  bool d_qNameMinimization{false};
759
  bool d_qNameMinimizationFallbackMode{false};
760
  bool d_queryReceivedOverTCP{false};
761
  bool d_followCNAME{true};
762
  bool d_refresh{false};
763
  bool d_serveStale{false};
764

765
  LogMode d_lm;
766
};
767

768
/* external functions, opaque to us */
769
LWResult::Result asendtcp(const PacketBuffer& data, shared_ptr<TCPIOHandler>&);
770
LWResult::Result arecvtcp(PacketBuffer& data, size_t len, shared_ptr<TCPIOHandler>&, bool incompleteOkay);
771
void mthreadSleep(unsigned int jitterMsec);
772

773
enum TCPAction : uint8_t
774
{
775
  DoingRead,
776
  DoingWrite
777
};
778

779
struct PacketID
780
{
781
  PacketID()
782
  {
30,408✔
783
    remote.reset();
30,408✔
784
  }
30,408✔
785

786
  ComboAddress remote; // this is the remote
787
  DNSName domain; // this is the question
788

789
  PacketBuffer inMSG; // they'll go here
790
  PacketBuffer outMSG; // the outgoing message that needs to be sent
791

792
  using chain_t = set<std::pair<int, uint16_t>>;
793
  mutable chain_t authReqChain;
794
  shared_ptr<TCPIOHandler> tcphandler{nullptr};
795
  timeval creationTime{};
796
  std::optional<Netmask> ecsSubnet;
797
  string::size_type inPos{0}; // how far are we along in the inMSG
798
  size_t inWanted{0}; // if this is set, we'll read until inWanted bytes are read
799
  string::size_type outPos{0}; // how far we are along in the outMSG
800
  mutable uint32_t nearMisses{0}; // number of near misses - host correct, id wrong
801
  int fd{-1};
802
  int tcpsock{0}; // or wait for an event on a TCP fd
803
  mutable bool closed{false}; // Processing already started, don't accept new chained ids
804
  bool inIncompleteOkay{false};
805
  uint16_t id{0}; // wait for a specific id/remote pair
806
  uint16_t type{0}; // and this is its type
807
  TCPAction highState{TCPAction::DoingRead};
808
  IOState lowState{IOState::NeedRead};
809

810
  bool operator<(const PacketID& /* b */) const
811
  {
×
812
    // We don't want explicit PacketID compare here, but always via predicate classes below
×
813
    assert(0); // NOLINT: lib
×
814
  }
×
815
};
816

817
inline ostream& operator<<(ostream& ostr, const PacketID& pid)
818
{
×
819
  return ostr << "PacketID(id=" << pid.id << ",remote=" << pid.remote.toString() << ",type=" << pid.type << ",tcpsock=" << pid.tcpsock << ",fd=" << pid.fd << ",name=" << pid.domain << ",ecs=" << (pid.ecsSubnet ? pid.ecsSubnet->toString() : "") << ')';
×
820
}
×
821

822
inline ostream& operator<<(ostream& ostr, const shared_ptr<PacketID>& pid)
823
{
×
824
  return ostr << *pid;
×
825
}
×
826

827
/*
828
 * The two compare predicates below must be consistent!
829
 * PacketIDBirthdayCompare can omit minor fields, but not change the or skip fields
830
 * order! See boost docs on CompatibleCompare.
831
 */
832
struct PacketIDCompare
833
{
834
  bool operator()(const std::shared_ptr<PacketID>& lhs, const std::shared_ptr<PacketID>& rhs) const
835
  {
241,065✔
836
    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) < std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
241,065✔
837
      return true;
75,750✔
838
    }
75,750✔
839
    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) > std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
165,315✔
840
      return false;
45,210✔
841
    }
45,210✔
842

843
    return std::tie(lhs->domain, lhs->fd, lhs->id) < std::tie(rhs->domain, rhs->fd, rhs->id);
120,105✔
844
  }
165,315✔
845
};
846

847
struct PacketIDBirthdayCompare
848
{
849
  bool operator()(const std::shared_ptr<PacketID>& lhs, const std::shared_ptr<PacketID>& rhs) const
850
  {
56,593✔
851
    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) < std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
56,593✔
852
      return true;
24,421✔
853
    }
24,421✔
854
    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) > std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
32,172✔
855
      return false;
10,408✔
856
    }
10,408✔
857
    return lhs->domain < rhs->domain;
21,764✔
858
  }
32,172✔
859
};
860
extern std::unique_ptr<MemRecursorCache> g_recCache;
861

862
extern rec::GlobalCounters g_Counters;
863
extern thread_local rec::TCounters t_Counters;
864

865
//! represents a running TCP/IP client session
866
class TCPConnection
867
{
868
public:
869
  TCPConnection(int fileDesc, const ComboAddress& addr);
870
  ~TCPConnection();
871
  TCPConnection(const TCPConnection&) = delete;
872
  TCPConnection& operator=(const TCPConnection&) = delete;
873
  TCPConnection(TCPConnection&&) = delete;
874
  TCPConnection& operator=(TCPConnection&&) = delete;
875

876
  [[nodiscard]] int getFD() const
877
  {
4,547✔
878
    return d_fd;
4,547✔
879
  }
4,547✔
880
  void setDropOnIdle()
881
  {
3✔
882
    d_dropOnIdle = true;
3✔
883
  }
3✔
884
  [[nodiscard]] bool isDropOnIdle() const
885
  {
850✔
886
    return d_dropOnIdle;
850✔
887
  }
850✔
888

889
  // The max number of concurrent TCP requests we're willing to process
890
  static uint16_t s_maxInFlight;
891
  static unsigned int getCurrentConnections() { return s_currentConnections; }
542✔
892

893
  std::vector<ProxyProtocolValue> proxyProtocolValues;
894
  std::string data;
895
  ComboAddress d_remote;
896
  ComboAddress d_source;
897
  ComboAddress d_destination;
898
  ComboAddress d_mappedSource;
899
  size_t queriesCount{0};
900
  size_t proxyProtocolGot{0};
901
  ssize_t proxyProtocolNeed{0};
902
  enum stateenum
903
  {
904
    PROXYPROTOCOLHEADER,
905
    BYTE0,
906
    BYTE1,
907
    GETQUESTION,
908
    DONE
909
  } state{BYTE0};
910
  uint16_t qlen{0};
911
  uint16_t bytesread{0};
912
  uint16_t d_requestsInFlight{0}; // number of mthreads spawned for this connection
913

914
private:
915
  int d_fd;
916
  static std::atomic<uint32_t> s_currentConnections; //!< total number of current TCP connections
917
  bool d_dropOnIdle{false};
918
};
919

920
class ImmediateServFailException
921
{
922
public:
923
  ImmediateServFailException(string reason_) :
924
    reason(std::move(reason_)) {};
48✔
925

926
  string reason; //! Print this to tell the user what went wrong
927
};
928

929
class PolicyHitException
930
{
931
};
932

933
class ImmediateQueryDropException
934
{
935
};
936

937
class SendTruncatedAnswerException
938
{
939
};
940

941
using addrringbuf_t = boost::circular_buffer<ComboAddress>;
942
extern thread_local std::unique_ptr<addrringbuf_t> t_servfailremotes, t_largeanswerremotes, t_remotes, t_bogusremotes, t_timeouts;
943

944
extern thread_local std::unique_ptr<boost::circular_buffer<pair<DNSName, uint16_t>>> t_queryring, t_servfailqueryring, t_bogusqueryring;
945
extern thread_local std::shared_ptr<NetmaskGroup> t_allowFrom;
946
extern thread_local std::shared_ptr<NetmaskGroup> t_allowNotifyFrom;
947
extern unsigned int g_networkTimeoutMsec;
948
extern uint16_t g_outgoingEDNSBufsize;
949
extern std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries;
950
extern bool g_lowercaseOutgoing;
951

952
using pipefunc_t = std::function<void*()>;
953
void broadcastFunction(const pipefunc_t& func);
954
void distributeAsyncFunction(const std::string& packet, const pipefunc_t& func);
955

956
int directResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, Logr::log_t);
957
int directResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, bool qnamemin, Logr::log_t);
958
int followCNAMERecords(std::vector<DNSRecord>& ret, QType qtype, int rcode);
959
int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret);
960
int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret);
961

962
template <class T>
963
T broadcastAccFunction(const std::function<T*()>& func);
964

965
using notifyset_t = std::unordered_set<DNSName>;
966
std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration(bool yaml);
967
void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> allowNotifyFor);
968

969
uint64_t* pleaseGetNsSpeedsSize();
970
uint64_t* pleaseGetFailedServersSize();
971
uint64_t* pleaseGetConcurrentQueries();
972
uint64_t* pleaseGetThrottleSize();
973
void doCarbonDump(void*);
974
bool primeHints(time_t now = time(nullptr));
975

976
using timebuf_t = std::array<char, 64>;
977
const char* isoDateTimeMillis(const struct timeval& tval, timebuf_t& buf);
978

979
struct WipeCacheResult
980
{
981
  int record_count = 0;
982
  int negative_record_count = 0;
983
  int packet_count = 0;
984
};
985

986
struct WipeCacheResult wipeCaches(const DNSName& canon, bool subtree, uint16_t qtype);
987

988
extern __thread struct timeval g_now;
989

990
struct ThreadTimes
991
{
992
  uint64_t msec{0};
993
  vector<uint64_t> times;
994
  ThreadTimes& operator+=(const ThreadTimes& rhs)
995
  {
384✔
996
    times.push_back(rhs.msec);
384✔
997
    return *this;
384✔
998
  }
384✔
999
};
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