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

PowerDNS / pdns / 17129807640

21 Aug 2025 02:23PM UTC coverage: 65.961% (+0.008%) from 65.953%
17129807640

Pull #16013

github

web-flow
Merge 7b9d23664 into 5566651aa
Pull Request #16013: update keyblocks with non-SHA1 signing keys

42083 of 92400 branches covered (45.54%)

Branch coverage included in aggregate %.

127968 of 165404 relevant lines covered (77.37%)

5605035.52 hits per line

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

66.02
/pdns/recursordist/aggressive_nsec.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
#include <cinttypes>
23
#include <climits>
24

25
#include "aggressive_nsec.hh"
26
#include "cachecleaner.hh"
27
#include "recursor_cache.hh"
28
#include "logger.hh"
29
#include "validate.hh"
30

31
std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache{nullptr};
32
uint64_t AggressiveNSECCache::s_nsec3DenialProofMaxCost{0};
33
uint8_t AggressiveNSECCache::s_maxNSEC3CommonPrefix = AggressiveNSECCache::s_default_maxNSEC3CommonPrefix;
34

35
/* this is defined in syncres.hh and we are not importing that here */
36
extern std::unique_ptr<MemRecursorCache> g_recCache;
37

38
std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>> AggressiveNSECCache::getBestZone(const DNSName& zone)
39
{
15,302✔
40
  std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>> entry{nullptr};
15,302✔
41
  {
15,302✔
42
    auto zones = d_zones.try_read_lock();
15,302✔
43
    if (!zones.owns_lock()) {
15,302✔
44
      return entry;
1✔
45
    }
1✔
46

47
    auto got = zones->lookup(zone);
15,301✔
48
    if (got) {
15,301✔
49
      return *got;
7,412✔
50
    }
7,412✔
51
  }
15,301✔
52
  return entry;
7,889✔
53
}
15,301✔
54

55
std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>> AggressiveNSECCache::getZone(const DNSName& zone)
56
{
43,031✔
57
  {
43,031✔
58
    auto zones = d_zones.read_lock();
43,031✔
59
    auto got = zones->lookup(zone);
43,031✔
60
    if (got && *got) {
43,031!
61
      auto locked = (*got)->lock();
42,797✔
62
      if (locked->d_zone == zone) {
42,797✔
63
        return *got;
42,795✔
64
      }
42,795✔
65
    }
42,797✔
66
  }
43,031✔
67

68
  auto entry = std::make_shared<LockGuarded<ZoneEntry>>(zone);
236✔
69

70
  {
236✔
71
    auto zones = d_zones.write_lock();
236✔
72
    /* it might have been inserted in the mean time */
73
    auto got = zones->lookup(zone);
236✔
74
    if (got && *got) {
236!
75
      auto locked = (*got)->lock();
2✔
76
      if (locked->d_zone == zone) {
2!
77
        return *got;
×
78
      }
×
79
    }
2✔
80
    zones->add(zone, std::shared_ptr<LockGuarded<ZoneEntry>>(entry));
236✔
81
    return entry;
236✔
82
  }
236✔
83
}
236✔
84

85
void AggressiveNSECCache::updateEntriesCount(SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& zones)
86
{
592✔
87
  uint64_t counter = 0;
592✔
88
  zones.visit([&counter](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
592✔
89
    if (node.d_value) {
2!
90
      counter += node.d_value->lock()->d_entries.size();
2✔
91
    }
2✔
92
  });
2✔
93
  d_entriesCount = counter;
592✔
94
}
592✔
95

96
void AggressiveNSECCache::removeZoneInfo(const DNSName& zone, bool subzones)
97
{
594✔
98
  auto zones = d_zones.write_lock();
594✔
99

100
  if (subzones) {
594✔
101
    zones->remove(zone, true);
592✔
102
    updateEntriesCount(*zones);
592✔
103
  }
592✔
104
  else {
2✔
105
    auto got = zones->lookup(zone);
2✔
106
    if (!got || !*got) {
2!
107
      return;
×
108
    }
×
109

110
    /* let's increase the ref count of the shared pointer
111
       so we get the lock, remove the zone from the tree,
112
       then release the lock before the entry is deleted */
113
    auto entry = *got;
2✔
114
    {
2✔
115
      auto locked = (*got)->lock();
2✔
116
      if (locked->d_zone != zone) {
2!
117
        return;
×
118
      }
×
119
      auto removed = locked->d_entries.size();
2✔
120
      zones->remove(zone, false);
2✔
121
      d_entriesCount -= removed;
2✔
122
    }
2✔
123
  }
2✔
124
}
594✔
125

126
void AggressiveNSECCache::prune(time_t now)
127
{
140✔
128
  uint64_t maxNumberOfEntries = d_maxEntries;
140✔
129
  std::vector<DNSName> emptyEntries;
140✔
130
  uint64_t erased = 0;
140✔
131

132
  auto zones = d_zones.write_lock();
140✔
133
  // To start, just look through 10% of each zone and nuke everything that is expired
134
  zones->visit([now, &erased, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
183✔
135
    if (!node.d_value) {
90!
136
      return;
×
137
    }
×
138

139
    auto zoneEntry = node.d_value->lock();
90✔
140
    auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries);
90✔
141
    const auto toLookAtForThisZone = (zoneEntry->d_entries.size() + 9) / 10;
90✔
142
    uint64_t lookedAt = 0;
90✔
143
    for (auto it = sidx.begin(); it != sidx.end() && lookedAt < toLookAtForThisZone; ++lookedAt) {
127✔
144
      if (it->d_ttd <= now) {
37✔
145
        it = sidx.erase(it);
6✔
146
        ++erased;
6✔
147
      }
6✔
148
      else {
31✔
149
        ++it;
31✔
150
      }
31✔
151
    }
37✔
152

153
    if (zoneEntry->d_entries.empty()) {
90✔
154
      emptyEntries.push_back(zoneEntry->d_zone);
57✔
155
    }
57✔
156
  });
