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_maps(mapsCount
), d_lastclean(time(nullptr))
35 S
.declare("packetcache-hit", "Number of hits on the packet cache");
36 S
.declare("packetcache-miss", "Number of misses on the packet cache");
37 S
.declare("packetcache-size", "Number of entries in the packet cache");
38 S
.declare("deferred-packetcache-inserts","Amount of packet cache inserts that were deferred because of maintenance");
39 S
.declare("deferred-packetcache-lookup","Amount of packet cache lookups that were deferred because of maintenance");
41 d_statnumhit
=S
.getPointer("packetcache-hit");
42 d_statnummiss
=S
.getPointer("packetcache-miss");
43 d_statnumentries
=S
.getPointer("packetcache-size");
46 AuthPacketCache::~AuthPacketCache()
49 vector
<WriteLock
*> locks
;
50 for(auto& mc
: d_maps
) {
51 locks
.push_back(new WriteLock(&mc
.d_mut
));
53 for(auto wl
: locks
) {
61 bool AuthPacketCache::get(DNSPacket
& p
, DNSPacket
& cached
)
69 uint32_t hash
= canHashPacket(p
.getString());
74 time_t now
= time(nullptr);
75 auto& mc
= getMap(p
.qdomain
);
77 TryReadLock
rl(&mc
.d_mut
);
79 S
.inc("deferred-packetcache-lookup");
83 haveSomething
= getEntryLocked(mc
.d_map
, p
.getString(), hash
, p
.qdomain
, p
.qtype
.getCode(), p
.d_tcp
, now
, value
);
91 if(cached
.noparse(value
.c_str(), value
.size()) < 0) {
96 cached
.spoofQuestion(p
); // for correct case
97 cached
.qdomain
= p
.qdomain
;
98 cached
.qtype
= p
.qtype
;
103 bool AuthPacketCache::entryMatches(cmap_t::index
<HashTag
>::type::iterator
& iter
, const std::string
& query
, const DNSName
& qname
, uint16_t qtype
, bool tcp
)
105 return iter
->tcp
== tcp
&& iter
->qtype
== qtype
&& iter
->qname
== qname
&& queryMatches(iter
->query
, query
, qname
);
108 void AuthPacketCache::insert(DNSPacket
& q
, DNSPacket
& r
, unsigned int maxTTL
)
116 if (ntohs(q
.d
.qdcount
) != 1) {
117 return; // do not try to cache packets with multiple questions
120 if (q
.qclass
!= QClass::IN
) // we only cache the INternet
123 uint32_t ourttl
= std::min(d_ttl
, maxTTL
);
128 uint32_t hash
= q
.getHash();
129 time_t now
= time(nullptr);
133 entry
.ttd
= now
+ ourttl
;
134 entry
.qname
= q
.qdomain
;
135 entry
.qtype
= q
.qtype
.getCode();
136 entry
.value
= r
.getString();
138 entry
.query
= q
.getString();
140 auto& mc
= getMap(entry
.qname
);
142 TryWriteLock
l(&mc
.d_mut
);
144 S
.inc("deferred-packetcache-inserts");
148 auto& idx
= mc
.d_map
.get
<HashTag
>();
149 auto range
= idx
.equal_range(hash
);
150 auto iter
= range
.first
;
152 for( ; iter
!= range
.second
; ++iter
) {
153 if (!entryMatches(iter
, entry
.query
, entry
.qname
, entry
.qtype
, entry
.tcp
)) {
157 iter
->value
= entry
.value
;
158 iter
->ttd
= now
+ ourttl
;
163 /* no existing entry found to refresh */
164 mc
.d_map
.insert(entry
);
165 (*d_statnumentries
)++;
169 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
)
171 auto& idx
= map
.get
<HashTag
>();
172 auto range
= idx
.equal_range(hash
);
174 for(auto iter
= range
.first
; iter
!= range
.second
; ++iter
) {
175 if (iter
->ttd
< now
) {
179 if (!entryMatches(iter
, query
, qname
, qtype
, tcp
)) {
190 /* clears the entire cache. */
191 uint64_t AuthPacketCache::purge()
197 d_statnumentries
->store(0);
199 return purgeLockedCollectionsVector(d_maps
);
202 uint64_t AuthPacketCache::purgeExact(const DNSName
& qname
)
204 auto& mc
= getMap(qname
);
205 uint64_t delcount
= purgeExactLockedCollection
<NameTag
>(mc
, qname
);
207 *d_statnumentries
-= delcount
;
212 /* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
213 uint64_t AuthPacketCache::purge(const string
&match
)
219 uint64_t delcount
= 0;
221 if(ends_with(match
, "$")) {
222 delcount
= purgeLockedCollectionsVector
<NameTag
>(d_maps
, match
);
223 *d_statnumentries
-= delcount
;
226 delcount
= purgeExact(DNSName(match
));
232 void AuthPacketCache::cleanup()
234 uint64_t maxCached
= d_maxEntries
;
235 uint64_t cacheSize
= *d_statnumentries
;
236 uint64_t totErased
= 0;
238 totErased
= pruneLockedCollectionsVector
<SequencedTag
>(d_maps
, maxCached
, cacheSize
);
239 *d_statnumentries
-= totErased
;
241 DLOG(g_log
<<"Done with cache clean, cacheSize: "<<(*d_statnumentries
)<<", totErased"<<totErased
<<endl
);
245 after d_nextclean operations, we clean. We also adjust the cleaninterval
246 a bit so we slowly move it to a value where we clean roughly every 30 seconds.
248 If d_nextclean has reached its maximum value, we also test if we were called
249 within 30 seconds, and if so, we skip cleaning. This means that under high load,
250 we will not clean more often than every 30 seconds anyhow.
253 void AuthPacketCache::cleanupIfNeeded()
255 if (d_ops
++ == d_nextclean
) {
256 time_t now
= time(nullptr);
257 int timediff
= max((int)(now
- d_lastclean
), 1);
259 DLOG(g_log
<<"cleaninterval: "<<d_cleaninterval
<<", timediff: "<<timediff
<<endl
);
261 if (d_cleaninterval
== s_maxcleaninterval
&& timediff
< 30) {
262 d_cleanskipped
= true;
263 d_nextclean
+= d_cleaninterval
;
265 DLOG(g_log
<<"cleaning skipped, timediff: "<<timediff
<<endl
);
270 if(!d_cleanskipped
) {
271 d_cleaninterval
=(int)(0.6*d_cleaninterval
)+(0.4*d_cleaninterval
*(30.0/timediff
));
272 d_cleaninterval
=std::max(d_cleaninterval
, s_mincleaninterval
);
273 d_cleaninterval
=std::min(d_cleaninterval
, s_maxcleaninterval
);
275 DLOG(g_log
<<"new cleaninterval: "<<d_cleaninterval
<<endl
);
277 d_cleanskipped
= false;
280 d_nextclean
+= d_cleaninterval
;