]>
Commit | Line | Data |
---|---|---|
25f5783a RG |
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 | */ | |
5586c462 | 22 | #include <cinttypes> |
dcf64a6e | 23 | #include <climits> |
25f5783a RG |
24 | |
25 | #include "aggressive_nsec.hh" | |
bf720981 | 26 | #include "cachecleaner.hh" |
25f5783a | 27 | #include "recursor_cache.hh" |
5a5c53b8 | 28 | #include "logger.hh" |
25f5783a RG |
29 | #include "validate.hh" |
30 | ||
31 | std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache{nullptr}; | |
15e973d6 | 32 | uint64_t AggressiveNSECCache::s_nsec3DenialProofMaxCost{0}; |
dcf64a6e | 33 | uint8_t AggressiveNSECCache::s_maxNSEC3CommonPrefix = AggressiveNSECCache::s_default_maxNSEC3CommonPrefix; |
25f5783a RG |
34 | |
35 | /* this is defined in syncres.hh and we are not importing that here */ | |
36 | extern std::unique_ptr<MemRecursorCache> g_recCache; | |
37 | ||
1f9eeeba | 38 | std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>> AggressiveNSECCache::getBestZone(const DNSName& zone) |
25f5783a | 39 | { |
1f9eeeba | 40 | std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>> entry{nullptr}; |
25f5783a | 41 | { |
1f9eeeba RG |
42 | auto zones = d_zones.try_read_lock(); |
43 | if (!zones.owns_lock()) { | |
ca87a83b RG |
44 | return entry; |
45 | } | |
46 | ||
1f9eeeba | 47 | auto got = zones->lookup(zone); |
25f5783a RG |
48 | if (got) { |
49 | return *got; | |
50 | } | |
51 | } | |
52 | return entry; | |
53 | } | |
54 | ||
1f9eeeba | 55 | std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>> AggressiveNSECCache::getZone(const DNSName& zone) |
25f5783a | 56 | { |
25f5783a | 57 | { |
1f9eeeba RG |
58 | auto zones = d_zones.read_lock(); |
59 | auto got = zones->lookup(zone); | |
60 | if (got && *got) { | |
61 | auto locked = (*got)->lock(); | |
62 | if (locked->d_zone == zone) { | |
63 | return *got; | |
64 | } | |
25f5783a RG |
65 | } |
66 | } | |
67 | ||
1f9eeeba | 68 | auto entry = std::make_shared<LockGuarded<ZoneEntry>>(zone); |
25f5783a RG |
69 | |
70 | { | |
6e41eb90 | 71 | auto zones = d_zones.write_lock(); |
25f5783a | 72 | /* it might have been inserted in the mean time */ |
1f9eeeba RG |
73 | auto got = zones->lookup(zone); |
74 | if (got && *got) { | |
75 | auto locked = (*got)->lock(); | |
76 | if (locked->d_zone == zone) { | |
77 | return *got; | |
78 | } | |
25f5783a | 79 | } |
1f9eeeba | 80 | zones->add(zone, std::shared_ptr<LockGuarded<ZoneEntry>>(entry)); |
25f5783a RG |
81 | return entry; |
82 | } | |
83 | } | |
84 | ||
1f9eeeba | 85 | void AggressiveNSECCache::updateEntriesCount(SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& zones) |
da6be6e3 | 86 | { |
da6be6e3 | 87 | uint64_t counter = 0; |
1f9eeeba | 88 | zones.visit([&counter](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) { |
edcf680e | 89 | if (node.d_value) { |
1f9eeeba | 90 | counter += node.d_value->lock()->d_entries.size(); |
edcf680e | 91 | } |
da6be6e3 RG |
92 | }); |
93 | d_entriesCount = counter; | |
94 | } | |
95 | ||
96 | void AggressiveNSECCache::removeZoneInfo(const DNSName& zone, bool subzones) | |
97 | { | |
6e41eb90 | 98 | auto zones = d_zones.write_lock(); |
da6be6e3 RG |
99 | |
100 | if (subzones) { | |
1f9eeeba RG |
101 | zones->remove(zone, true); |
102 | updateEntriesCount(*zones); | |
da6be6e3 RG |
103 | } |
104 | else { | |
1f9eeeba RG |
105 | auto got = zones->lookup(zone); |
106 | if (!got || !*got) { | |
5586c462 RG |
107 | return; |
108 | } | |
109 | ||
6cf8f84f RG |
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; | |
114 | { | |
1f9eeeba RG |
115 | auto locked = (*got)->lock(); |
116 | if (locked->d_zone != zone) { | |
117 | return; | |
118 | } | |
119 | auto removed = locked->d_entries.size(); | |
120 | zones->remove(zone, false); | |
6cf8f84f RG |
121 | d_entriesCount -= removed; |
122 | } | |
da6be6e3 RG |
123 | } |
124 | } | |
125 | ||
5586c462 | 126 | void AggressiveNSECCache::prune(time_t now) |
edcf680e RG |
127 | { |
128 | uint64_t maxNumberOfEntries = d_maxEntries; | |
edcf680e | 129 | std::vector<DNSName> emptyEntries; |
edcf680e | 130 | uint64_t erased = 0; |
edcf680e | 131 | |
f4408114 OM |
132 | auto zones = d_zones.write_lock(); |
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) { | |
135 | if (!node.d_value) { | |
136 | return; | |
137 | } | |
edcf680e | 138 | |
f4408114 OM |
139 | auto zoneEntry = node.d_value->lock(); |
140 | auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries); | |
141 | const auto toLookAtForThisZone = (zoneEntry->d_entries.size() + 9) / 10; | |
142 | uint64_t lookedAt = 0; | |
143 | for (auto it = sidx.begin(); it != sidx.end() && lookedAt < toLookAtForThisZone; ++lookedAt) { | |
17806638 | 144 | if (it->d_ttd <= now) { |
f4408114 OM |
145 | it = sidx.erase(it); |
146 | ++erased; | |
147 | } | |
148 | else { | |
149 | ++it; | |
edcf680e | 150 | } |
f4408114 | 151 | } |
edcf680e | 152 | |
f4408114 OM |
153 | if (zoneEntry->d_entries.empty()) { |
154 | emptyEntries.push_back(zoneEntry->d_zone); | |
155 | } | |
156 | }); | |
1f9eeeba | 157 | |
f4408114 | 158 | d_entriesCount -= erased; |
edcf680e | 159 | |
f4408114 OM |
160 | // If we are still above try harder by nuking entries from each zone in LRU order |
161 | auto entriesCount = d_entriesCount.load(); | |
162 | if (entriesCount > maxNumberOfEntries) { | |
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) { | |
edcf680e RG |
167 | return; |
168 | } | |
1f9eeeba | 169 | auto zoneEntry = node.d_value->lock(); |
f4408114 | 170 | const auto zoneSize = zoneEntry->d_entries.size(); |
1f9eeeba | 171 | auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries); |
f4408114 OM |
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; | |
dab9636b | 179 | for (auto it = sidx.begin(); it != sidx.end() && trimmedFromThisZone < toTrimForThisZone;) { |
f4408114 OM |
180 | it = sidx.erase(it); |
181 | ++erased; | |
182 | ++trimmedFromThisZone; | |
183 | if (--toErase == 0) { | |
1f9eeeba RG |
184 | break; |
185 | } | |
edcf680e | 186 | } |
f4408114 | 187 | if (zoneEntry->d_entries.empty()) { |
1f9eeeba | 188 | emptyEntries.push_back(zoneEntry->d_zone); |
edcf680e RG |
189 | } |
190 | }); | |
edcf680e | 191 | |
f4408114 OM |
192 | d_entriesCount -= erased; |
193 | } | |
5e120e2b | 194 | |
45c1026d | 195 | if (!emptyEntries.empty()) { |
5e120e2b | 196 | for (const auto& entry : emptyEntries) { |
1f9eeeba | 197 | zones->remove(entry); |
5e120e2b RG |
198 | } |
199 | } | |
edcf680e RG |
200 | } |
201 | ||
d06dcda4 | 202 | static bool isMinimallyCoveringNSEC(const DNSName& owner, const std::shared_ptr<const NSECRecordContent>& nsec) |
cdc9823b RG |
203 | { |
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(); | |
208 | const auto& nextStorage = nsec->d_next.getStorage(); | |
2c37277a PD |
209 | |
210 | // is the next name at least two octets long? | |
cdc9823b RG |
211 | if (nextStorage.size() <= 2 || storage.size() != (nextStorage.size() - 2)) { |
212 | return false; | |
213 | } | |
214 | ||
2c37277a | 215 | // does the next name start with a one-octet long label containing a zero, i.e. `\000`? |
cdc9823b RG |
216 | if (nextStorage.at(0) != 1 || static_cast<uint8_t>(nextStorage.at(1)) != static_cast<uint8_t>(0)) { |
217 | return false; | |
218 | } | |
219 | ||
2c37277a | 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.'? |
cdc9823b RG |
221 | if (nextStorage.compare(2, nextStorage.size() - 2, storage) != 0) { |
222 | return false; | |
223 | } | |
224 | ||
225 | return true; | |
226 | } | |
227 | ||
27df0673 | 228 | static bool commonPrefixIsLong(const string& one, const string& two, size_t bound) |
cdc9823b | 229 | { |
dcf64a6e | 230 | size_t length = 0; |
27df0673 | 231 | const auto minLength = std::min(one.length(), two.length()); |
dcf64a6e | 232 | |
27df0673 | 233 | for (size_t i = 0; i < minLength; i++) { |
dcf64a6e OM |
234 | const auto byte1 = one.at(i); |
235 | const auto byte2 = two.at(i); | |
236 | // shortcut | |
237 | if (byte1 == byte2) { | |
238 | length += CHAR_BIT; | |
27df0673 OM |
239 | if (length > bound) { |
240 | return true; | |
241 | } | |
dcf64a6e OM |
242 | continue; |
243 | } | |
4169386e | 244 | // bytes differ, let's look at the bits |
dcf64a6e OM |
245 | for (ssize_t j = CHAR_BIT - 1; j >= 0; j--) { |
246 | const auto bit1 = byte1 & (1 << j); | |
247 | const auto bit2 = byte2 & (1 << j); | |
248 | if (bit1 != bit2) { | |
27df0673 | 249 | return length > bound; |
dcf64a6e OM |
250 | } |
251 | length++; | |
27df0673 OM |
252 | if (length > bound) { |
253 | return true; | |
254 | } | |
dcf64a6e OM |
255 | } |
256 | } | |
27df0673 | 257 | return length > bound; |
dcf64a6e | 258 | } |
cdc9823b | 259 | |
dcf64a6e OM |
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. | |
171376f1 | 262 | bool AggressiveNSECCache::isSmallCoveringNSEC3(const DNSName& owner, const std::string& nextHash) |
dcf64a6e OM |
263 | { |
264 | std::string ownerHash(fromBase32Hex(owner.getRawLabel(0))); | |
257b23b4 OM |
265 | // Special case: empty zone, so the single NSEC3 covers everything. Prefix is long but we still want it cached. |
266 | if (ownerHash == nextHash) { | |
267 | return false; | |
268 | } | |
27df0673 | 269 | return commonPrefixIsLong(ownerHash, nextHash, AggressiveNSECCache::s_maxNSEC3CommonPrefix); |
cdc9823b RG |
270 | } |
271 | ||
d06dcda4 | 272 | void AggressiveNSECCache::insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, bool nsec3) |
25f5783a | 273 | { |
e4f20c78 OM |
274 | if (nsec3 && nsec3Disabled()) { |
275 | return; | |
276 | } | |
0cbcfeda RG |
277 | if (signatures.empty()) { |
278 | return; | |
279 | } | |
280 | ||
1f9eeeba | 281 | std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>> entry = getZone(zone); |
25f5783a | 282 | { |
1f9eeeba RG |
283 | auto zoneEntry = entry->lock(); |
284 | if (nsec3 && !zoneEntry->d_nsec3) { | |
285 | d_entriesCount -= zoneEntry->d_entries.size(); | |
286 | zoneEntry->d_entries.clear(); | |
287 | zoneEntry->d_nsec3 = true; | |
25f5783a RG |
288 | } |
289 | ||
290 | DNSName next; | |
291 | if (!nsec3) { | |
292 | auto content = getRR<NSECRecordContent>(record); | |
293 | if (!content) { | |
294 | throw std::runtime_error("Error getting the content from a NSEC record"); | |
295 | } | |
cdc9823b | 296 | |
25f5783a | 297 | next = content->d_next; |
0cbcfeda RG |
298 | if (next.canonCompare(owner) && next != zone) { |
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 | } | |
cdc9823b RG |
303 | |
304 | if (isMinimallyCoveringNSEC(owner, content)) { | |
305 | /* not accepting minimally covering answers since they only deny one name */ | |
306 | return; | |
307 | } | |
25f5783a RG |
308 | } |
309 | else { | |
310 | auto content = getRR<NSEC3RecordContent>(record); | |
311 | if (!content) { | |
312 | throw std::runtime_error("Error getting the content from a NSEC3 record"); | |
313 | } | |
0cbcfeda RG |
314 | |
315 | if (content->isOptOut()) { | |
316 | /* doesn't prove anything, sorry */ | |
317 | return; | |
318 | } | |
319 | ||
bf720981 | 320 | if (g_maxNSEC3Iterations && content->d_iterations > g_maxNSEC3Iterations) { |
0cbcfeda | 321 | /* can't use that */ |
bf720981 RG |
322 | return; |
323 | } | |
324 | ||
171376f1 | 325 | if (isSmallCoveringNSEC3(owner, content->d_nexthash)) { |
dcf64a6e | 326 | /* not accepting small covering answers since they only deny a small subset */ |
cdc9823b RG |
327 | return; |
328 | } | |
329 | ||
0fb08e4f RG |
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 | |
0cbcfeda | 332 | next = DNSName(toBase32Hex(content->d_nexthash)) + zone; |
684b0a56 | 333 | |
1f9eeeba RG |
334 | if (zoneEntry->d_iterations != content->d_iterations || zoneEntry->d_salt != content->d_salt) { |
335 | zoneEntry->d_iterations = content->d_iterations; | |
336 | zoneEntry->d_salt = content->d_salt; | |
684b0a56 RG |
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. | |
1f9eeeba RG |
340 | d_entriesCount -= zoneEntry->d_entries.size(); |
341 | zoneEntry->d_entries.clear(); | |
684b0a56 | 342 | } |
25f5783a RG |
343 | } |
344 | ||
345 | /* the TTL is already a TTD by now */ | |
03e5e4cb | 346 | if (!nsec3 && isWildcardExpanded(owner.countLabels(), *signatures.at(0))) { |
0cbcfeda | 347 | DNSName realOwner = getNSECOwnerName(owner, signatures); |
93b25e96 | 348 | auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, realOwner, next, record.d_ttl}); |
4b93ec25 RG |
349 | if (pair.second) { |
350 | ++d_entriesCount; | |
351 | } | |
93b25e96 | 352 | else { |
4c5a50dc | 353 | zoneEntry->d_entries.replace(pair.first, {record.getContent(), signatures, std::move(realOwner), next, record.d_ttl}); |
93b25e96 | 354 | } |
0cbcfeda RG |
355 | } |
356 | else { | |
93b25e96 | 357 | auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, owner, next, record.d_ttl}); |
4b93ec25 RG |
358 | if (pair.second) { |
359 | ++d_entriesCount; | |
360 | } | |
93b25e96 | 361 | else { |
4c5a50dc | 362 | zoneEntry->d_entries.replace(pair.first, {record.getContent(), signatures, owner, std::move(next), record.d_ttl}); |
93b25e96 | 363 | } |
0cbcfeda | 364 | } |
25f5783a RG |
365 | } |
366 | } | |
367 | ||
1f9eeeba | 368 | bool AggressiveNSECCache::getNSECBefore(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zone, const DNSName& name, ZoneEntry::CacheEntry& entry) |
d3336f1a | 369 | { |
1f9eeeba RG |
370 | auto zoneEntry = zone->try_lock(); |
371 | if (!zoneEntry.owns_lock() || zoneEntry->d_entries.empty()) { | |
25f5783a RG |
372 | return false; |
373 | } | |
374 | ||
375 | auto& idx = zoneEntry->d_entries.get<ZoneEntry::OrderedTag>(); | |
376 | auto it = idx.lower_bound(name); | |
377 | bool end = false; | |
bf720981 | 378 | bool wrapped = false; |
25f5783a | 379 | |
bf720981 | 380 | if (it == idx.begin() && it->d_owner != name) { |
bf720981 RG |
381 | it = idx.end(); |
382 | // we know the map is not empty | |
383 | it--; | |
384 | // might be that owner > name && name < next | |
385 | // can't go further, but perhaps we wrapped? | |
386 | wrapped = true; | |
387 | } | |
25f5783a | 388 | |
45c1026d | 389 | while (!end && !wrapped && (it == idx.end() || (it->d_owner != name && !it->d_owner.canonCompare(name)))) { |
25f5783a | 390 | if (it == idx.begin()) { |
25f5783a RG |
391 | end = true; |
392 | break; | |
393 | } | |
394 | else { | |
395 | it--; | |
25f5783a RG |
396 | } |
397 | } | |
398 | ||
399 | if (end) { | |
25f5783a RG |
400 | return false; |
401 | } | |
402 | ||
f4408114 | 403 | auto firstIndexIterator = zoneEntry->d_entries.project<ZoneEntry::OrderedTag>(it); |
25f5783a | 404 | if (it->d_ttd <= now) { |
f4408114 | 405 | moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator); |
25f5783a RG |
406 | return false; |
407 | } | |
408 | ||
409 | entry = *it; | |
f4408114 | 410 | moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator); |
25f5783a RG |
411 | return true; |
412 | } | |
413 | ||
1f9eeeba | 414 | bool AggressiveNSECCache::getNSEC3(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zone, const DNSName& name, ZoneEntry::CacheEntry& entry) |
d3336f1a | 415 | { |
1f9eeeba RG |
416 | auto zoneEntry = zone->try_lock(); |
417 | if (!zoneEntry.owns_lock() || zoneEntry->d_entries.empty()) { | |
25f5783a | 418 | return false; |
25f5783a RG |
419 | } |
420 | ||
bf720981 RG |
421 | auto& idx = zoneEntry->d_entries.get<ZoneEntry::HashedTag>(); |
422 | auto entries = idx.equal_range(name); | |
25f5783a | 423 | |
bf720981 | 424 | for (auto it = entries.first; it != entries.second; ++it) { |
25f5783a | 425 | |
bf720981 RG |
426 | if (it->d_owner != name) { |
427 | continue; | |
428 | } | |
25f5783a | 429 | |
da6be6e3 | 430 | auto firstIndexIterator = zoneEntry->d_entries.project<ZoneEntry::OrderedTag>(it); |
bf720981 | 431 | if (it->d_ttd <= now) { |
05a49857 | 432 | moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator); |
bf720981 | 433 | return false; |
25f5783a | 434 | } |
25f5783a | 435 | |
bf720981 RG |
436 | entry = *it; |
437 | moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator); | |
438 | return true; | |
25f5783a RG |
439 | } |
440 | ||
bf720981 RG |
441 | return false; |
442 | } | |
25f5783a | 443 | |
d06dcda4 | 444 | static void addToRRSet(const time_t now, std::vector<DNSRecord>& recordSet, std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures, const DNSName& owner, bool doDNSSEC, std::vector<DNSRecord>& ret, DNSResourceRecord::Place place = DNSResourceRecord::AUTHORITY) |
bf720981 RG |
445 | { |
446 | uint32_t ttl = 0; | |
25f5783a | 447 | |
bf720981 | 448 | for (auto& record : recordSet) { |
25f5783a RG |
449 | if (record.d_class != QClass::IN) { |
450 | continue; | |
451 | } | |
452 | ||
453 | record.d_ttl -= now; | |
5a5c53b8 | 454 | record.d_name = owner; |
25f5783a | 455 | ttl = record.d_ttl; |
5a5c53b8 | 456 | record.d_place = place; |
25f5783a RG |
457 | ret.push_back(std::move(record)); |
458 | } | |
459 | ||
460 | if (doDNSSEC) { | |
bf720981 | 461 | for (auto& signature : signatures) { |
25f5783a RG |
462 | DNSRecord dr; |
463 | dr.d_type = QType::RRSIG; | |
bf720981 | 464 | dr.d_name = owner; |
25f5783a | 465 | dr.d_ttl = ttl; |
d06dcda4 | 466 | dr.setContent(std::move(signature)); |
5a5c53b8 | 467 | dr.d_place = place; |
25f5783a RG |
468 | dr.d_class = QClass::IN; |
469 | ret.push_back(std::move(dr)); | |
470 | } | |
471 | } | |
bf720981 | 472 | } |
25f5783a | 473 | |
e9e8fbcd | 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) |
bf720981 | 475 | { |
25f5783a | 476 | DNSRecord nsecRec; |
bf720981 RG |
477 | nsecRec.d_type = type.getCode(); |
478 | nsecRec.d_name = owner; | |
479 | nsecRec.d_ttl = ttl; | |
d06dcda4 | 480 | nsecRec.setContent(std::move(content)); |
25f5783a RG |
481 | nsecRec.d_place = DNSResourceRecord::AUTHORITY; |
482 | nsecRec.d_class = QClass::IN; | |
483 | ret.push_back(std::move(nsecRec)); | |
484 | ||
485 | if (doDNSSEC) { | |
bf720981 | 486 | for (auto& signature : signatures) { |
25f5783a RG |
487 | DNSRecord dr; |
488 | dr.d_type = QType::RRSIG; | |
bf720981 | 489 | dr.d_name = owner; |
25f5783a | 490 | dr.d_ttl = ttl; |
d06dcda4 | 491 | dr.setContent(std::move(signature)); |
25f5783a RG |
492 | dr.d_place = DNSResourceRecord::AUTHORITY; |
493 | dr.d_class = QClass::IN; | |
494 | ret.push_back(std::move(dr)); | |
495 | } | |
496 | } | |
25f5783a RG |
497 | } |
498 | ||
3bb98d97 | 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) |
5a5c53b8 RG |
500 | { |
501 | vState cachedState; | |
502 | ||
503 | std::vector<DNSRecord> wcSet; | |
d06dcda4 | 504 | std::vector<std::shared_ptr<const RRSIGRecordContent>> wcSignatures; |
5a5c53b8 | 505 | |
171089f5 | 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) { |
baaa61a1 | 507 | VLOG(log, name << ": Unfortunately we don't have a valid entry for " << wildcardName << ", so we cannot synthesize from that wildcard" << endl); |
5a5c53b8 RG |
508 | return false; |
509 | } | |
510 | ||
4c5a50dc | 511 | addToRRSet(now, wcSet, std::move(wcSignatures), name, doDNSSEC, ret, DNSResourceRecord::ANSWER); |
5a5c53b8 | 512 | /* no need for closest encloser proof, the wildcard is there */ |
8f67f0c2 | 513 | // coverity[store_truncates_time_t] |
e9e8fbcd | 514 | addRecordToRRSet(nextCloser.d_owner, QType::NSEC3, nextCloser.d_ttd - now, nextCloser.d_record, nextCloser.d_signatures, doDNSSEC, ret); |
5a5c53b8 RG |
515 | /* and of course we won't deny the wildcard either */ |
516 | ||
baaa61a1 | 517 | VLOG(log, name << ": Synthesized valid answer from NSEC3s and wildcard!" << endl); |
6c1fcbfe | 518 | ++d_nsec3WildcardHits; |
3bb98d97 | 519 | res = RCode::NoError; |
5a5c53b8 RG |
520 | return true; |
521 | } | |
522 | ||
3bb98d97 | 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) |
5a5c53b8 RG |
524 | { |
525 | vState cachedState; | |
526 | ||
527 | std::vector<DNSRecord> wcSet; | |
d06dcda4 | 528 | std::vector<std::shared_ptr<const RRSIGRecordContent>> wcSignatures; |
5a5c53b8 | 529 | |
171089f5 | 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) { |
baaa61a1 | 531 | VLOG(log, name << ": Unfortunately we don't have a valid entry for " << wildcardName << ", so we cannot synthesize from that wildcard" << endl); |
5a5c53b8 RG |
532 | return false; |
533 | } | |
534 | ||
4c5a50dc | 535 | addToRRSet(now, wcSet, std::move(wcSignatures), name, doDNSSEC, ret, DNSResourceRecord::ANSWER); |
8f67f0c2 | 536 | // coverity[store_truncates_time_t] |
e9e8fbcd | 537 | addRecordToRRSet(nsec.d_owner, QType::NSEC, nsec.d_ttd - now, nsec.d_record, nsec.d_signatures, doDNSSEC, ret); |
5a5c53b8 | 538 | |
baaa61a1 | 539 | VLOG(log, name << ": Synthesized valid answer from NSECs and wildcard!" << endl); |
6c1fcbfe | 540 | ++d_nsecWildcardHits; |
3bb98d97 | 541 | res = RCode::NoError; |
5a5c53b8 RG |
542 | return true; |
543 | } | |
544 | ||
15e973d6 | 545 | bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog& log, pdns::validation::ValidationContext& validationContext) |
25f5783a | 546 | { |
779f35b4 RG |
547 | DNSName zone; |
548 | std::string salt; | |
549 | uint16_t iterations; | |
550 | ||
551 | { | |
1f9eeeba RG |
552 | auto entry = zoneEntry->try_lock(); |
553 | if (!entry.owns_lock()) { | |
779f35b4 RG |
554 | return false; |
555 | } | |
1f9eeeba RG |
556 | salt = entry->d_salt; |
557 | zone = entry->d_zone; | |
558 | iterations = entry->d_iterations; | |
779f35b4 | 559 | } |
25f5783a | 560 | |
15e973d6 OM |
561 | const auto zoneLabelsCount = zone.countLabels(); |
562 | if (s_nsec3DenialProofMaxCost != 0) { | |
563 | const auto worstCaseIterations = getNSEC3DenialProofWorstCaseIterationsCount(name.countLabels() - zoneLabelsCount, iterations, salt.length()); | |
564 | if (worstCaseIterations > s_nsec3DenialProofMaxCost) { | |
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); | |
567 | return false; | |
568 | } | |
569 | } | |
570 | ||
571 | auto nameHash = DNSName(toBase32Hex(getHashFromNSEC3(name, iterations, salt, validationContext))) + zone; | |
25f5783a | 572 | |
bf720981 RG |
573 | ZoneEntry::CacheEntry exactNSEC3; |
574 | if (getNSEC3(now, zoneEntry, nameHash, exactNSEC3)) { | |
baaa61a1 | 575 | VLOG(log, name << ": Found a direct NSEC3 match for " << nameHash); |
d06dcda4 | 576 | auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(exactNSEC3.d_record); |
684b0a56 | 577 | if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) { |
baaa61a1 | 578 | VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl); |
bf720981 RG |
579 | return false; |
580 | } | |
25f5783a | 581 | |
03e5e4cb | 582 | if (!isTypeDenied(*nsec3, type)) { |
baaa61a1 | 583 | VLOG_NO_PREFIX(log, " but the requested type (" << type.toString() << ") does exist" << endl); |
bf720981 RG |
584 | return false; |
585 | } | |
25f5783a | 586 | |
0cbcfeda | 587 | const DNSName signer = getSigner(exactNSEC3.d_signatures); |
1eed7f45 RG |
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 */ | |
03e5e4cb | 590 | if (type != QType::DS && isNSEC3AncestorDelegation(signer, exactNSEC3.d_owner, *nsec3)) { |
0cbcfeda RG |
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 | */ | |
baaa61a1 | 597 | VLOG_NO_PREFIX(log, " but this is an ancestor delegation NSEC3" << endl); |
0cbcfeda RG |
598 | return false; |
599 | } | |
600 | ||
ae727ae2 | 601 | if (type == QType::DS && !name.isRoot() && signer == name) { |
baaa61a1 | 602 | VLOG_NO_PREFIX(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS"); |
48c54748 RG |
603 | return false; |
604 | } | |
605 | ||
a8cd5db5 | 606 | VLOG(log, ": done!" << endl); |
6c1fcbfe | 607 | ++d_nsec3Hits; |
0cbcfeda | 608 | res = RCode::NoError; |
779f35b4 | 609 | addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret); |
e9e8fbcd | 610 | addRecordToRRSet(exactNSEC3.d_owner, QType::NSEC3, exactNSEC3.d_ttd - now, exactNSEC3.d_record, exactNSEC3.d_signatures, doDNSSEC, ret); |
bf720981 RG |
611 | return true; |
612 | } | |
0cbcfeda | 613 | |
baaa61a1 | 614 | VLOG(log, name << ": No direct NSEC3 match found for " << nameHash << ", looking for closest encloser" << endl); |
bf720981 | 615 | DNSName closestEncloser(name); |
25f5783a | 616 | bool found = false; |
bf720981 | 617 | ZoneEntry::CacheEntry closestNSEC3; |
15e973d6 OM |
618 | auto remainingLabels = closestEncloser.countLabels() - 1; |
619 | while (!found && closestEncloser.chopOff() && remainingLabels >= zoneLabelsCount) { | |
620 | auto closestHash = DNSName(toBase32Hex(getHashFromNSEC3(closestEncloser, iterations, salt, validationContext))) + zone; | |
621 | remainingLabels--; | |
25f5783a | 622 | |
bf720981 | 623 | if (getNSEC3(now, zoneEntry, closestHash, closestNSEC3)) { |
baaa61a1 | 624 | VLOG(log, name << ": Found closest encloser at " << closestEncloser << " (" << closestHash << ")" << endl); |
684b0a56 | 625 | |
d06dcda4 | 626 | auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(closestNSEC3.d_record); |
684b0a56 | 627 | if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) { |
baaa61a1 | 628 | VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl); |
684b0a56 RG |
629 | break; |
630 | } | |
631 | ||
d270600b | 632 | const DNSName signer = getSigner(closestNSEC3.d_signatures); |
1eed7f45 RG |
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. */ | |
03e5e4cb | 635 | if (isNSEC3AncestorDelegation(signer, closestNSEC3.d_owner, *nsec3)) { |
d270600b RG |
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 | */ | |
baaa61a1 | 642 | VLOG_NO_PREFIX(log, " but this is an ancestor delegation NSEC3" << endl); |
d270600b RG |
643 | break; |
644 | } | |
645 | ||
ae727ae2 | 646 | if (type == QType::DS && !name.isRoot() && signer == name) { |
baaa61a1 | 647 | VLOG_NO_PREFIX(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS"); |
48c54748 RG |
648 | return false; |
649 | } | |
650 | ||
25f5783a RG |
651 | found = true; |
652 | break; | |
653 | } | |
654 | } | |
655 | ||
656 | if (!found) { | |
baaa61a1 | 657 | VLOG(log, name << ": Nothing found for the closest encloser in NSEC3 aggressive cache either" << endl); |
25f5783a RG |
658 | return false; |
659 | } | |
660 | ||
bf720981 | 661 | unsigned int labelIdx = name.countLabels() - closestEncloser.countLabels(); |
25f5783a RG |
662 | if (labelIdx < 1) { |
663 | return false; | |
664 | } | |
665 | ||
666 | DNSName nsecFound; | |
667 | DNSName nextCloser(closestEncloser); | |
bf720981 | 668 | nextCloser.prependRawLabel(name.getRawLabel(labelIdx - 1)); |
15e973d6 | 669 | auto nextCloserHash = toBase32Hex(getHashFromNSEC3(nextCloser, iterations, salt, validationContext)); |
baaa61a1 | 670 | VLOG(log, name << ": Looking for a NSEC3 covering the next closer " << nextCloser << " (" << nextCloserHash << ")" << endl); |
25f5783a | 671 | |
bf720981 RG |
672 | ZoneEntry::CacheEntry nextCloserEntry; |
673 | if (!getNSECBefore(now, zoneEntry, DNSName(nextCloserHash) + zone, nextCloserEntry)) { | |
baaa61a1 | 674 | VLOG(log, name << ": Nothing found for the next closer in NSEC3 aggressive cache" << endl); |
25f5783a RG |
675 | return false; |
676 | } | |
677 | ||
bf720981 | 678 | if (!isCoveredByNSEC3Hash(DNSName(nextCloserHash) + zone, nextCloserEntry.d_owner, nextCloserEntry.d_next)) { |
baaa61a1 | 679 | VLOG(log, name << ": No covering record found for the next closer in NSEC3 aggressive cache" << endl); |
bf720981 RG |
680 | return false; |
681 | } | |
682 | ||
d06dcda4 | 683 | auto nextCloserNsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(nextCloserEntry.d_record); |
684b0a56 | 684 | if (!nextCloserNsec3 || nextCloserNsec3->d_iterations != iterations || nextCloserNsec3->d_salt != salt) { |
baaa61a1 | 685 | VLOG(log, name << ": The NSEC3 covering the next closer is not valid, or has a different salt or iterations count, bailing out" << endl); |
684b0a56 RG |
686 | return false; |
687 | } | |
688 | ||
48c54748 | 689 | const DNSName nextCloserSigner = getSigner(nextCloserEntry.d_signatures); |
ae727ae2 | 690 | if (type == QType::DS && !name.isRoot() && nextCloserSigner == name) { |
a8cd5db5 | 691 | VLOG(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS"); |
48c54748 RG |
692 | return false; |
693 | } | |
694 | ||
1eed7f45 RG |
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). */ | |
25f5783a | 697 | DNSName wildcard(g_wildcarddnsname + closestEncloser); |
15e973d6 | 698 | auto wcHash = toBase32Hex(getHashFromNSEC3(wildcard, iterations, salt, validationContext)); |
baaa61a1 | 699 | VLOG(log, name << ": Looking for a NSEC3 covering the wildcard " << wildcard << " (" << wcHash << ")" << endl); |
25f5783a | 700 | |
bf720981 RG |
701 | ZoneEntry::CacheEntry wcEntry; |
702 | if (!getNSECBefore(now, zoneEntry, DNSName(wcHash) + zone, wcEntry)) { | |
baaa61a1 | 703 | VLOG(log, name << ": Nothing found for the wildcard in NSEC3 aggressive cache" << endl); |
25f5783a RG |
704 | return false; |
705 | } | |
706 | ||
bf720981 | 707 | if ((DNSName(wcHash) + zone) == wcEntry.d_owner) { |
baaa61a1 | 708 | VLOG(log, name << ": Found an exact match for the wildcard"); |
5a5c53b8 | 709 | |
d06dcda4 | 710 | auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(wcEntry.d_record); |
684b0a56 | 711 | if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) { |
baaa61a1 | 712 | VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl); |
bf720981 RG |
713 | return false; |
714 | } | |
25f5783a | 715 | |
d270600b | 716 | const DNSName wcSigner = getSigner(wcEntry.d_signatures); |
1eed7f45 RG |
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. */ | |
03e5e4cb | 719 | if (type != QType::DS && isNSEC3AncestorDelegation(wcSigner, wcEntry.d_owner, *nsec3)) { |
d270600b RG |
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 | */ | |
baaa61a1 | 726 | VLOG_NO_PREFIX(log, " but the NSEC3 covering the wildcard is an ancestor delegation NSEC3, bailing out" << endl); |
d270600b RG |
727 | return false; |
728 | } | |
729 | ||
ae727ae2 | 730 | if (type == QType::DS && !name.isRoot() && wcSigner == name) { |
baaa61a1 | 731 | VLOG_NO_PREFIX(log, " but this wildcard NSEC3 comes from the child zone and cannot be used to deny a DS"); |
48c54748 RG |
732 | return false; |
733 | } | |
734 | ||
03e5e4cb | 735 | if (!isTypeDenied(*nsec3, type)) { |
baaa61a1 | 736 | VLOG_NO_PREFIX(log, " but the requested type (" << type.toString() << ") does exist" << endl); |
10971f78 | 737 | return synthesizeFromNSEC3Wildcard(now, name, type, ret, res, doDNSSEC, nextCloserEntry, wildcard, log); |
bf720981 | 738 | } |
0cbcfeda RG |
739 | |
740 | res = RCode::NoError; | |
a8cd5db5 | 741 | VLOG(log, endl); |
bf720981 RG |
742 | } |
743 | else { | |
744 | if (!isCoveredByNSEC3Hash(DNSName(wcHash) + zone, wcEntry.d_owner, wcEntry.d_next)) { | |
baaa61a1 | 745 | VLOG(log, name << ": No covering record found for the wildcard in aggressive cache" << endl); |
bf720981 RG |
746 | return false; |
747 | } | |
684b0a56 | 748 | |
d06dcda4 | 749 | auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(wcEntry.d_record); |
684b0a56 | 750 | if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) { |
baaa61a1 | 751 | VLOG(log, name << ": The content of the NSEC3 covering the wildcard is not valid, or has a different salt or iterations count" << endl); |
684b0a56 RG |
752 | return false; |
753 | } | |
754 | ||
48c54748 | 755 | const DNSName wcSigner = getSigner(wcEntry.d_signatures); |
ae727ae2 | 756 | if (type == QType::DS && !name.isRoot() && wcSigner == name) { |
baaa61a1 | 757 | VLOG_NO_PREFIX(log, " but this wildcard NSEC3 comes from the child zone and cannot be used to deny a DS"); |
48c54748 RG |
758 | return false; |
759 | } | |
760 | ||
1eed7f45 RG |
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). */ | |
0cbcfeda | 763 | res = RCode::NXDomain; |
25f5783a RG |
764 | } |
765 | ||
779f35b4 | 766 | addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret); |
e9e8fbcd | 767 | addRecordToRRSet(closestNSEC3.d_owner, QType::NSEC3, closestNSEC3.d_ttd - now, closestNSEC3.d_record, closestNSEC3.d_signatures, doDNSSEC, ret); |
eca47c4d RG |
768 | |
769 | /* no need to include the same NSEC3 twice */ | |
770 | if (nextCloserEntry.d_owner != closestNSEC3.d_owner) { | |
e9e8fbcd | 771 | addRecordToRRSet(nextCloserEntry.d_owner, QType::NSEC3, nextCloserEntry.d_ttd - now, nextCloserEntry.d_record, nextCloserEntry.d_signatures, doDNSSEC, ret); |
eca47c4d RG |
772 | } |
773 | if (wcEntry.d_owner != closestNSEC3.d_owner && wcEntry.d_owner != nextCloserEntry.d_owner) { | |
8f67f0c2 | 774 | // coverity[store_truncates_time_t] |
e9e8fbcd | 775 | addRecordToRRSet(wcEntry.d_owner, QType::NSEC3, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret); |
eca47c4d | 776 | } |
bf720981 | 777 | |
baaa61a1 | 778 | VLOG(log, name << ": Found valid NSEC3s covering the requested name and type!" << endl); |
6c1fcbfe | 779 | ++d_nsec3Hits; |
bf720981 RG |
780 | return true; |
781 | } | |
782 | ||
15e973d6 | 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) |
bf720981 | 784 | { |
1f9eeeba | 785 | std::shared_ptr<LockGuarded<ZoneEntry>> zoneEntry; |
48c54748 RG |
786 | if (type == QType::DS) { |
787 | DNSName parent(name); | |
788 | parent.chopOff(); | |
789 | zoneEntry = getBestZone(parent); | |
790 | } | |
791 | else { | |
792 | zoneEntry = getBestZone(name); | |
793 | } | |
794 | ||
1f9eeeba | 795 | if (!zoneEntry) { |
25f5783a RG |
796 | return false; |
797 | } | |
798 | ||
1f9eeeba RG |
799 | DNSName zone; |
800 | bool nsec3; | |
801 | { | |
802 | auto entry = zoneEntry->try_lock(); | |
803 | if (!entry.owns_lock()) { | |
804 | return false; | |
805 | } | |
806 | if (entry->d_entries.empty()) { | |
807 | return false; | |
808 | } | |
809 | zone = entry->d_zone; | |
810 | nsec3 = entry->d_nsec3; | |
811 | } | |
812 | ||
25f5783a RG |
813 | vState cachedState; |
814 | std::vector<DNSRecord> soaSet; | |
d06dcda4 | 815 | std::vector<std::shared_ptr<const RRSIGRecordContent>> soaSignatures; |
684b0a56 | 816 | /* we might not actually need the SOA if we find a matching wildcard, but let's not bother for now */ |
171089f5 | 817 | if (g_recCache->get(now, zone, QType::SOA, MemRecursorCache::RequireAuth, &soaSet, who, routingTag, doDNSSEC ? &soaSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) { |
baaa61a1 | 818 | VLOG(log, name << ": No valid SOA found for " << zone << ", which is the best match for " << name << endl); |
25f5783a RG |
819 | return false; |
820 | } | |
821 | ||
1f9eeeba | 822 | if (nsec3) { |
15e973d6 | 823 | return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log, validationContext); |
25f5783a RG |
824 | } |
825 | ||
bf720981 RG |
826 | ZoneEntry::CacheEntry entry; |
827 | ZoneEntry::CacheEntry wcEntry; | |
828 | bool covered = false; | |
829 | bool needWildcard = false; | |
25f5783a | 830 | |
baaa61a1 | 831 | VLOG(log, name << ": Looking for a NSEC before " << name); |
bf720981 | 832 | if (!getNSECBefore(now, zoneEntry, name, entry)) { |
baaa61a1 | 833 | VLOG_NO_PREFIX(log, ": nothing found in the aggressive cache" << endl); |
25f5783a RG |
834 | return false; |
835 | } | |
836 | ||
d06dcda4 | 837 | auto content = std::dynamic_pointer_cast<const NSECRecordContent>(entry.d_record); |
bf720981 | 838 | if (!content) { |
25f5783a RG |
839 | return false; |
840 | } | |
841 | ||
baaa61a1 | 842 | VLOG_NO_PREFIX(log, ": found a possible NSEC at " << entry.d_owner << " "); |
1eed7f45 | 843 | // note that matchesNSEC() takes care of ruling out ancestor NSECs for us |
03e5e4cb | 844 | auto denial = matchesNSEC(name, type.getCode(), entry.d_owner, *content, entry.d_signatures, log); |
cd4beb37 | 845 | if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) { |
65a177b1 | 846 | VLOG_NO_PREFIX(log, " but it does not cover us" << endl); |
25f5783a RG |
847 | return false; |
848 | } | |
bf720981 | 849 | else if (denial == dState::NXQTYPE) { |
25f5783a | 850 | covered = true; |
baaa61a1 | 851 | VLOG_NO_PREFIX(log, " and it proves that the type does not exist" << endl); |
25f5783a RG |
852 | res = RCode::NoError; |
853 | } | |
854 | else if (denial == dState::NXDOMAIN) { | |
baaa61a1 | 855 | VLOG_NO_PREFIX(log, " and it proves that the name does not exist" << endl); |
505e343f RG |
856 | DNSName closestEncloser = getClosestEncloserFromNSEC(name, entry.d_owner, entry.d_next); |
857 | DNSName wc = g_wildcarddnsname + closestEncloser; | |
25f5783a | 858 | |
baaa61a1 | 859 | VLOG(log, name << ": Now looking for a NSEC before the wildcard " << wc); |
bf720981 | 860 | if (!getNSECBefore(now, zoneEntry, wc, wcEntry)) { |
baaa61a1 | 861 | VLOG_NO_PREFIX(log, ": nothing found in the aggressive cache" << endl); |
bf720981 RG |
862 | return false; |
863 | } | |
25f5783a | 864 | |
baaa61a1 | 865 | VLOG_NO_PREFIX(log, ": found a possible NSEC at " << wcEntry.d_owner << " "); |
505e343f | 866 | |
d06dcda4 | 867 | auto nsecContent = std::dynamic_pointer_cast<const NSECRecordContent>(wcEntry.d_record); |
5a5c53b8 | 868 | |
03e5e4cb | 869 | denial = matchesNSEC(wc, type.getCode(), wcEntry.d_owner, *nsecContent, wcEntry.d_signatures, log); |
5a5c53b8 | 870 | if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) { |
5a5c53b8 RG |
871 | |
872 | if (wcEntry.d_owner == wc) { | |
baaa61a1 | 873 | VLOG_NO_PREFIX(log, " proving that the wildcard does exist" << endl); |
10971f78 | 874 | return synthesizeFromNSECWildcard(now, name, type, ret, res, doDNSSEC, entry, wc, log); |
5a5c53b8 RG |
875 | } |
876 | ||
baaa61a1 | 877 | VLOG_NO_PREFIX(log, " but it does no cover us" << endl); |
505e343f | 878 | |
5a5c53b8 RG |
879 | return false; |
880 | } | |
881 | else if (denial == dState::NXQTYPE) { | |
baaa61a1 | 882 | VLOG_NO_PREFIX(log, " and it proves that there is a matching wildcard, but the type does not exist" << endl); |
5a5c53b8 RG |
883 | covered = true; |
884 | res = RCode::NoError; | |
885 | } | |
886 | else if (denial == dState::NXDOMAIN) { | |
baaa61a1 | 887 | VLOG_NO_PREFIX(log, " and it proves that there is no matching wildcard" << endl); |
bf720981 RG |
888 | covered = true; |
889 | res = RCode::NXDomain; | |
890 | } | |
0cbcfeda | 891 | |
5a5c53b8 RG |
892 | if (wcEntry.d_owner != wc && wcEntry.d_owner != entry.d_owner) { |
893 | needWildcard = true; | |
25f5783a RG |
894 | } |
895 | } | |
896 | ||
897 | if (!covered) { | |
898 | return false; | |
899 | } | |
900 | ||
bf720981 | 901 | ret.reserve(ret.size() + soaSet.size() + soaSignatures.size() + /* NSEC */ 1 + entry.d_signatures.size() + (needWildcard ? (/* NSEC */ 1 + wcEntry.d_signatures.size()) : 0)); |
25f5783a | 902 | |
4c5a50dc | 903 | addToRRSet(now, soaSet, std::move(soaSignatures), zone, doDNSSEC, ret); |
b90448f5 | 904 | // coverity[store_truncates_time_t] |
e9e8fbcd | 905 | addRecordToRRSet(entry.d_owner, QType::NSEC, entry.d_ttd - now, entry.d_record, entry.d_signatures, doDNSSEC, ret); |
25f5783a | 906 | |
bf720981 | 907 | if (needWildcard) { |
8f67f0c2 | 908 | // coverity[store_truncates_time_t] |
e9e8fbcd | 909 | addRecordToRRSet(wcEntry.d_owner, QType::NSEC, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret); |
25f5783a RG |
910 | } |
911 | ||
baaa61a1 | 912 | VLOG(log, name << ": Found valid NSECs covering the requested name and type!" << endl); |
6c1fcbfe | 913 | ++d_nsecHits; |
25f5783a RG |
914 | return true; |
915 | } | |
5586c462 | 916 | |
45c1026d | 917 | size_t AggressiveNSECCache::dumpToFile(std::unique_ptr<FILE, int (*)(FILE*)>& fp, const struct timeval& now) |
5586c462 RG |
918 | { |
919 | size_t ret = 0; | |
920 | ||
1f9eeeba RG |
921 | auto zones = d_zones.read_lock(); |
922 | zones->visit([&ret, now, &fp](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) { | |
5586c462 RG |
923 | if (!node.d_value) { |
924 | return; | |
925 | } | |
926 | ||
1f9eeeba RG |
927 | auto zone = node.d_value->lock(); |
928 | fprintf(fp.get(), "; Zone %s\n", zone->d_zone.toString().c_str()); | |
5586c462 | 929 | |
1f9eeeba | 930 | for (const auto& entry : zone->d_entries) { |
5586c462 RG |
931 | int64_t ttl = entry.d_ttd - now.tv_sec; |
932 | try { | |
1f9eeeba | 933 | fprintf(fp.get(), "%s %" PRId64 " IN %s %s\n", entry.d_owner.toString().c_str(), ttl, zone->d_nsec3 ? "NSEC3" : "NSEC", entry.d_record->getZoneRepresentation().c_str()); |
5586c462 RG |
934 | for (const auto& signature : entry.d_signatures) { |
935 | fprintf(fp.get(), "- RRSIG %s\n", signature->getZoneRepresentation().c_str()); | |
936 | } | |
937 | ++ret; | |
938 | } | |
939 | catch (const std::exception& e) { | |
1f9eeeba | 940 | fprintf(fp.get(), "; Error dumping record from zone %s: %s\n", zone->d_zone.toString().c_str(), e.what()); |
5586c462 RG |
941 | } |
942 | catch (...) { | |
1f9eeeba | 943 | fprintf(fp.get(), "; Error dumping record from zone %s\n", zone->d_zone.toString().c_str()); |
5586c462 RG |
944 | } |
945 | } | |
946 | }); | |
947 | ||
948 | return ret; | |
949 | } |