90✔
157

158
  d_entriesCount -= erased;
140✔
159

160
  // If we are still above try harder by nuking entries from each zone in LRU order
161
  auto entriesCount = d_entriesCount.load();
140✔
162
  if (entriesCount > maxNumberOfEntries) {
140!
163
    erased = 0;
×
164
    uint64_t toErase = entriesCount - maxNumberOfEntries;
×
165
    zones->visit([&erased, &toErase, &entriesCount, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
×
166
      if (!node.d_value || entriesCount == 0) {
×
167
        return;
×
168
      }
×
169
      auto zoneEntry = node.d_value->lock();
×
170
      const auto zoneSize = zoneEntry->d_entries.size();
×
171
      auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries);
×
172
      const auto toTrimForThisZone = static_cast<uint64_t>(std::round(static_cast<double>(toErase) * static_cast<double>(zoneSize) / static_cast<double>(entriesCount)));
×
173
      if (entriesCount < zoneSize) {
×
174
        throw std::runtime_error("Inconsistent agggressive cache " + std::to_string(entriesCount) + " " + std::to_string(zoneSize));
×
175
      }
×
176
      // This is comparable to what cachecleaner.hh::pruneMutexCollectionsVector() is doing, look there for an explanation
177
      entriesCount -= zoneSize;
×
178
      uint64_t trimmedFromThisZone = 0;
×
179
      for (auto it = sidx.begin(); it != sidx.end() && trimmedFromThisZone < toTrimForThisZone;) {
×
180
        it = sidx.erase(it);
×
181
        ++erased;
×
182
        ++trimmedFromThisZone;
×
183
        if (--toErase == 0) {
×
184
          break;
×
185
        }
×
186
      }
×
187
      if (zoneEntry->d_entries.empty()) {
×
188
        emptyEntries.push_back(zoneEntry->d_zone);
×
189
      }
×
190
    });
×
191

192
    d_entriesCount -= erased;
×
193
  }
×
194

195
  if (!emptyEntries.empty()) {
140✔
196
    for (const auto& entry : emptyEntries) {
57✔
197
      zones->remove(entry);
57✔
198
    }
57✔
199
  }
19✔
200
}
140✔
201

202
static bool isMinimallyCoveringNSEC(const DNSName& owner, const std::shared_ptr<const NSECRecordContent>& nsec)
203
{
756✔
204
  /* this test only covers Cloudflare's ones (https://blog.cloudflare.com/black-lies/),
205
     we might need to cover more cases described in rfc4470 as well, but the name generation algorithm
206
     is not clearly defined there */
207
  const auto& storage = owner.getStorage();
756✔
208
  const auto& nextStorage = nsec->d_next.getStorage();
756✔
209

210
  // is the next name at least two octets long?
211
  if (nextStorage.size() <= 2 || storage.size() != (nextStorage.size() - 2)) {
756!
212
    return false;
539✔
213
  }
539✔
214

215
  // does the next name start with a one-octet long label containing a zero, i.e. `\000`?
216
  if (nextStorage.at(0) != 1 || static_cast<uint8_t>(nextStorage.at(1)) != static_cast<uint8_t>(0)) {
217!
217
    return false;
217✔
218
  }
217✔
219

220
  // is the rest of the next name identical to the owner name, i.e. is the next name the owner name prefixed by '\000.'?
221
  if (nextStorage.compare(2, nextStorage.size() - 2, storage) != 0) {
×
222
    return false;
×
223
  }
×
224

225
  return true;
×
226
}
×
227

228
static bool commonPrefixIsLong(const string& one, const string& two, size_t bound)
229
{
40,145✔
230
  size_t length = 0;
40,145✔
231
  const auto minLength = std::min(one.length(), two.length());
40,145✔
232

233
  for (size_t i = 0; i < minLength; i++) {
41,060!
234
    const auto byte1 = one.at(i);
41,060✔
235
    const auto byte2 = two.at(i);
41,060✔
236
    // shortcut
237
    if (byte1 == byte2) {
41,060✔
238
      length += CHAR_BIT;
917✔
239
      if (length > bound) {
917✔
240
        return true;
2✔
241
      }
2✔
242
      continue;
915✔
243
    }
917✔
244
    // bytes differ, let's look at the bits
245
    for (ssize_t j = CHAR_BIT - 1; j >= 0; j--) {
40,529!
246
      const auto bit1 = byte1 & (1 << j);
40,529✔
247
      const auto bit2 = byte2 & (1 << j);
40,529✔
248
      if (bit1 != bit2) {
40,529✔
249
        return length > bound;
40,139✔
250
      }
40,139✔
251
      length++;
390✔
252
      if (length > bound) {
390✔
253
        return true;
4✔
254
      }
4✔
255
    }
390✔
256
  }
40,143✔
257
  return length > bound;
×
258
}
40,145✔
259

260
// If the NSEC3 hashes have a long common prefix, they deny only a small subset of all possible hashes
261
// So don't take the trouble to store those.
262
bool AggressiveNSECCache::isSmallCoveringNSEC3(const DNSName& owner, const std::string& nextHash)
263
{
40,151✔
264
  std::string ownerHash(fromBase32Hex(owner.getRawLabel(0)));
40,151✔
265
  // Special case: empty zone, so the single NSEC3 covers everything. Prefix is long but we still want it cached.
266
  if (ownerHash == nextHash) {
40,151✔
267
    return false;
6✔
268
  }
6✔
269
  return commonPrefixIsLong(ownerHash, nextHash, AggressiveNSECCache::s_maxNSEC3CommonPrefix);
40,145✔
270
}
40,151✔
271

