2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
25 #include "auth-packetcache.hh"
28 #include "cachecleaner.hh"
31 const unsigned int AuthPacketCache::s_mincleaninterval
, AuthPacketCache::s_maxcleaninterval
;
33 AuthPacketCache::AuthPacketCache(size_t mapsCount
): d_lastclean(time(nullptr))
35 d_maps
.resize(mapsCount
);
37 S
.declare("packetcache-hit", "Number of hits on the packet cache");
38 S
.declare("packetcache-miss", "Number of misses on the packet cache");
39 S
.declare("packetcache-size", "Number of entries in the packet cache");
40 S
.declare("deferred-packetcache-inserts","Amount of packet cache inserts that were deferred because of maintenance");
41 S
.declare("deferred-packetcache-lookup","Amount of packet cache lookups that were deferred because of maintenance");
43 d_statnumhit
=S
.getPointer("packetcache-hit");
44 d_statnummiss
=S
.getPointer("packetcache-miss");
45 d_statnumentries
=S
.getPointer("packetcache-size");
48 AuthPacketCache::~AuthPacketCache()
51 vector
<WriteLock
*> locks
;
52 for(auto& mc
: d_maps
) {
53 locks
.push_back(new WriteLock(&mc
.d_mut
));
55 for(auto wl
: locks
) {
63 bool AuthPacketCache::get(DNSPacket
& p
, DNSPacket
& cached
)
71 uint32_t hash
= canHashPacket(p
.getString());
76 time_t now
= time(nullptr);
77 auto& mc
= getMap(p
.qdomain
);
79 TryReadLock
rl(&mc
.d_mut
);
81 S
.inc("deferred-packetcache-lookup");
85 haveSomething
= getEntryLocked(mc
.d_map
, p
.getString(), hash
, p
.qdomain
, p
.qtype
.getCode(), p
.d_tcp
, now
, value
);
93 if(cached
.noparse(value
.c_str(), value
.size()) < 0) {
98 cached
.spoofQuestion(p
); // for correct case
99 cached
.qdomain
= p
.qdomain
;
100 cached
.qtype
= p
.qtype
;
105 bool AuthPacketCache::entryMatches(cmap_t::index
<HashTag
>::type::iterator
& iter
, const std::string
& query
, const DNSName
& qname
, uint16_t qtype
, bool tcp
)
107 return iter
->tcp
== tcp
&& iter
->qtype
== qtype
&& iter
->qname
== qname
&& queryMatches(iter
->query
, query
, qname
);
110 void AuthPacketCache::insert(DNSPacket
& q
, DNSPacket
& r
, unsigned int maxTTL
)
118 if (ntohs(q
.d
.qdcount
) != 1) {
119 return; // do not try to cache packets with multiple questions
122 if (q
.qclass
!= QClass::IN
) // we only cache the INternet
125 uint32_t ourttl
= std::min(d_ttl
, maxTTL
);
130 uint32_t hash
= q
.getHash();
131 time_t now
= time(nullptr);
135 entry
.ttd
= now
+ ourttl
;
136 entry
.qname
= q
.qdomain
;
137 entry
.qtype
= q
.qtype
.getCode();
138 entry
.value
= r
.getString();
140 entry
.query
= q
.getString();
142 auto& mc
= getMap(entry
.qname
);
144 TryWriteLock
l(&mc
.d_mut
);
146 S
.inc("deferred-packetcache-inserts");
150 auto& idx
= mc
.d_map
.get
<HashTag
>();
151 auto range
= idx
.equal_range(hash
);
152 auto iter
= range
.first
;
154 for( ; iter
!= range
.second
; ++iter
) {
155 if (!entryMatches(iter
, entry
.query
, entry
.qname
, entry
.qtype
, entry
.tcp
)) {
159 iter
->value
= entry
.value
;
160 iter
->ttd
= now
+ ourttl
;
165 /* no existing entry found to refresh */
166 mc
.d_map
.insert(entry
);
167 (*d_statnumentries
)++;
171 bool AuthPacketCache::getEntryLocked(cmap_t
& map
, const std::string
& query
, uint32_t hash
, const DNSName
&qname
, uint16_t qtype
, bool tcp
, time_t now
, string
& value
)
173 auto& idx
= map
.get
<HashTag
>();
174 auto range
= idx
.equal_range(hash
);
176 for(auto iter
= range
.first
; iter
!= range
.second
; ++iter
) {
177 if (iter
->ttd
< now
) {
181 if (!entryMatches(iter
, query
, qname
, qtype
, tcp
)) {
192 /* clears the entire cache. */
193 uint64_t AuthPacketCache::purge()
199 d_statnumentries
->store(0);
201 return purgeLockedCollectionsVector(d_maps
);
204 uint64_t AuthPacketCache::purgeExact(const DNSName
& qname
)
206 auto& mc
= getMap(qname
);
207 uint64_t delcount
= purgeExactLockedCollection
<NameTag
>(mc
, qname
);
209 *d_statnumentries
-= delcount
;
214 /* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
215 uint64_t AuthPacketCache::purge(const string
&match
)
221 uint64_t delcount
= 0;
223 if(ends_with(match
, "$")) {
224 delcount
= purgeLockedCollectionsVector
<NameTag
>(d_maps
, match
);
225 *d_statnumentries
-= delcount
;
228 delcount
= purgeExact(DNSName(match
));
234 void AuthPacketCache::cleanup()
236 uint64_t maxCached
= d_maxEntries
;
237 uint64_t cacheSize
= *d_statnumentries
;
238 uint64_t totErased
= 0;
240 totErased
= pruneLockedCollectionsVector
<SequencedTag
>(d_maps
, maxCached
, cacheSize
);
241 *d_statnumentries
-= totErased
;
243 DLOG(g_log
<<"Done with cache clean, cacheSize: "<<(*d_statnumentries
)<<", totErased"<<totErased
<<endl
);
247 after d_nextclean operations, we clean. We also adjust the cleaninterval
248 a bit so we slowly move it to a value where we clean roughly every 30 seconds.
250 If d_nextclean has reached its maximum value, we also test if we were called
251 within 30 seconds, and if so, we skip cleaning. This means that under high load,
252 we will not clean more often than every 30 seconds anyhow.
255 void AuthPacketCache::cleanupIfNeeded()
257 if (d_ops
++ == d_nextclean
) {
258 time_t now
= time(nullptr);
259 int timediff
= max((int)(now
- d_lastclean
), 1);
261 DLOG(g_log
<<"cleaninterval: "<<d_cleaninterval
<<", timediff: "<<timediff
<<endl
);
263 if (d_cleaninterval
== s_maxcleaninterval
&& timediff
< 30) {
264 d_cleanskipped
= true;
265 d_nextclean
+= d_cleaninterval
;
267 DLOG(g_log
<<"cleaning skipped, timediff: "<<timediff
<<endl
);
272 if(!d_cleanskipped
) {
273 d_cleaninterval
=(int)(0.6*d_cleaninterval
)+(0.4*d_cleaninterval
*(30.0/timediff
));
274 d_cleaninterval
=std::max(d_cleaninterval
, s_mincleaninterval
);
275 d_cleaninterval
=std::min(d_cleaninterval
, s_maxcleaninterval
);
277 DLOG(g_log
<<"new cleaninterval: "<<d_cleaninterval
<<endl
);
279 d_cleanskipped
= false;
282 d_nextclean
+= d_cleaninterval
;