]>
Commit | Line | Data |
---|---|---|
12c86877 | 1 | /* |
12471842 PL |
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 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
8e50cd4c | 25 | #include "utility.hh" |
12c86877 BH |
26 | #include "packetcache.hh" |
27 | #include "logger.hh" | |
28 | #include "arguments.hh" | |
29 | #include "statbag.hh" | |
30 | #include <map> | |
9e134196 | 31 | #include <boost/algorithm/string.hpp> |
12c86877 BH |
32 | |
33 | extern StatBag S; | |
34 | ||
35 | PacketCache::PacketCache() | |
36 | { | |
c3064e57 | 37 | d_ops=0; |
908c1491 | 38 | d_maps.resize(1024); |
46e4b77c | 39 | for(auto& mc : d_maps) { |
908c1491 | 40 | pthread_rwlock_init(&mc.d_mut, 0); |
41 | } | |
12c86877 BH |
42 | |
43 | d_ttl=-1; | |
44 | d_recursivettl=-1; | |
45 | ||
46 | S.declare("packetcache-hit"); | |
47 | S.declare("packetcache-miss"); | |
48 | S.declare("packetcache-size"); | |
49 | ||
dee7ba5a BH |
50 | d_statnumhit=S.getPointer("packetcache-hit"); |
51 | d_statnummiss=S.getPointer("packetcache-miss"); | |
52 | d_statnumentries=S.getPointer("packetcache-size"); | |
dfd3b427 | 53 | d_doRecursion=false; |
12c86877 BH |
54 | } |
55 | ||
3dbfa51e BH |
56 | PacketCache::~PacketCache() |
57 | { | |
908c1491 | 58 | // WriteLock l(&d_mut); |
59 | vector<WriteLock*> locks; | |
46e4b77c | 60 | for(auto& mc : d_maps) { |
908c1491 | 61 | locks.push_back(new WriteLock(&mc.d_mut)); |
62 | } | |
46e4b77c | 63 | for(auto wl : locks) { |
908c1491 | 64 | delete wl; |
65 | } | |
3dbfa51e BH |
66 | } |
67 | ||
908c1491 | 68 | |
69 | ||
75fde355 | 70 | int PacketCache::get(DNSPacket *p, DNSPacket *cached, bool recursive) |
12c86877 | 71 | { |
2667dc9a | 72 | extern StatBag S; |
12c86877 | 73 | |
2667dc9a BH |
74 | if(d_ttl<0) |
75 | getTTLS(); | |
12c86877 | 76 | |
ac0995bb | 77 | cleanupIfNeeded(); |
b2ac0df8 | 78 | |
2667dc9a BH |
79 | if(d_doRecursion && p->d.rd) { // wants recursion |
80 | if(!d_recursivettl) { | |
dee7ba5a | 81 | (*d_statnummiss)++; |
2667dc9a BH |
82 | return 0; |
83 | } | |
84 | } | |
85 | else { // does not | |
86 | if(!d_ttl) { | |
dee7ba5a | 87 | (*d_statnummiss)++; |
2667dc9a BH |
88 | return 0; |
89 | } | |
90 | } | |
91 | ||
2667dc9a BH |
92 | if(ntohs(p->d.qdcount)!=1) // we get confused by packets with more than one question |
93 | return 0; | |
94 | ||
75fde355 | 95 | unsigned int age=0; |
2f24bcd2 BH |
96 | string value; |
97 | bool haveSomething; | |
2667dc9a | 98 | { |
46e4b77c | 99 | auto& mc=getMap(p->qdomain); |
908c1491 | 100 | TryReadLock l(&mc.d_mut); // take a readlock here |
2667dc9a BH |
101 | if(!l.gotIt()) { |
102 | S.inc("deferred-cache-lookup"); | |
103 | return 0; | |
104 | } | |
105 | ||
9a8a1c8b | 106 | uint16_t maxReplyLen = p->d_tcp ? 0xffff : p->getMaxReplyLen(); |
8487d349 | 107 | haveSomething=getEntryLocked(p->qdomain, p->qtype, PacketCache::PACKETCACHE, value, -1, recursive, maxReplyLen, p->d_dnssecOk, p->hasEDNS(), &age); |
2f24bcd2 BH |
108 | } |
109 | if(haveSomething) { | |
110 | (*d_statnumhit)++; | |
75fde355 KM |
111 | if (recursive) |
112 | ageDNSPacket(value, age); | |
113 | if(cached->noparse(value.c_str(), value.size()) < 0) | |
2f24bcd2 | 114 | return 0; |
63e365db | 115 | cached->spoofQuestion(p); // for correct case |
9951e2d0 KM |
116 | cached->qdomain=p->qdomain; |
117 | cached->qtype=p->qtype; | |
2f24bcd2 | 118 | return 1; |
2667dc9a | 119 | } |
2f24bcd2 | 120 | |
15d02f9d | 121 | // cerr<<"Packet cache miss for '"<<p->qdomain<<"', merits: "<<packetMeritsRecursion<<endl; |
dee7ba5a | 122 | (*d_statnummiss)++; |
2667dc9a | 123 | return 0; // bummer |
12c86877 BH |
124 | } |
125 | ||
126 | void PacketCache::getTTLS() | |
127 | { | |
ba45c866 BH |
128 | d_ttl=::arg().asNum("cache-ttl"); |
129 | d_recursivettl=::arg().asNum("recursive-cache-ttl"); | |
12c86877 | 130 | |
ba45c866 | 131 | d_doRecursion=::arg().mustDo("recursor"); |
12c86877 BH |
132 | } |
133 | ||
2667dc9a | 134 | |
75fde355 | 135 | void PacketCache::insert(DNSPacket *q, DNSPacket *r, bool recursive, unsigned int maxttl) |
12c86877 | 136 | { |
2667dc9a | 137 | if(d_ttl < 0) |
12c86877 | 138 | getTTLS(); |
2667dc9a BH |
139 | |
140 | if(ntohs(q->d.qdcount)!=1) { | |
2667dc9a BH |
141 | return; // do not try to cache packets with multiple questions |
142 | } | |
12c86877 | 143 | |
adf13442 BH |
144 | if(q->qclass != QClass::IN) // we only cache the INternet |
145 | return; | |
146 | ||
9a8a1c8b | 147 | uint16_t maxReplyLen = q->d_tcp ? 0xffff : q->getMaxReplyLen(); |
75fde355 KM |
148 | unsigned int ourttl = recursive ? d_recursivettl : d_ttl; |
149 | if(!recursive) { | |
150 | if(maxttl<ourttl) | |
151 | ourttl=maxttl; | |
152 | } else { | |
153 | unsigned int minttl = r->getMinTTL(); | |
154 | if(minttl<ourttl) | |
155 | ourttl=minttl; | |
156 | } | |
8487d349 | 157 | insert(q->qdomain, q->qtype, PacketCache::PACKETCACHE, r->getString(), ourttl, -1, recursive, |
17d0b1e6 | 158 | maxReplyLen, q->d_dnssecOk, q->hasEDNS()); |
12c86877 BH |
159 | } |
160 | ||
ba45c866 | 161 | // universal key appears to be: qname, qtype, kind (packet, query cache), optionally zoneid, meritsRecursion |
675fa24c | 162 | void PacketCache::insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const string& value, unsigned int ttl, int zoneID, |
17d0b1e6 | 163 | bool meritsRecursion, unsigned int maxReplyLen, bool dnssecOk, bool EDNS) |
12c86877 | 164 | { |
ac0995bb | 165 | cleanupIfNeeded(); |
b2ac0df8 | 166 | |
12c86877 BH |
167 | if(!ttl) |
168 | return; | |
ba45c866 | 169 | |
17d0b1e6 | 170 | //cerr<<"Inserting qname '"<<qname<<"', cet: "<<(int)cet<<", qtype: "<<qtype.getName()<<", ttl: "<<ttl<<", maxreplylen: "<<maxReplyLen<<", hasEDNS: "<<EDNS<<endl; |
ba45c866 | 171 | CacheEntry val; |
75fde355 KM |
172 | val.created=time(0); |
173 | val.ttd=val.created+ttl; | |
478748c3 | 174 | val.qname=qname; |
ba45c866 BH |
175 | val.qtype=qtype.getCode(); |
176 | val.value=value; | |
177 | val.ctype=cet; | |
178 | val.meritsRecursion=meritsRecursion; | |
cf00d8d3 | 179 | val.maxReplyLen = maxReplyLen; |
a637d0a5 | 180 | val.dnssecOk = dnssecOk; |
771af01a | 181 | val.zoneID = zoneID; |
17d0b1e6 | 182 | val.hasEDNS = EDNS; |
a637d0a5 | 183 | |
46e4b77c | 184 | auto& mc = getMap(val.qname); |
185 | ||
908c1491 | 186 | TryWriteLock l(&mc.d_mut); |
ba45c866 BH |
187 | if(l.gotIt()) { |
188 | bool success; | |
189 | cmap_t::iterator place; | |
908c1491 | 190 | tie(place, success)=mc.d_map.insert(val); |
191 | ||
aee8cae2 | 192 | if(!success) |
908c1491 | 193 | mc.d_map.replace(place, val); |
ba45c866 | 194 | } |
12c86877 | 195 | else |
eefd15f9 | 196 | S.inc("deferred-cache-inserts"); |
12c86877 BH |
197 | } |
198 | ||
a3b6f8d0 | 199 | void PacketCache::insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const vector<DNSResourceRecord>& value, unsigned int ttl, int zoneID) |
200 | { | |
ac0995bb | 201 | cleanupIfNeeded(); |
a3b6f8d0 | 202 | |
203 | if(!ttl) | |
204 | return; | |
205 | ||
206 | //cerr<<"Inserting qname '"<<qname<<"', cet: "<<(int)cet<<", qtype: "<<qtype.getName()<<", ttl: "<<ttl<<", maxreplylen: "<<maxReplyLen<<", hasEDNS: "<<EDNS<<endl; | |
207 | CacheEntry val; | |
208 | val.created=time(0); | |
209 | val.ttd=val.created+ttl; | |
210 | val.qname=qname; | |
211 | val.qtype=qtype.getCode(); | |
212 | val.drs=value; | |
213 | val.ctype=cet; | |
214 | val.meritsRecursion=false; | |
215 | val.maxReplyLen = 0; | |
216 | val.dnssecOk = false; | |
217 | val.zoneID = zoneID; | |
218 | val.hasEDNS = false; | |
219 | ||
220 | auto& mc = getMap(val.qname); | |
221 | ||
222 | TryWriteLock l(&mc.d_mut); | |
223 | if(l.gotIt()) { | |
224 | bool success; | |
225 | cmap_t::iterator place; | |
226 | tie(place, success)=mc.d_map.insert(val); | |
227 | ||
228 | if(!success) | |
229 | mc.d_map.replace(place, val); | |
230 | } | |
231 | else | |
232 | S.inc("deferred-cache-inserts"); | |
233 | } | |
234 | ||
235 | ||
27fdc3fc BH |
236 | /* clears the entire packetcache. */ |
237 | int PacketCache::purge() | |
238 | { | |
908c1491 | 239 | int delcount=0; |
46e4b77c | 240 | for(auto& mc : d_maps) { |
908c1491 | 241 | WriteLock l(&mc.d_mut); |
242 | delcount+=mc.d_map.size(); | |
243 | mc.d_map.clear(); | |
244 | } | |
ac0995bb | 245 | d_statnumentries->store(0); |
27fdc3fc BH |
246 | return delcount; |
247 | } | |
248 | ||
be9d7339 CH |
249 | int PacketCache::purgeExact(const DNSName& qname) |
250 | { | |
251 | int delcount=0; | |
252 | auto& mc = getMap(qname); | |
253 | ||
254 | WriteLock l(&mc.d_mut); | |
255 | auto range = mc.d_map.equal_range(tie(qname)); | |
256 | if(range.first != range.second) { | |
257 | delcount+=distance(range.first, range.second); | |
258 | mc.d_map.erase(range.first, range.second); | |
259 | } | |
260 | *d_statnumentries-=delcount; // XXX FIXME NEEDS TO BE ADJUSTED (for packetcache shards) | |
261 | return delcount; | |
262 | } | |
263 | ||
27fdc3fc BH |
264 | /* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */ |
265 | int PacketCache::purge(const string &match) | |
12c86877 | 266 | { |
478748c3 | 267 | if(ends_with(match, "$")) { |
be9d7339 | 268 | int delcount=0; |
478748c3 | 269 | string prefix(match); |
270 | prefix.resize(prefix.size()-1); | |
271 | DNSName dprefix(prefix); | |
46e4b77c | 272 | for(auto& mc : d_maps) { |
478748c3 | 273 | WriteLock l(&mc.d_mut); |
274 | cmap_t::const_iterator iter = mc.d_map.lower_bound(tie(dprefix)); | |
275 | auto start=iter; | |
27fdc3fc | 276 | |
908c1491 | 277 | for(; iter != mc.d_map.end(); ++iter) { |
478748c3 | 278 | if(!iter->qname.isPartOf(dprefix)) { |
8487d349 KM |
279 | break; |
280 | } | |
281 | delcount++; | |
9e134196 | 282 | } |
908c1491 | 283 | mc.d_map.erase(start, iter); |
284 | } | |
be9d7339 CH |
285 | *d_statnumentries-=delcount; // XXX FIXME NEEDS TO BE ADJUSTED (for packetcache shards) |
286 | return delcount; | |
478748c3 | 287 | } |
288 | else { | |
be9d7339 | 289 | return purgeExact(DNSName(match)); |
9e134196 | 290 | } |
12c86877 | 291 | } |
a637d0a5 | 292 | // called from ueberbackend |
a3b6f8d0 | 293 | bool PacketCache::getEntry(const DNSName &qname, const QType& qtype, CacheEntryType cet, vector<DNSResourceRecord>& value, int zoneID) |
12c86877 | 294 | { |
b2ac0df8 BH |
295 | if(d_ttl<0) |
296 | getTTLS(); | |
297 | ||
ac0995bb | 298 | cleanupIfNeeded(); |
b2ac0df8 | 299 | |
46e4b77c | 300 | auto& mc=getMap(qname); |
908c1491 | 301 | |
302 | TryReadLock l(&mc.d_mut); // take a readlock here | |
12c86877 | 303 | if(!l.gotIt()) { |
eefd15f9 | 304 | S.inc( "deferred-cache-lookup"); |
12c86877 BH |
305 | return false; |
306 | } | |
a637d0a5 | 307 | |
a3b6f8d0 | 308 | return getEntryLocked(qname, qtype, cet, value, zoneID); |
b2ac0df8 BH |
309 | } |
310 | ||
a637d0a5 | 311 | |
675fa24c | 312 | bool PacketCache::getEntryLocked(const DNSName &qname, const QType& qtype, CacheEntryType cet, string& value, int zoneID, bool meritsRecursion, |
75fde355 | 313 | unsigned int maxReplyLen, bool dnssecOK, bool hasEDNS, unsigned int *age) |
b2ac0df8 | 314 | { |
ba45c866 | 315 | uint16_t qt = qtype.getCode(); |
9a8a1c8b | 316 | //cerr<<"Lookup for maxReplyLen: "<<maxReplyLen<<endl; |
46e4b77c | 317 | auto& mc=getMap(qname); |
478748c3 | 318 | cmap_t::const_iterator i=mc.d_map.find(tie(qname, qt, cet, zoneID, meritsRecursion, maxReplyLen, dnssecOK, hasEDNS, *age)); |
12c86877 | 319 | time_t now=time(0); |
908c1491 | 320 | bool ret=(i!=mc.d_map.end() && i->ttd > now); |
75fde355 KM |
321 | if(ret) { |
322 | if (age) | |
323 | *age = now - i->created; | |
ba45c866 | 324 | value = i->value; |
75fde355 | 325 | } |
a3b6f8d0 | 326 | |
327 | return ret; | |
328 | } | |
329 | ||
330 | bool PacketCache::getEntryLocked(const DNSName &qname, const QType& qtype, CacheEntryType cet, vector<DNSResourceRecord>& value, int zoneID) | |
331 | { | |
332 | uint16_t qt = qtype.getCode(); | |
333 | //cerr<<"Lookup for maxReplyLen: "<<maxReplyLen<<endl; | |
334 | auto& mc=getMap(qname); | |
335 | cmap_t::const_iterator i=mc.d_map.find(tie(qname, qt, cet, zoneID)); | |
336 | time_t now=time(0); | |
337 | bool ret=(i!=mc.d_map.end() && i->ttd > now); | |
338 | if(ret) { | |
339 | value = i->drs; | |
340 | } | |
12c86877 BH |
341 | return ret; |
342 | } | |
343 | ||
801812e6 | 344 | |
12c86877 BH |
345 | map<char,int> PacketCache::getCounts() |
346 | { | |
4b298c7f BH |
347 | int recursivePackets=0, nonRecursivePackets=0, queryCacheEntries=0, negQueryCacheEntries=0; |
348 | ||
46e4b77c | 349 | for(auto& mc : d_maps) { |
908c1491 | 350 | ReadLock l(&mc.d_mut); |
351 | ||
352 | for(cmap_t::const_iterator iter = mc.d_map.begin() ; iter != mc.d_map.end(); ++iter) { | |
353 | if(iter->ctype == PACKETCACHE) | |
354 | if(iter->meritsRecursion) | |
355 | recursivePackets++; | |
356 | else | |
357 | nonRecursivePackets++; | |
358 | else if(iter->ctype == QUERYCACHE) { | |
359 | if(iter->value.empty()) | |
360 | negQueryCacheEntries++; | |
361 | else | |
362 | queryCacheEntries++; | |
363 | } | |
2f24bcd2 | 364 | } |
4b298c7f | 365 | } |
908c1491 | 366 | map<char,int> ret; |
367 | ||
4b298c7f BH |
368 | ret['!']=negQueryCacheEntries; |
369 | ret['Q']=queryCacheEntries; | |
370 | ret['n']=nonRecursivePackets; | |
371 | ret['r']=recursivePackets; | |
12c86877 | 372 | return ret; |
12c86877 BH |
373 | } |
374 | ||
12c86877 BH |
375 | int PacketCache::size() |
376 | { | |
908c1491 | 377 | uint64_t ret=0; |
46e4b77c | 378 | for(auto& mc : d_maps) { |
908c1491 | 379 | ReadLock l(&mc.d_mut); |
380 | ret+=mc.d_map.size(); | |
381 | } | |
382 | return ret; | |
12c86877 BH |
383 | } |
384 | ||
385 | /** readlock for figuring out which iterators to delete, upgrade to writelock when actually cleaning */ | |
386 | void PacketCache::cleanup() | |
387 | { | |
ac0995bb | 388 | d_statnumentries->store(0); |
46e4b77c | 389 | for(auto& mc : d_maps) { |
908c1491 | 390 | ReadLock l(&mc.d_mut); |
12c86877 | 391 | |
908c1491 | 392 | *d_statnumentries+=mc.d_map.size(); |
393 | } | |
ba45c866 BH |
394 | unsigned int maxCached=::arg().asNum("max-cache-entries"); |
395 | unsigned int toTrim=0; | |
396 | ||
ac0995bb | 397 | unsigned long cacheSize=*d_statnumentries; |
908c1491 | 398 | |
ba45c866 BH |
399 | if(maxCached && cacheSize > maxCached) { |
400 | toTrim = cacheSize - maxCached; | |
401 | } | |
402 | ||
403 | unsigned int lookAt=0; | |
b2ac0df8 | 404 | // two modes - if toTrim is 0, just look through 10% of the cache and nuke everything that is expired |
ba45c866 BH |
405 | // otherwise, scan first 5*toTrim records, and stop once we've nuked enough |
406 | if(toTrim) | |
407 | lookAt=5*toTrim; | |
408 | else | |
409 | lookAt=cacheSize/10; | |
410 | ||
411 | // cerr<<"cacheSize: "<<cacheSize<<", lookAt: "<<lookAt<<", toTrim: "<<toTrim<<endl; | |
12c86877 | 412 | time_t now=time(0); |
12c86877 | 413 | DLOG(L<<"Starting cache clean"<<endl); |
20ac38f9 | 414 | //unsigned int totErased=0; |
46e4b77c | 415 | for(auto& mc : d_maps) { |
20ac38f9 | 416 | WriteLock wl(&mc.d_mut); |
908c1491 | 417 | typedef cmap_t::nth_index<1>::type sequence_t; |
418 | sequence_t& sidx=mc.d_map.get<1>(); | |
419 | unsigned int erased=0, lookedAt=0; | |
420 | for(sequence_t::iterator i=sidx.begin(); i != sidx.end(); lookedAt++) { | |
421 | if(i->ttd < now) { | |
422 | sidx.erase(i++); | |
423 | erased++; | |
424 | } | |
20ac38f9 | 425 | else { |
908c1491 | 426 | ++i; |
20ac38f9 | 427 | } |
12c86877 | 428 | |
908c1491 | 429 | if(toTrim && erased > toTrim / d_maps.size()) |
430 | break; | |
431 | ||
432 | if(lookedAt > lookAt / d_maps.size()) | |
433 | break; | |
434 | } | |
20ac38f9 | 435 | //totErased += erased; |
ba45c866 | 436 | } |
20ac38f9 | 437 | // if(totErased) |
438 | // cerr<<"erased: "<<totErased<<endl; | |
439 | ||
ac0995bb | 440 | d_statnumentries->store(0); |
46e4b77c | 441 | for(auto& mc : d_maps) { |
908c1491 | 442 | ReadLock l(&mc.d_mut); |
443 | *d_statnumentries+=mc.d_map.size(); | |
444 | } | |
445 | ||
12c86877 BH |
446 | DLOG(L<<"Done with cache clean"<<endl); |
447 | } |