272
void AggressiveNSECCache::insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, bool nsec3, const DNSName& qname, QType qtype)
273
{
43,031✔
274
  if (nsec3 && nsec3Disabled()) {
43,031!
275
    return;
×
276
  }
×
277
  if (signatures.empty()) {
43,031!
278
    return;
×
279
  }
×
280

281
  std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>> entry = getZone(zone);
43,031✔
282
  {
43,031✔
283
    auto zoneEntry = entry->lock();
43,031✔
284
    if (nsec3 && !zoneEntry->d_nsec3) {
43,031✔
285
      d_entriesCount -= zoneEntry->d_entries.size();
129✔
286
      zoneEntry->d_entries.clear();
129✔
287
      zoneEntry->d_nsec3 = true;
129✔
288
    }
129✔
289

290
    DNSName next;
43,031✔
291
    if (!nsec3) {
43,031✔
292
      auto content = getRR<NSECRecordContent>(record);
756✔
293
      if (!content) {
756!
294
        throw std::runtime_error("Error getting the content from a NSEC record");
×
295
      }
×
296

297
      next = content->d_next;
756✔
298
      if (next.canonCompare(owner) && next != zone) {
756!
299
        /* not accepting a NSEC whose next domain name is before the owner
300
           unless the next domain name is the apex, sorry */
301
        return;
×
302
      }
×
303

304
      if (isMinimallyCoveringNSEC(owner, content)) {
756!
305
        /* not accepting minimally covering answers since they only deny one name */
306
        return;
×
307
      }
×
308
    }
756✔
309
    else {
42,275✔
310
      auto content = getRR<NSEC3RecordContent>(record);
42,275✔
311
      if (!content) {
42,275!
312
        throw std::runtime_error("Error getting the content from a NSEC3 record");
×
313
      }
×
314

315
      if (content->isOptOut()) {
42,275✔
316
        /* doesn't prove anything, sorry */
317
        return;
2,144✔
318
      }
2,144✔
319

320
      if (g_maxNSEC3Iterations && content->d_iterations > g_maxNSEC3Iterations) {
40,131!
321
        /* can't use that */
322
        return;
×
323
      }
×
324

325
      if (isSmallCoveringNSEC3(owner, content->d_nexthash)) {
40,131✔
326
        /* not accepting small covering answers since they only deny a small subset */
327
        return;
2✔
328
      }
2✔
329

330
      // XXX: Ponder storing everything in raw form, without the zone instead. It still needs to be a DNSName for NSEC, though,
331
      // but doing the conversion on cache hits only might be faster
332
      next = DNSName(toBase32Hex(content->d_nexthash)) + zone;
40,129✔
333

334
      if (zoneEntry->d_iterations != content->d_iterations || zoneEntry->d_salt != content->d_salt) {
40,129✔
335
        zoneEntry->d_iterations = content->d_iterations;
73✔
336
        zoneEntry->d_salt = content->d_salt;
73✔
337

338
        // Clearing the existing entries since we can't use them, and it's likely a rollover
339
        // If it instead is different servers using different parameters, well, too bad.
340
        d_entriesCount -= zoneEntry->d_entries.size();
73✔
341
        zoneEntry->d_entries.clear();
73✔
342
      }
73✔
343
    }
40,129✔
344

345
    /* the TTL is already a TTD by now */
346
    if (!nsec3 && isWildcardExpanded(owner.countLabels(), *signatures.at(0))) {
40,885✔
347
      DNSName realOwner = getNSECOwnerName(owner, signatures);
385✔
348
      auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, realOwner, next, qname, record.d_ttl, qtype});
385✔
349
      if (pair.second) {
385✔
350
        ++d_entriesCount;
11✔
351
      }
11✔
352
      else {
374✔
353
        zoneEntry->d_entries.replace(pair.first, {record.getContent(), signatures, std::move(realOwner), std::move(next), qname, record.d_ttl, qtype});
374✔
354
      }
374✔
355
    }
385✔
356
    else {
40,500✔
357
      auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, owner, next, qname, record.d_ttl, qtype});
40,500✔
358
      if (pair.second) {
40,500✔
359
        ++d_entriesCount;
20,268✔
360
      }
20,268✔
361
      else {
20,232✔
362
        zoneEntry->d_entries.replace(pair.first, {record.getContent(), signatures, owner, std::move(next), qname, record.d_ttl, qtype});
20,232✔
363
      }
20,232✔
364
    }
40,500✔
365
  }
40,885✔
366
}
40,885✔
367

368
bool AggressiveNSECCache::getNSECBefore(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zone, const DNSName& name, ZoneEntry::CacheEntry& entry)
369
{
223✔
370
  auto zoneEntry = zone->try_lock();
223✔
371
  if (!zoneEntry.owns_lock() || zoneEntry->d_entries.empty()) {
223!
372
    return false;
×
373
  }
×
374

375
  auto& idx = zoneEntry->d_entries.get<ZoneEntry::OrderedTag>();
223✔
376
  auto it = idx.lower_bound(name);
223✔
377
  bool end = false;
223✔
378
  bool wrapped = false;
223✔
379

380
  if (it == idx.begin() && it->d_owner != name) {
223✔
381
    it = idx.end();
24✔
382
    // we know the map is not empty
383
    it--;
24✔
384
    // might be that owner > name && name < next
385
    // can't go further, but perhaps we wrapped?
386
    wrapped = true;
24✔
387
  }
24✔
388

389
  while (!end && !wrapped && (it == idx.end() || (it->d_owner != name && !it->d_owner.canonCompare(name)))) {
378!
390
    if (it == idx.begin()) {
155!
391
      end = true;
×
392
      break;
×
393
    }
×
394
    else {
155✔
395
      it--;
155✔
396
    }
155✔
397
  }
155✔
398

399
  if (end) {
223!
400
    return false;
×
401
  }
×
402

403
  auto firstIndexIterator = zoneEntry->d_entries.project<ZoneEntry::OrderedTag>(it);
223✔
404
  if (it->d_ttd <= now) {
223!
405
    moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
×
406
    return false;
×
407
  }
×
408

409
  entry = *it;
223✔
410
  moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
223✔
411
  return true;
223✔
412
}
223✔
413

