]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/auth-packetcache.cc
rec: Don't account chained queries more than once
[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 cleanupIfNeeded();
69
70 if(!d_ttl) {
71 (*d_statnummiss)++;
72 return false;
73 }
74
75 uint32_t hash = canHashPacket(p->getString(), false);
76 p->setHash(hash);
77
78 string value;
79 bool haveSomething;
80 time_t now = time(nullptr);
81 auto& mc = getMap(p->qdomain);
82 {
83 TryReadLock rl(&mc.d_mut);
84 if(!rl.gotIt()) {
85 S.inc("deferred-packetcache-lookup");
86 return false;
87 }
88
89 haveSomething = getEntryLocked(mc.d_map, hash, p->qdomain, p->qtype.getCode(), p->d_tcp, now, value);
90 }
91
92 if (!haveSomething) {
93 (*d_statnummiss)++;
94 return false;
95 }
96
97 if(cached->noparse(value.c_str(), value.size()) < 0) {
98 return false;
99 }
100
101 (*d_statnumhit)++;
102 cached->spoofQuestion(p); // for correct case
103 cached->qdomain = p->qdomain;
104 cached->qtype = p->qtype;
105
106 return true;
107 }
108
109 void AuthPacketCache::insert(DNSPacket *q, DNSPacket *r, unsigned int maxTTL)
110 {
111 cleanupIfNeeded();
112
113 if (ntohs(q->d.qdcount) != 1) {
114 return; // do not try to cache packets with multiple questions
115 }
116
117 if (q->qclass != QClass::IN) // we only cache the INternet
118 return;
119
120 uint32_t ourttl = std::min(d_ttl, maxTTL);
121 if (ourttl == 0) {
122 return;
123 }
124
125 uint32_t hash = q->getHash();
126 time_t now = time(nullptr);
127 CacheEntry entry;
128 entry.hash = hash;
129 entry.created = now;
130 entry.ttd = now + ourttl;
131 entry.qname = q->qdomain;
132 entry.qtype = q->qtype.getCode();
133 entry.value = r->getString();
134 entry.tcp = r->d_tcp;
135
136 auto& mc = getMap(entry.qname);
137 {
138 TryWriteLock l(&mc.d_mut);
139 if (!l.gotIt()) {
140 S.inc("deferred-packetcache-inserts");
141 return;
142 }
143
144 auto& idx = mc.d_map.get<HashTag>();
145 auto range = idx.equal_range(hash);
146 auto iter = range.first;
147
148 for( ; iter != range.second ; ++iter) {
149 if (iter->tcp != entry.tcp || iter->qtype != entry.qtype || iter->qname != entry.qname)
150 continue;
151
152 iter->value = entry.value;
153 iter->ttd = now + ourttl;
154 iter->created = now;
155 return;
156 }
157
158 /* no existing entry found to refresh */
159 mc.d_map.insert(entry);
160 (*d_statnumentries)++;
161 }
162 }
163
164 bool AuthPacketCache::getEntryLocked(cmap_t& map, uint32_t hash, const DNSName &qname, uint16_t qtype, bool tcp, time_t now, string& value)
165 {
166 auto& idx = map.get<HashTag>();
167 auto range = idx.equal_range(hash);
168
169 for(auto iter = range.first; iter != range.second ; ++iter) {
170 if (iter->ttd < now)
171 continue;
172
173 if (iter->tcp != tcp || iter->qtype != qtype || iter->qname != qname)
174 continue;
175
176 value = iter->value;
177 return true;
178 }
179
180 return false;
181 }
182
183 /* clears the entire cache. */
184 uint64_t AuthPacketCache::purge()
185 {
186 d_statnumentries->store(0);
187
188 return purgeLockedCollectionsVector(d_maps);
189 }
190
191 uint64_t AuthPacketCache::purgeExact(const DNSName& qname)
192 {
193 auto& mc = getMap(qname);
194 uint64_t delcount = purgeExactLockedCollection(mc, qname);
195
196 *d_statnumentries -= delcount;
197
198 return delcount;
199 }
200
201 /* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
202 uint64_t AuthPacketCache::purge(const string &match)
203 {
204 uint64_t delcount = 0;
205
206 if(ends_with(match, "$")) {
207 delcount = purgeLockedCollectionsVector(d_maps, match);
208 *d_statnumentries -= delcount;
209 }
210 else {
211 delcount = purgeExact(DNSName(match));
212 }
213
214 return delcount;
215 }
216
217 void AuthPacketCache::cleanup()
218 {
219 uint64_t maxCached = d_maxEntries;
220 uint64_t cacheSize = *d_statnumentries;
221 uint64_t totErased = 0;
222
223 totErased = pruneLockedCollectionsVector(d_maps, maxCached, cacheSize);
224 *d_statnumentries -= totErased;
225
226 DLOG(L<<"Done with cache clean, cacheSize: "<<(*d_statnumentries)<<", totErased"<<totErased<<endl);
227 }
228
229 /* the logic:
230 after d_nextclean operations, we clean. We also adjust the cleaninterval
231 a bit so we slowly move it to a value where we clean roughly every 30 seconds.
232
233 If d_nextclean has reached its maximum value, we also test if we were called
234 within 30 seconds, and if so, we skip cleaning. This means that under high load,
235 we will not clean more often than every 30 seconds anyhow.
236 */
237
238 void AuthPacketCache::cleanupIfNeeded()
239 {
240 if (d_ops++ == d_nextclean) {
241 time_t now = time(nullptr);
242 int timediff = max((int)(now - d_lastclean), 1);
243
244 DLOG(L<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
245
246 if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
247 d_cleanskipped = true;
248 d_nextclean += d_cleaninterval;
249
250 DLOG(L<<"cleaning skipped, timediff: "<<timediff<<endl);
251
252 return;
253 }
254
255 if(!d_cleanskipped) {
256 d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff));
257 d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval);
258 d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval);
259
260 DLOG(L<<"new cleaninterval: "<<d_cleaninterval<<endl);
261 } else {
262 d_cleanskipped = false;
263 }
264
265 d_nextclean += d_cleaninterval;
266 d_lastclean=now;
267 cleanup();
268 }
269 }