]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/recursor_cache.cc
auth: switch circleci mssql image
[thirdparty/pdns.git] / pdns / recursor_cache.cc
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 #include <cinttypes>
6
7 #include "recursor_cache.hh"
8 #include "misc.hh"
9 #include <iostream>
10 #include "dnsrecords.hh"
11 #include "arguments.hh"
12 #include "syncres.hh"
13 #include "recursor_cache.hh"
14 #include "cachecleaner.hh"
15 #include "namespaces.hh"
16
17 unsigned int MemRecursorCache::size() const
18 {
19 return (unsigned int)d_cache.size();
20 }
21
22 size_t MemRecursorCache::ecsIndexSize() const
23 {
24 return d_ecsIndex.size();
25 }
26
27 // this function is too slow to poll!
28 unsigned int MemRecursorCache::bytes() const
29 {
30 unsigned int ret=0;
31
32 for(const auto& i : d_cache) {
33 ret+=sizeof(struct CacheEntry);
34 ret+=(unsigned int)i.d_qname.toString().length();
35 for(const auto& record : i.d_records)
36 ret+= sizeof(record); // XXX WRONG we don't know the stored size!
37 }
38 return ret;
39 }
40
41 int32_t MemRecursorCache::handleHit(MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, const ComboAddress& who, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth)
42 {
43 int32_t ttd = entry->d_ttd;
44
45 if(variable && !entry->d_netmask.empty()) {
46 *variable = true;
47 }
48
49 // cerr<<"Looking at "<<entry->d_records.size()<<" records for this name"<<endl;
50 if (res) {
51 res->reserve(res->size() + entry->d_records.size());
52
53 for(const auto& k : entry->d_records) {
54 DNSRecord dr;
55 dr.d_name = qname;
56 dr.d_type = entry->d_qtype;
57 dr.d_class = QClass::IN;
58 dr.d_content = k;
59 dr.d_ttl = static_cast<uint32_t>(entry->d_ttd);
60 dr.d_place = DNSResourceRecord::ANSWER;
61 res->push_back(std::move(dr));
62 }
63 }
64
65 if(signatures) { // if you do an ANY lookup you are hosed XXXX
66 *signatures = entry->d_signatures;
67 }
68
69 if(authorityRecs) {
70 *authorityRecs = entry->d_authorityRecs;
71 }
72
73 if (state) {
74 *state = entry->d_state;
75 }
76
77 if (wasAuth) {
78 *wasAuth = entry->d_auth;
79 }
80
81 moveCacheItemToBack(d_cache, entry);
82
83 return ttd;
84 }
85
86 MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSIndex(time_t now, const DNSName &qname, uint16_t qtype, bool requireAuth, const ComboAddress& who)
87 {
88 auto ecsIndexKey = tie(qname, qtype);
89 auto ecsIndex = d_ecsIndex.find(ecsIndexKey);
90 if (ecsIndex != d_ecsIndex.end() && !ecsIndex->isEmpty()) {
91 /* we have netmask-specific entries, let's see if we match one */
92 while (true) {
93 const Netmask best = ecsIndex->lookupBestMatch(who);
94 if (best.empty()) {
95 /* we have nothing more specific for you */
96 break;
97 }
98 auto key = boost::make_tuple(qname, qtype, best);
99 auto entry = d_cache.find(key);
100 if (entry == d_cache.end()) {
101 /* ecsIndex is not up-to-date */
102 ecsIndex->removeNetmask(best);
103 if (ecsIndex->isEmpty()) {
104 d_ecsIndex.erase(ecsIndex);
105 break;
106 }
107 continue;
108 }
109
110 if (entry->d_ttd > now) {
111 if (!requireAuth || entry->d_auth) {
112 return entry;
113 }
114 /* we need auth data and the best match is not authoritative */
115 return d_cache.end();
116 }
117 else {
118 /* this netmask-specific entry has expired */
119 moveCacheItemToFront(d_cache, entry);
120 ecsIndex->removeNetmask(best);
121 if (ecsIndex->isEmpty()) {
122 d_ecsIndex.erase(ecsIndex);
123 break;
124 }
125 }
126 }
127 }
128
129 /* we have nothing specific, let's see if we have a generic one */
130 auto key = boost::make_tuple(qname, qtype, Netmask());
131 auto entry = d_cache.find(key);
132 if (entry != d_cache.end()) {
133 if (entry->d_ttd > now) {
134 if (!requireAuth || entry->d_auth) {
135 return entry;
136 }
137 }
138 else {
139 moveCacheItemToFront(d_cache, entry);
140 }
141 }
142
143 /* nothing for you, sorry */
144 return d_cache.end();
145 }
146
147 // returns -1 for no hits
148 std::pair<MemRecursorCache::NameOnlyHashedTagIterator_t, MemRecursorCache::NameOnlyHashedTagIterator_t> MemRecursorCache::getEntries(const DNSName &qname, const QType& qt)
149 {
150 // cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n";
151 if(!d_cachecachevalid || d_cachedqname!= qname) {
152 // cerr<<"had cache cache miss"<<endl;
153 d_cachedqname = qname;
154 const auto& idx = d_cache.get<NameOnlyHashedTag>();
155 d_cachecache = idx.equal_range(qname);
156 d_cachecachevalid = true;
157 }
158 // else cerr<<"had cache cache hit!"<<endl;
159
160 return d_cachecache;
161 }
162
163 bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entry, uint16_t qt, bool requireAuth, const ComboAddress& who)
164 {
165 if (requireAuth && !entry->d_auth)
166 return false;
167
168 return ((entry->d_qtype == qt || qt == QType::ANY ||
169 (qt == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA)))
170 && (entry->d_netmask.empty() || entry->d_netmask.match(who)));
171 }
172
173 // returns -1 for no hits
174 int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth)
175 {
176 time_t ttd=0;
177 // cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n";
178 if(res) {
179 res->clear();
180 }
181
182 const uint16_t qtype = qt.getCode();
183 /* If we don't have any netmask-specific entries at all, let's just skip this
184 to be able to use the nice d_cachecache hack. */
185 if (qtype != QType::ANY && !d_ecsIndex.empty()) {
186 if (qtype == QType::ADDR) {
187 int32_t ret = -1;
188
189 auto entryA = getEntryUsingECSIndex(now, qname, QType::A, requireAuth, who);
190 if (entryA != d_cache.end()) {
191 ret = handleHit(entryA, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
192 }
193 auto entryAAAA = getEntryUsingECSIndex(now, qname, QType::AAAA, requireAuth, who);
194 if (entryAAAA != d_cache.end()) {
195 int32_t ttdAAAA = handleHit(entryAAAA, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
196 if (ret > 0) {
197 ret = std::min(ret, ttdAAAA);
198 } else {
199 ret = ttdAAAA;
200 }
201 }
202 return ret > 0 ? static_cast<int32_t>(ret-now) : ret;
203 }
204 else {
205 auto entry = getEntryUsingECSIndex(now, qname, qtype, requireAuth, who);
206 if (entry != d_cache.end()) {
207 return static_cast<int32_t>(handleHit(entry, qname, who, res, signatures, authorityRecs, variable, state, wasAuth) - now);
208 }
209 return -1;
210 }
211 }
212
213 auto entries = getEntries(qname, qt);
214
215 if(entries.first!=entries.second) {
216 for(auto i=entries.first; i != entries.second; ++i) {
217
218 auto firstIndexIterator = d_cache.project<OrderedTag>(i);
219 if (i->d_ttd <= now) {
220 moveCacheItemToFront(d_cache, firstIndexIterator);
221 continue;
222 }
223
224 if (!entryMatches(firstIndexIterator, qtype, requireAuth, who))
225 continue;
226
227 ttd = handleHit(firstIndexIterator, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
228
229 if(qt.getCode()!=QType::ANY && qt.getCode()!=QType::ADDR) // normally if we have a hit, we are done
230 break;
231 }
232
233 // cerr<<"time left : "<<ttd - now<<", "<< (res ? res->size() : 0) <<"\n";
234 return static_cast<int32_t>(ttd-now);
235 }
236 return -1;
237 }
238
239 void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, boost::optional<Netmask> ednsmask, vState state)
240 {
241 d_cachecachevalid = false;
242 // cerr<<"Replacing "<<qname<<" for "<< (ednsmask ? ednsmask->toString() : "everyone") << endl;
243 auto key = boost::make_tuple(qname, qt.getCode(), ednsmask ? *ednsmask : Netmask());
244 bool isNew = false;
245 cache_t::iterator stored = d_cache.find(key);
246 if (stored == d_cache.end()) {
247 stored = d_cache.insert(CacheEntry(key, auth)).first;
248 isNew = true;
249 }
250
251 /* if we are inserting a new entry or updating an expired one (in which case the
252 ECS index might have been removed but the entry still exists because it has not
253 been garbage collected yet) we might need to update the ECS index.
254 Otherwise it should already be indexed and we don't need to update it.
255 */
256 if (isNew || stored->d_ttd <= now) {
257 /* don't bother building an ecsIndex if we don't have any netmask-specific entries */
258 if (ednsmask && !ednsmask->empty()) {
259 auto ecsIndexKey = boost::make_tuple(qname, qt.getCode());
260 auto ecsIndex = d_ecsIndex.find(ecsIndexKey);
261 if (ecsIndex == d_ecsIndex.end()) {
262 ecsIndex = d_ecsIndex.insert(ECSIndexEntry(qname, qt.getCode())).first;
263 }
264 ecsIndex->addMask(*ednsmask);
265 }
266 }
267
268 time_t maxTTD=std::numeric_limits<time_t>::max();
269 CacheEntry ce=*stored; // this is a COPY
270 ce.d_qtype=qt.getCode();
271 ce.d_signatures=signatures;
272 ce.d_authorityRecs=authorityRecs;
273 ce.d_state=state;
274
275 // cerr<<"asked to store "<< (qname.empty() ? "EMPTY" : qname.toString()) <<"|"+qt.getName()<<" -> '";
276 // cerr<<(content.empty() ? string("EMPTY CONTENT") : content.begin()->d_content->getZoneRepresentation())<<"', auth="<<auth<<", ce.auth="<<ce.d_auth;
277 // cerr<<", ednsmask: " << (ednsmask ? ednsmask->toString() : "none") <<endl;
278
279 if(!auth && ce.d_auth) { // unauth data came in, we have some auth data, but is it fresh?
280 if(ce.d_ttd > now) { // we still have valid data, ignore unauth data
281 // cerr<<"\tStill hold valid auth data, and the new data is unauth, return\n";
282 return;
283 }
284 else {
285 ce.d_auth = false; // new data won't be auth
286 }
287 }
288
289 // refuse any attempt to *raise* the TTL of auth NS records, as it would make it possible
290 // for an auth to keep a "ghost" zone alive forever, even after the delegation is gone from
291 // the parent
292 // BUT make sure that we CAN refresh the root
293 if(ce.d_auth && auth && qt.getCode()==QType::NS && !isNew && !qname.isRoot()) {
294 // cerr<<"\tLimiting TTL of auth->auth NS set replace to "<<ce.d_ttd<<endl;
295 maxTTD = ce.d_ttd;
296 }
297
298 if(auth) {
299 ce.d_auth = true;
300 }
301
302 ce.d_records.clear();
303 ce.d_records.reserve(content.size());
304
305 for(const auto i : content) {
306 /* Yes, we have altered the d_ttl value by adding time(nullptr) to it
307 prior to calling this function, so the TTL actually holds a TTD. */
308 ce.d_ttd=min(maxTTD, static_cast<time_t>(i.d_ttl)); // XXX this does weird things if TTLs differ in the set
309 // cerr<<"To store: "<<i.d_content->getZoneRepresentation()<<" with ttl/ttd "<<i.d_ttl<<", capped at: "<<maxTTD<<endl;
310 ce.d_records.push_back(i.d_content);
311 }
312
313 if (!isNew) {
314 moveCacheItemToBack(d_cache, stored);
315 }
316 d_cache.replace(stored, ce);
317 }
318
319 int MemRecursorCache::doWipeCache(const DNSName& name, bool sub, uint16_t qtype)
320 {
321 int count=0;
322 d_cachecachevalid=false;
323
324 if(!sub) {
325 auto& idx = d_cache.get<NameOnlyHashedTag>();
326 auto range = idx.equal_range(name);
327 for(auto& i=range.first; i != range.second; ) {
328 if (qtype == 0xffff || i->d_qtype == qtype) {
329 count++;
330 idx.erase(i++);
331 }
332 else {
333 ++i;
334 }
335 }
336 if (qtype == 0xffff) {
337 auto& ecsIdx = d_ecsIndex.get<OrderedTag>();
338 auto ecsIndexRange = ecsIdx.equal_range(name);
339 for(auto i = ecsIndexRange.first; i != ecsIndexRange.second; ) {
340 ecsIdx.erase(i++);
341 }
342 }
343 else {
344 auto& ecsIdx = d_ecsIndex.get<HashedTag>();
345 auto ecsIndexRange = ecsIdx.equal_range(tie(name, qtype));
346 for(auto i = ecsIndexRange.first; i != ecsIndexRange.second; ) {
347 ecsIdx.erase(i++);
348 }
349 }
350 }
351 else {
352 auto& idx = d_cache.get<OrderedTag>();
353 auto& ecsIdx = d_ecsIndex.get<OrderedTag>();
354
355 for(auto iter = idx.lower_bound(name); iter != idx.end(); ) {
356 if(!iter->d_qname.isPartOf(name))
357 break;
358 if(iter->d_qtype == qtype || qtype == 0xffff) {
359 count++;
360 idx.erase(iter++);
361 }
362 else
363 iter++;
364 }
365 for(auto iter = ecsIdx.lower_bound(name); iter != ecsIdx.end(); ) {
366 if(!iter->d_qname.isPartOf(name))
367 break;
368 if(iter->d_qtype == qtype || qtype == 0xffff) {
369 ecsIdx.erase(iter++);
370 }
371 else {
372 iter++;
373 }
374 }
375 }
376 return count;
377 }
378
379 bool MemRecursorCache::doAgeCache(time_t now, const DNSName& name, uint16_t qtype, uint32_t newTTL)
380 {
381 cache_t::iterator iter = d_cache.find(tie(name, qtype));
382 if(iter == d_cache.end()) {
383 return false;
384 }
385
386 CacheEntry ce = *iter;
387 if(ce.d_ttd < now)
388 return false; // would be dead anyhow
389
390 uint32_t maxTTL = static_cast<uint32_t>(ce.d_ttd - now);
391 if(maxTTL > newTTL) {
392 d_cachecachevalid=false;
393
394 time_t newTTD = now + newTTL;
395
396
397 if(ce.d_ttd > newTTD) // do never renew expired or older TTLs
398 ce.d_ttd = newTTD;
399
400
401 d_cache.replace(iter, ce);
402 return true;
403 }
404 return false;
405 }
406
407 bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState, boost::optional<time_t> capTTD)
408 {
409 bool updated = false;
410 uint16_t qtype = qt.getCode();
411 if (qtype != QType::ANY && qtype != QType::ADDR && !d_ecsIndex.empty()) {
412 auto entry = getEntryUsingECSIndex(now, qname, qtype, requireAuth, who);
413 if (entry == d_cache.end()) {
414 return false;
415 }
416
417 entry->d_state = newState;
418 if (capTTD) {
419 entry->d_ttd = std::min(entry->d_ttd, *capTTD);
420 }
421 return true;
422 }
423
424 auto entries = getEntries(qname, qt);
425
426 for(auto i = entries.first; i != entries.second; ++i) {
427 auto firstIndexIterator = d_cache.project<OrderedTag>(i);
428
429 if (!entryMatches(firstIndexIterator, qtype, requireAuth, who))
430 continue;
431
432 i->d_state = newState;
433 if (capTTD) {
434 i->d_ttd = std::min(i->d_ttd, *capTTD);
435 }
436 updated = true;
437
438 if(qtype != QType::ANY && qtype != QType::ADDR) // normally if we have a hit, we are done
439 break;
440 }
441
442 return updated;
443 }
444
445 uint64_t MemRecursorCache::doDump(int fd)
446 {
447 auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
448 if(!fp) { // dup probably failed
449 return 0;
450 }
451 fprintf(fp.get(), "; main record cache dump from thread follows\n;\n");
452 const auto& sidx=d_cache.get<SequencedTag>();
453
454 uint64_t count=0;
455 time_t now=time(0);
456 for(const auto i : sidx) {
457 for(const auto j : i.d_records) {
458 count++;
459 try {
460 fprintf(fp.get(), "%s %" PRId64 " IN %s %s ; (%s) auth=%i %s\n", i.d_qname.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), DNSRecordContent::NumberToType(i.d_qtype).c_str(), j->getZoneRepresentation().c_str(), vStates[i.d_state], i.d_auth, i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
461 }
462 catch(...) {
463 fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
464 }
465 }
466 for(const auto &sig : i.d_signatures) {
467 count++;
468 try {
469 fprintf(fp.get(), "%s %" PRId64 " IN RRSIG %s ; %s\n", i.d_qname.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), sig->getZoneRepresentation().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
470 }
471 catch(...) {
472 fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
473 }
474 }
475 }
476 return count;
477 }
478
479 void MemRecursorCache::doPrune(unsigned int keep)
480 {
481 d_cachecachevalid=false;
482
483 pruneCollection(*this, d_cache, keep);
484 }
485