414
bool AggressiveNSECCache::getNSEC3(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zone, const DNSName& name, ZoneEntry::CacheEntry& entry)
415
{
155✔
416
  auto zoneEntry = zone->try_lock();
155✔
417
  if (!zoneEntry.owns_lock() || zoneEntry->d_entries.empty()) {
155!
418
    return false;
×
419
  }
×
420

421
  auto& idx = zoneEntry->d_entries.get<ZoneEntry::HashedTag>();
155✔
422
  auto entries = idx.equal_range(name);
155✔
423

424
  for (auto it = entries.first; it != entries.second; ++it) {
155✔
425

426
    if (it->d_owner != name) {
66!
427
      continue;
×
428
    }
×
429

430
    auto firstIndexIterator = zoneEntry->d_entries.project<ZoneEntry::OrderedTag>(it);
66✔
431
    if (it->d_ttd <= now) {
66!
432
      moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
×
433
      return false;
×
434
    }
×
435

436
    entry = *it;
66✔
437
    moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
66✔
438
    return true;
66✔
439
  }
66✔
440

441
  return false;
89✔
442
}
155✔
443

444
static void addToRRSet(const time_t now, std::vector<DNSRecord>& recordSet, const MemRecursorCache::SigRecs& signatures, const DNSName& owner, bool doDNSSEC, std::vector<DNSRecord>& ret, DNSResourceRecord::Place place = DNSResourceRecord::AUTHORITY)
445
{
78✔
446
  uint32_t ttl = 0;
78✔
447

448
  for (auto& record : recordSet) {
78✔
449
    if (record.d_class != QClass::IN) {
78!
450
      continue;
×
451
    }
×
452

453
    record.d_ttl -= now;
78✔
454
    record.d_name = owner;
78✔
455
    ttl = record.d_ttl;
78✔
456
    record.d_place = place;
78✔
457
    ret.push_back(std::move(record));
78✔
458
  }
78✔
459

460
  if (doDNSSEC) {
78!
461
    for (const auto& signature : *signatures) {
78✔
462
      DNSRecord dr;
44✔
463
      dr.d_type = QType::RRSIG;
44✔
464
      dr.d_name = owner;
44✔
465
      dr.d_ttl = ttl;
44✔
466
      dr.setContent(signature);
44✔
467
      dr.d_place = place;
44✔
468
      dr.d_class = QClass::IN;
44✔
469
      ret.push_back(std::move(dr));
44✔
470
    }
44✔
471
  }
78✔
472
}
78✔
473

474
static void addRecordToRRSet(const DNSName& owner, const QType& type, uint32_t ttl, std::shared_ptr<const DNSRecordContent>& content, std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures, bool doDNSSEC, std::vector<DNSRecord>& ret)
475
{
114✔
476
  DNSRecord nsecRec;
114✔
477
  nsecRec.d_type = type.getCode();
114✔
478
  nsecRec.d_name = owner;
114✔
479
  nsecRec.d_ttl = ttl;
114✔
480
  nsecRec.setContent(std::move(content));
114✔
481
  nsecRec.d_place = DNSResourceRecord::AUTHORITY;
114✔
482
  nsecRec.d_class = QClass::IN;
114✔
483
  ret.push_back(std::move(nsecRec));
114✔
484

485
  if (doDNSSEC) {
114!
486
    for (auto& signature : signatures) {
114✔
487
      DNSRecord dr;
114✔
488
      dr.d_type = QType::RRSIG;
114✔
489
      dr.d_name = owner;
114✔
490
      dr.d_ttl = ttl;
114✔
491
      dr.setContent(std::move(signature));
114✔
492
      dr.d_place = DNSResourceRecord::AUTHORITY;
114✔
493
      dr.d_class = QClass::IN;
114✔
494
      ret.push_back(std::move(dr));
114✔
495
    }
114✔
496
  }
114✔
497
}
114✔
498

