]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/auth-packetcache.cc
Merge pull request #8066 from franklouwers/master
[thirdparty/pdns.git] / pdns / auth-packetcache.cc
1 /*
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 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "auth-packetcache.hh"
26 #include "logger.hh"
27 #include "statbag.hh"
28 #include "cachecleaner.hh"
29 extern StatBag S;
30
31 const unsigned int AuthPacketCache::s_mincleaninterval, AuthPacketCache::s_maxcleaninterval;
32
33 AuthPacketCache::AuthPacketCache(size_t mapsCount): d_lastclean(time(nullptr))
34 {
35 d_maps.resize(mapsCount);
36 for(auto& mc : d_maps) {
37 pthread_rwlock_init(&mc.d_mut, 0);
38 }
39
40 S.declare("packetcache-hit", "Number of hits on the packet cache");
41 S.declare("packetcache-miss", "Number of misses on the packet cache");
42 S.declare("packetcache-size", "Number of entries in the packet cache");
43 S.declare("deferred-packetcache-inserts","Amount of packet cache inserts that were deferred because of maintenance");
44 S.declare("deferred-packetcache-lookup","Amount of packet cache lookups that were deferred because of maintenance");
45
46 d_statnumhit=S.getPointer("packetcache-hit");
47 d_statnummiss=S.getPointer("packetcache-miss");
48 d_statnumentries=S.getPointer("packetcache-size");
49 }
50
51 AuthPacketCache::~AuthPacketCache()
52 {
53 try {
54 vector<WriteLock*> locks;
55 for(auto& mc : d_maps) {
56 locks.push_back(new WriteLock(&mc.d_mut));
57 }
58 for(auto wl : locks) {
59 delete wl;
60 }
61 }
62 catch(...) {
63 }
64 }
65
66 bool AuthPacketCache::get(DNSPacket *p, DNSPacket *cached)
67 {
68 if(!d_ttl) {
69 return false;
70 }
71
72 cleanupIfNeeded();
73
74 uint32_t hash = canHashPacket(p->getString());
75 p->setHash(hash);
76
77 string value;
78 bool haveSomething;
79 time_t now = time(nullptr);
80 auto& mc = getMap(p->qdomain);
81 {
82 TryReadLock rl(&mc.d_mut);
83 if(!rl.gotIt()) {
84 S.inc("deferred-packetcache-lookup");
85 return false;
86 }
87
88 haveSomething = getEntryLocked(mc.d_map, p->getString(), hash, p->qdomain, p->qtype.getCode(), p->d_tcp, now, value);
89 }
90
91 if (!haveSomething) {
92 (*d_statnummiss)++;
93 return false;
94 }
95
96 if(cached->noparse(value.c_str(), value.size()) < 0) {
97 return false;
98 }
99
100 (*d_statnumhit)++;
101 cached->spoofQuestion(p); // for correct case
102 cached->qdomain = p->qdomain;
103 cached->qtype = p->qtype;
104
105 return true;
106 }
107
108 bool AuthPacketCache::entryMatches(cmap_t::index<HashTag>::type::iterator& iter, const std::string& query, const DNSName& qname, uint16_t qtype, bool tcp)
109 {
110 return iter->tcp == tcp && iter->qtype == qtype && iter->qname == qname && queryMatches(iter->query, query, qname);
111 }
112
113 void AuthPacketCache::insert(DNSPacket *q, DNSPacket *r, unsigned int maxTTL)
114 {
115 if(!d_ttl) {
116 return;
117 }
118
119 cleanupIfNeeded();
120
121 if (ntohs(q->d.qdcount) != 1) {
122 return; // do not try to cache packets with multiple questions
123 }
124
125 if (q->qclass != QClass::IN) // we only cache the INternet
126 return;
127
128 uint32_t ourttl = std::min(d_ttl, maxTTL);
129 if (ourttl == 0) {
130 return;
131 }
132
133 uint32_t hash = q->getHash();
134 time_t now = time(nullptr);
135 CacheEntry entry;
136 entry.hash = hash;
137 entry.created = now;
138 entry.ttd = now + ourttl;
139 entry.qname = q->qdomain;
140 entry.qtype = q->qtype.getCode();
141 entry.value = r->getString();
142 entry.tcp = r->d_tcp;
143 entry.query = q->getString();
144
145 auto& mc = getMap(entry.qname);
146 {
147 TryWriteLock l(&mc.d_mut);
148 if (!l.gotIt()) {
149 S.inc("deferred-packetcache-inserts");
150 return;
151 }
152
153 auto& idx = mc.d_map.get<HashTag>();
154 auto range = idx.equal_range(hash);
155 auto iter = range.first;
156
157 for( ; iter != range.second ; ++iter) {
158 if (!entryMatches(iter, entry.query, entry.qname, entry.qtype, entry.tcp)) {
159 continue;
160 }
161
162 iter->value = entry.value;
163 iter->ttd = now + ourttl;
164 iter->created = now;
165 return;
166 }
167
168 /* no existing entry found to refresh */
169 mc.d_map.insert(entry);
170 (*d_statnumentries)++;
171 }
172 }
173
174 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)
175 {
176 auto& idx = map.get<HashTag>();
177 auto range = idx.equal_range(hash);
178
179 for(auto iter = range.first; iter != range.second ; ++iter) {
180 if (iter->ttd < now) {
181 continue;
182 }
183
184 if (!entryMatches(iter, query, qname, qtype, tcp)) {
185 continue;
186 }
187
188 value = iter->value;
189 return true;
190 }
191
192 return false;
193 }
194
195 /* clears the entire cache. */
196 uint64_t AuthPacketCache::purge()
197 {
198 if(!d_ttl) {
199 return 0;
200 }
201
202 d_statnumentries->store(0);
203
204 return purgeLockedCollectionsVector(d_maps);
205 }
206
207 uint64_t AuthPacketCache::purgeExact(const DNSName& qname)
208 {
209 auto& mc = getMap(qname);
210 uint64_t delcount = purgeExactLockedCollection(mc, qname);
211
212 *d_statnumentries -= delcount;
213
214 return delcount;
215 }
216
217 /* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
218 uint64_t AuthPacketCache::purge(const string &match)
219 {
220 if(!d_ttl) {
221 return 0;
222 }
223
224 uint64_t delcount = 0;
225
226 if(ends_with(match, "$")) {
227 delcount = purgeLockedCollectionsVector(d_maps, match);
228 *d_statnumentries -= delcount;
229 }
230 else {
231 delcount = purgeExact(DNSName(match));
232 }
233
234 return delcount;
235 }
236
237 void AuthPacketCache::cleanup()
238 {
239 uint64_t maxCached = d_maxEntries;
240 uint64_t cacheSize = *d_statnumentries;
241 uint64_t totErased = 0;
242
243 totErased = pruneLockedCollectionsVector(d_maps, maxCached, cacheSize);
244 *d_statnumentries -= totErased;
245
246 DLOG(g_log<<"Done with cache clean, cacheSize: "<<(*d_statnumentries)<<", totErased"<<totErased<<endl);
247 }
248
249 /* the logic:
250 after d_nextclean operations, we clean. We also adjust the cleaninterval
251 a bit so we slowly move it to a value where we clean roughly every 30 seconds.
252
253 If d_nextclean has reached its maximum value, we also test if we were called
254 within 30 seconds, and if so, we skip cleaning. This means that under high load,
255 we will not clean more often than every 30 seconds anyhow.
256 */
257
258 void AuthPacketCache::cleanupIfNeeded()
259 {
260 if (d_ops++ == d_nextclean) {
261 time_t now = time(nullptr);
262 int timediff = max((int)(now - d_lastclean), 1);
263
264 DLOG(g_log<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
265
266 if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
267 d_cleanskipped = true;
268 d_nextclean += d_cleaninterval;
269
270 DLOG(g_log<<"cleaning skipped, timediff: "<<timediff<<endl);
271
272 return;
273 }
274
275 if(!d_cleanskipped) {
276 d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff));
277 d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval);
278 d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval);
279
280 DLOG(g_log<<"new cleaninterval: "<<d_cleaninterval<<endl);
281 } else {
282 d_cleanskipped = false;
283 }
284
285 d_nextclean += d_cleaninterval;
286 d_lastclean=now;
287 cleanup();
288 }
289 }