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