499
bool AggressiveNSECCache::synthesizeFromNSEC3Wildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nextCloser, const DNSName& wildcardName, const OptLog& log)
500
{
3✔
501
  vState cachedState;
3✔
502

503
  std::vector<DNSRecord> wcSet;
3✔
504
  MemRecursorCache::SigRecs wcSignatures = MemRecursorCache::s_emptySigRecs;
3✔
505

506
  if (g_recCache->get(now, wildcardName, type, MemRecursorCache::RequireAuth, &wcSet, ComboAddress("127.0.0.1"), boost::none, doDNSSEC ? &wcSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
3!
507
    VLOG(log, name << ": Unfortunately we don't have a valid entry for " << wildcardName << ", so we cannot synthesize from that wildcard" << endl);
×
508
    return false;
×
509
  }
×
510

511
  addToRRSet(now, wcSet, std::move(wcSignatures), name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
3✔
512
  /* no need for closest encloser proof, the wildcard is there */
513
  // coverity[store_truncates_time_t]
514
  addRecordToRRSet(nextCloser.d_owner, QType::NSEC3, nextCloser.d_ttd - now, nextCloser.d_record, nextCloser.d_signatures, doDNSSEC, ret);
3✔
515
  /* and of course we won't deny the wildcard either */
516

517
  VLOG(log, name << ": Synthesized valid answer from NSEC3s and wildcard!" << endl);
3!
518
  ++d_nsec3WildcardHits;
3✔
519
  res = RCode::NoError;
3✔
520
  return true;
3✔
521
}
3✔
522

523
bool AggressiveNSECCache::synthesizeFromNSECWildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nsec, const DNSName& wildcardName, const OptLog& log)
524
{
3✔
525
  vState cachedState;
3✔
526

527
  std::vector<DNSRecord> wcSet;
3✔
528
  MemRecursorCache::SigRecs wcSignatures = MemRecursorCache::s_emptySigRecs;
3✔
529

530
  if (g_recCache->get(now, wildcardName, type, MemRecursorCache::RequireAuth, &wcSet, ComboAddress("127.0.0.1"), boost::none, doDNSSEC ? &wcSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
3!
531
    VLOG(log, name << ": Unfortunately we don't have a valid entry for " << wildcardName << ", so we cannot synthesize from that wildcard" << endl);
×
532
    return false;
×
533
  }
×
534

535
  addToRRSet(now, wcSet, wcSignatures, name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
3✔
536
  // coverity[store_truncates_time_t]
537
  addRecordToRRSet(nsec.d_owner, QType::NSEC, nsec.d_ttd - now, nsec.d_record, nsec.d_signatures, doDNSSEC, ret);
3✔
538

539
  VLOG(log, name << ": Synthesized valid answer from NSECs and wildcard!" << endl);
3!
540
  ++d_nsecWildcardHits;
3✔
541
  res = RCode::NoError;
3✔
542
  return true;
3✔
543
}
3✔
544

545
bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, const MemRecursorCache::SigRecs& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog& log, pdns::validation::ValidationContext& validationContext)
546
{
82✔
547
  DNSName zone;
82✔
548
  std::string salt;
82✔
549
  uint16_t iterations;
82✔
550

551
  {
82✔
552
    auto entry = zoneEntry->try_lock();
82✔
553
    if (!entry.owns_lock()) {
82!
554
      return false;
×
555
    }
×
556
    salt = entry->d_salt;
82✔
557
    zone = entry->d_zone;
82✔
558
    iterations = entry->d_iterations;
82✔
559
  }
82✔
560

561
  const auto zoneLabelsCount = zone.countLabels();
×
562
  if (s_nsec3DenialProofMaxCost != 0) {
82✔
563
    const auto worstCaseIterations = getNSEC3DenialProofWorstCaseIterationsCount(name.countLabels() - zoneLabelsCount, iterations, salt.length());
45✔
564
    if (worstCaseIterations > s_nsec3DenialProofMaxCost) {
45✔
565
      // skip NSEC3 aggressive cache for expensive NSEC3 parameters: "if you want us to take the pain of PRSD away from you, you need to make it cheap for us to do so"
566
      VLOG(log, name << ": Skipping aggressive use of the NSEC3 cache since the zone parameters are too expensive" << endl);
4!
567
      return false;
4✔
568
    }
4✔
569
  }
45✔
570

571
  auto nameHash = DNSName(toBase32Hex(getHashFromNSEC3(name, iterations, salt, validationContext))) + zone;
78✔
572

573
  ZoneEntry::CacheEntry exactNSEC3;
78✔
574
  if (getNSEC3(now, zoneEntry, nameHash, exactNSEC3)) {
78✔
575
    VLOG(log, name << ": Found a direct NSEC3 match for " << nameHash << " inserted by " << exactNSEC3.d_qname << '/' << exactNSEC3.d_qtype);
23!
576
    auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(exactNSEC3.d_record);
23✔
577
    if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) {
23!
578
      VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl);
×
579
      return false;
×
580
    }
×
581

582
    if (!isTypeDenied(*nsec3, type)) {
23✔
583
      VLOG_NO_PREFIX(log, " but the requested type (" << type.toString() << ") does exist" << endl);
2!
584
      return false;
2✔
585
    }
2✔
586

587
    const DNSName signer = getSigner(exactNSEC3.d_signatures);
21✔
588
    /* here we need to allow an ancestor NSEC3 proving that a DS does not exist as it is an
589
       exact match for the name */
590
    if (type != QType::DS && isNSEC3AncestorDelegation(signer, exactNSEC3.d_owner, *nsec3)) {
21✔
591
      /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
592
         Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
593
         nonexistence of any RRs below that zone cut, which include all RRs at
594
         that (original) owner name other than DS RRs, and all RRs below that
595
         owner name regardless of type.
596
      */
597
      VLOG_NO_PREFIX(log, " but this is an ancestor delegation NSEC3" << endl);
2!
598
      return false;
2✔
599
    }
2✔
600

601
    if (type == QType::DS && !name.isRoot() && signer == name) {
19!
602
      VLOG_NO_PREFIX(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS");
2!
603
      return false;
2✔
604
    }
2✔
605

606
    VLOG_NO_PREFIX(log, ": done!" << endl);
17!
607
    ++d_nsec3Hits;
17✔
608
    res = RCode::NoError;
17✔
609
    addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret);
17✔
610
    addRecordToRRSet(exactNSEC3.d_owner, QType::NSEC3, exactNSEC3.d_ttd - now, exactNSEC3.d_record, exactNSEC3.d_signatures, doDNSSEC, ret);
17✔
611
    return true;
17✔
612
  }
19✔
613

614
  VLOG(log, name << ": No direct NSEC3 match found for " << nameHash << ", looking for closest encloser" << endl);
55!
615
  DNSName closestEncloser(name);
55✔
616
  bool found = false;
55✔
617
  ZoneEntry::CacheEntry closestNSEC3;
55✔
618
  auto remainingLabels = closestEncloser.countLabels() - 1;
55✔
619
  while (!found && closestEncloser.chopOff() && remainingLabels >= zoneLabelsCount) {
89!
620
    auto closestHash = DNSName(toBase32Hex(getHashFromNSEC3(closestEncloser, iterations, salt, validationContext))) + zone;
77✔
621
    remainingLabels--;
77✔
622

623
    if (getNSEC3(now, zoneEntry, closestHash, closestNSEC3)) {
77✔
624
      VLOG(log, name << ": Found closest encloser at " << closestEncloser << " (" << closestHash << ") inserted by " << closestNSEC3.d_qname << '/' << closestNSEC3.d_qtype << endl);
43!
625

626
      auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(closestNSEC3.d_record);
43✔
627
      if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) {
43!
628
        VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl);
×
629
        break;
×
630
      }
×
631

632
      const DNSName signer = getSigner(closestNSEC3.d_signatures);
43✔
633
      /* This time we do not allow any ancestor NSEC3, as if the closest encloser is a delegation
634
         NS we know nothing about the names in the child zone. */
635
      if (isNSEC3AncestorDelegation(signer, closestNSEC3.d_owner, *nsec3)) {
43✔
636
        /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
637
           Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
638
           nonexistence of any RRs below that zone cut, which include all RRs at
639
           that (original) owner name other than DS RRs, and all RRs below that
640
           owner name regardless of type.
641
        */
642
        VLOG_NO_PREFIX(log, " but this is an ancestor delegation NSEC3" << endl);
6!
643
        break;
6✔
644
      }
6✔
645

646
      if (type == QType::DS && !name.isRoot() && signer == name) {
37!
647
        VLOG_NO_PREFIX(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS");
×
648
        return false;
×
649
      }
×
650

651
      found = true;
37✔
652
      break;
37✔
653
    }
37✔
654
  }
77✔
655

656
  if (!found) {
55✔
657
    VLOG(log, name << ": Nothing found for the closest encloser in NSEC3 aggressive cache either" << endl);
18!
658
    return false;
18✔
659
  }
18✔
660

661
  unsigned int labelIdx = name.countLabels() - closestEncloser.countLabels();
37✔
662
  if (labelIdx < 1) {
37!
663
    return false;
×
664
  }
×
665

666
  DNSName nsecFound;
37✔
667
  DNSName nextCloser(closestEncloser);
37✔
668
  nextCloser.prependRawLabel(name.getRawLabel(labelIdx - 1));
37✔
669
  auto nextCloserHash = toBase32Hex(getHashFromNSEC3(nextCloser, iterations, salt, validationContext));
37✔
670
  VLOG(log, name << ": Looking for a NSEC3 covering the next closer " << nextCloser << " (" << nextCloserHash << ")" << endl);
37!
671

672
  ZoneEntry::CacheEntry nextCloserEntry;
37✔
673
  if (!getNSECBefore(now, zoneEntry, DNSName(nextCloserHash) + zone, nextCloserEntry)) {
37!
674
    VLOG(log, name << ": Nothing found for the next closer in NSEC3 aggressive cache" << endl);
×
675
    return false;
×
676
  }
×
677

678
  if (!isCoveredByNSEC3Hash(DNSName(nextCloserHash) + zone, nextCloserEntry.d_owner, nextCloserEntry.d_next)) {
37✔
679
    VLOG(log, name << ": No covering record found for the next closer in NSEC3 aggressive cache" << endl);
18!
680
    return false;
18✔
681
  }
18✔
682

683
  auto nextCloserNsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(nextCloserEntry.d_record);
19✔
684
  if (!nextCloserNsec3 || nextCloserNsec3->d_iterations != iterations || nextCloserNsec3->d_salt != salt) {
19!
685
    VLOG(log, name << ": The NSEC3 covering the next closer is not valid, or has a different salt or iterations count, bailing out" << endl);
×
686
    return false;
×
687
  }
×
688

689
  const DNSName nextCloserSigner = getSigner(nextCloserEntry.d_signatures);
19✔
690
  if (type == QType::DS && !name.isRoot() && nextCloserSigner == name) {
19!
691
    VLOG(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS");
×
692
    return false;
×
693
  }
×
694

695
  /* An ancestor NSEC3 would be fine here, since it does prove that there is no delegation at the next closer
696
     name (we don't insert opt-out NSEC3s into the cache). */
697
  DNSName wildcard(g_wildcarddnsname + closestEncloser);
19✔
698
  auto wcHash = toBase32Hex(getHashFromNSEC3(wildcard, iterations, salt, validationContext));
19✔
699
  VLOG(log, name << ": Looking for a NSEC3 covering the wildcard " << wildcard << " (" << wcHash << ")" << endl);
19!
700

701
  ZoneEntry::CacheEntry wcEntry;
19✔
702
  if (!getNSECBefore(now, zoneEntry, DNSName(wcHash) + zone, wcEntry)) {
19!
703
    VLOG(log, name << ": Nothing found for the wildcard in NSEC3 aggressive cache" << endl);
×
704
    return false;
×
705
  }
×
706

707
  if ((DNSName(wcHash) + zone) == wcEntry.d_owner) {
19✔
708
    VLOG(log, name << ": Found an exact match for the wildcard");
7!
709

710
    auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(wcEntry.d_record);
7✔
711
    if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) {
7!
712
      VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl);
×
713
      return false;
×
714
    }
×
715

716
    const DNSName wcSigner = getSigner(wcEntry.d_signatures);
7✔
717
    /* It's an exact match for the wildcard, so it does exist. If we are looking for a DS
718
       an ancestor NSEC3 is fine, otherwise it does not prove anything. */
719
    if (type != QType::DS && isNSEC3AncestorDelegation(wcSigner, wcEntry.d_owner, *nsec3)) {
7!
720
      /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
721
         Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
722
         nonexistence of any RRs below that zone cut, which include all RRs at
723
         that (original) owner name other than DS RRs, and all RRs below that
724
         owner name regardless of type.
725
      */
726
      VLOG_NO_PREFIX(log, " but the NSEC3 covering the wildcard is an ancestor delegation NSEC3, bailing out" << endl);
×
727
      return false;
×
728
    }
×
729

730
    if (type == QType::DS && !name.isRoot() && wcSigner == name) {
7!
731
      VLOG_NO_PREFIX(log, " but this wildcard NSEC3 comes from the child zone and cannot be used to deny a DS");
×
732
      return false;
×
733
    }
×
734

735
    if (!isTypeDenied(*nsec3, type)) {
7✔
736
      VLOG_NO_PREFIX(log, " but the requested type (" << type.toString() << ") does exist" << endl);
3!
737
      return synthesizeFromNSEC3Wildcard(now, name, type, ret, res, doDNSSEC, nextCloserEntry, wildcard, log);
3✔
738
    }
3✔
739

740
    res = RCode::NoError;
4✔
741
    VLOG(log, endl);
4!
742
  }
4✔
743
  else {
12✔
744
    if (!isCoveredByNSEC3Hash(DNSName(wcHash) + zone, wcEntry.d_owner, wcEntry.d_next)) {
12✔
745
      VLOG(log, name << ": No covering record found for the wildcard in aggressive cache" << endl);
1!
746
      return false;
1✔
747
    }
1✔
748

749
    auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(wcEntry.d_record);
11✔
750
    if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) {
11!
751
      VLOG(log, name << ": The content of the NSEC3 covering the wildcard is not valid, or has a different salt or iterations count" << endl);
×
752
      return false;
×
753
    }
×
754

755
    const DNSName wcSigner = getSigner(wcEntry.d_signatures);
11✔
756
    if (type == QType::DS && !name.isRoot() && wcSigner == name) {
11!
757
      VLOG_NO_PREFIX(log, " but this wildcard NSEC3 comes from the child zone and cannot be used to deny a DS");
×
758
      return false;
×
759
    }
×
760

761
    /* We have a NSEC3 proving that the wildcard does not exist. An ancestor NSEC3 would be fine here, since it does prove
762
       that there is no delegation at the wildcard name (we don't insert opt-out NSEC3s into the cache). */
763
    res = RCode::NXDomain;
11✔
764
  }
11✔
765

766
  addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret);
15✔
767
  addRecordToRRSet(closestNSEC3.d_owner, QType::NSEC3, closestNSEC3.d_ttd - now, closestNSEC3.d_record, closestNSEC3.d_signatures, doDNSSEC, ret);
15✔
768

769
  /* no need to include the same NSEC3 twice */
770
  if (nextCloserEntry.d_owner != closestNSEC3.d_owner) {
15!
771
    addRecordToRRSet(nextCloserEntry.d_owner, QType::NSEC3, nextCloserEntry.d_ttd - now, nextCloserEntry.d_record, nextCloserEntry.d_signatures, doDNSSEC, ret);
15✔
772
  }
15✔
773
  if (wcEntry.d_owner != closestNSEC3.d_owner && wcEntry.d_owner != nextCloserEntry.d_owner) {
15!
774
    // coverity[store_truncates_time_t]
775
    addRecordToRRSet(wcEntry.d_owner, QType::NSEC3, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
14✔
776
  }
14✔
777

778
  VLOG(log, name << ": Found valid NSEC3s covering the requested name and type!" << endl);
15!
779
  ++d_nsec3Hits;
15✔
780
  return true;
15✔
781
}
19✔
782

783
bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, pdns::validation::ValidationContext& validationContext, const OptLog& log)
784
{
15,301✔
785
  std::shared_ptr<LockGuarded<ZoneEntry>> zoneEntry;
15,301✔
786
  if (type == QType::DS) {
15,301✔
787
    DNSName parent(name);
112✔
788
    parent.chopOff();
112✔
789
    zoneEntry = getBestZone(parent);
112✔
790
  }
112✔
791
  else {
15,189✔
792
    zoneEntry = getBestZone(name);
15,189✔
793
  }
15,189✔
794

795
  if (!zoneEntry) {
15,301✔
796
    return false;
7,888✔
797
  }
7,888✔
798

799
  DNSName zone;
7,413✔
800
  bool nsec3;
7,413✔
801
  {
7,413✔
802
    auto entry = zoneEntry->try_lock();
7,413✔
803
    if (!entry.owns_lock()) {
7,413✔
804
      return false;
4✔
805
    }
4✔
806
    if (entry->d_entries.empty()) {
7,409✔
807
      return false;
6,962✔
808
    }
6,962✔
809
    zone = entry->d_zone;
447✔
810
    nsec3 = entry->d_nsec3;
447✔
811
  }
447✔
812

813
  vState cachedState;
×
814
  std::vector<DNSRecord> soaSet;
447✔
815
  MemRecursorCache::SigRecs soaSignatures = MemRecursorCache::s_emptySigRecs;
447✔
816
  /* we might not actually need the SOA if we find a matching wildcard, but let's not bother for now */
817
  if (g_recCache->get(now, zone, QType::SOA, MemRecursorCache::RequireAuth, &soaSet, who, routingTag, doDNSSEC ? &soaSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
447!
818
    VLOG(log, name << ": No valid SOA found for " << zone << ", which is the best match for " << name << endl);
210!
819
    return false;
210✔
820
  }
210✔
821

822
  if (nsec3) {
237✔
823
    return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log, validationContext);
82✔
824
  }
82✔
825

826
  ZoneEntry::CacheEntry entry;
155✔
827
  ZoneEntry::CacheEntry wcEntry;
155✔
828
  bool covered = false;
155✔
829
  bool needWildcard = false;
155✔
830

831
  VLOG(log, name << ": Looking for a NSEC before " << name);
155!
832
  if (!getNSECBefore(now, zoneEntry, name, entry)) {
155!
833
    VLOG_NO_PREFIX(log, ": nothing found in the aggressive cache" << endl);
×
834
    return false;
×
835
  }
×
836

837
  auto content = std::dynamic_pointer_cast<const NSECRecordContent>(entry.d_record);
155✔
838
  if (!content) {
155!
839
    return false;
×
840
  }
×
841

842
  VLOG_NO_PREFIX(log, ": found a possible NSEC at " << entry.d_owner << " inserted by " << entry.d_qname << '/' << entry.d_qtype << ' ');
155!
843
  // note that matchesNSEC() takes care of ruling out ancestor NSECs for us
844
  auto denial = matchesNSEC(name, type.getCode(), entry.d_owner, *content, entry.d_signatures, log);
155✔
845
  if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) {
155✔
846
    VLOG_NO_PREFIX(log, " but it does not cover us" << endl);
110!
847
    return false;
110✔
848
  }
110✔
849
  else if (denial == dState::NXQTYPE) {
45✔
850
    covered = true;
29✔
851
    VLOG_NO_PREFIX(log, " and it proves that the type does not exist" << endl);
29!
852
    res = RCode::NoError;
29✔
853
  }
29✔
854
  else if (denial == dState::NXDOMAIN) {
16✔
855
    VLOG_NO_PREFIX(log, " and it proves that the name does not exist" << endl);
14!
856
    DNSName closestEncloser = getClosestEncloserFromNSEC(name, entry.d_owner, entry.d_next);
14✔
857
    DNSName wc = g_wildcarddnsname + closestEncloser;
14✔
858

859
    VLOG(log, name << ": Now looking for a NSEC before the wildcard " << wc);
14!
860
    if (!getNSECBefore(now, zoneEntry, wc, wcEntry)) {
14!
861
      VLOG_NO_PREFIX(log, ": nothing found in the aggressive cache" << endl);
×
862
      return false;
×
863
    }
×
864

865
    VLOG_NO_PREFIX(log, ": found a possible NSEC at " << wcEntry.d_owner << " ");
14!
866

867
    auto nsecContent = std::dynamic_pointer_cast<const NSECRecordContent>(wcEntry.d_record);
14✔
868

869
    denial = matchesNSEC(wc, type.getCode(), wcEntry.d_owner, *nsecContent, wcEntry.d_signatures, log);
14✔
870
    if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) {
14!
871

872
      if (wcEntry.d_owner == wc) {
3!
873
        VLOG_NO_PREFIX(log, " proving that the wildcard does exist" << endl);
3!
874
        return synthesizeFromNSECWildcard(now, name, type, ret, res, doDNSSEC, entry, wc, log);
3✔
875
      }
3✔
876

877
      VLOG_NO_PREFIX(log, " but it does no cover us" << endl);
×
878

879
      return false;
×
880
    }
3✔
881
    else if (denial == dState::NXQTYPE) {
11✔
882
      VLOG_NO_PREFIX(log, " and it proves that there is a matching wildcard, but the type does not exist" << endl);
4!
883
      covered = true;
4✔
884
      res = RCode::NoError;
4✔
885
    }
4✔
886
    else if (denial == dState::NXDOMAIN) {
7!
887
      VLOG_NO_PREFIX(log, " and it proves that there is no matching wildcard" << endl);
7!
888
      covered = true;
7✔
889
      res = RCode::NXDomain;
7✔
890
    }
7✔
891

892
    if (wcEntry.d_owner != wc && wcEntry.d_owner != entry.d_owner) {
11!
893
      needWildcard = true;
7✔
894
    }
7✔
895
  }
11✔
896

897
  if (!covered) {
42!
898
    return false;
×
899
  }
×
900

901
  ret.reserve(ret.size() + soaSet.size() + soaSignatures->size() + /* NSEC */ 1 + entry.d_signatures.size() + (needWildcard ? (/* NSEC */ 1 + wcEntry.d_signatures.size()) : 0));
42✔
902

903
  addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret);
42✔
904
  // coverity[store_truncates_time_t]
905
  addRecordToRRSet(entry.d_owner, QType::NSEC, entry.d_ttd - now, entry.d_record, entry.d_signatures, doDNSSEC, ret);
42✔
906

907
  if (needWildcard) {
42✔
908
    // coverity[store_truncates_time_t]
909
    addRecordToRRSet(wcEntry.d_owner, QType::NSEC, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
7✔
910
  }
7✔
911

912
  VLOG(log, name << ": Found valid NSECs covering the requested name and type!" << endl);
42!
913
  ++d_nsecHits;
42✔
914
  return true;
42✔
915
}
42✔
916

917
size_t AggressiveNSECCache::dumpToFile(pdns::UniqueFilePtr& filePtr, const struct timeval& now)
918
{
9✔
919
  size_t ret = 0;
9✔
920

921
  auto zones = d_zones.read_lock();
9✔
922
  zones->visit([&ret, now, &filePtr](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
13✔
923
    if (!node.d_value) {
8!
924
      return;
×
925
    }
×
926

927
    auto zone = node.d_value->lock();
8✔
928
    fprintf(filePtr.get(), "; Zone %s\n", zone->d_zone.toString().c_str());
8✔
929

930
    for (const auto& entry : zone->d_entries) {
12✔
931
      int64_t ttl = entry.d_ttd - now.tv_sec;
12✔
932
      try {
12✔
933
        fprintf(filePtr.get(), "%s %" PRId64 " IN %s %s by %s/%s\n", entry.d_owner.toString().c_str(), ttl, zone->d_nsec3 ? "NSEC3" : "NSEC", entry.d_record->getZoneRepresentation().c_str(), entry.d_qname.toString().c_str(), entry.d_qtype.toString().c_str());
12✔
934
        for (const auto& signature : entry.d_signatures) {
12✔
935
          fprintf(filePtr.get(), "- RRSIG %s\n", signature->getZoneRepresentation().c_str());
12✔
936
        }
12✔
937
        ++ret;
12✔
938
      }
12✔
939
      catch (const std::exception& e) {
12✔
940
        fprintf(filePtr.get(), "; Error dumping record from zone %s: %s\n", zone->d_zone.toString().c_str(), e.what());
×
941
      }
×
942
      catch (...) {
12✔
943
        fprintf(filePtr.get(), "; Error dumping record from zone %s\n", zone->d_zone.toString().c_str());
×
944
      }
×
945
    }
12✔
946
  });
8✔
947

948
  return ret;
9✔
949
}
9✔
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