]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/auth-packetcache.cc
Merge pull request #8580 from omoerbeek/thread_rwlock_destroy
[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
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");
42
43 d_statnumhit=S.getPointer("packetcache-hit");
44 d_statnummiss=S.getPointer("packetcache-miss");
45 d_statnumentries=S.getPointer("packetcache-size");
46 }
47
48 AuthPacketCache::~AuthPacketCache()
49 {
50 try {
51 vector<WriteLock*> locks;
52 for(auto& mc : d_maps) {
53 locks.push_back(new WriteLock(&mc.d_mut));
54 }
55 for(auto wl : locks) {
56 delete wl;
57 }
58 }
59 catch(...) {
60 }
61 }
62
63 bool AuthPacketCache::get(DNSPacket& p, DNSPacket& cached)
64 {
65 if(!d_ttl) {
66 return false;
67 }
68
69 cleanupIfNeeded();
70
71 uint32_t hash = canHashPacket(p.getString());
72 p.setHash(hash);
73
74 string value;
75 bool haveSomething;
76 time_t now = time(nullptr);
77 auto& mc = getMap(p.qdomain);
78 {
79 TryReadLock rl(&mc.d_mut);
80 if(!rl.gotIt()) {
81 S.inc("deferred-packetcache-lookup");
82 return false;
83 }
84
85 haveSomething = getEntryLocked(mc.d_map, p.getString(), hash, p.qdomain, p.qtype.getCode(), p.d_tcp, now, value);
86 }
87
88 if (!haveSomething) {
89 (*d_statnummiss)++;
90 return false;
91 }
92
93 if(cached.noparse(value.c_str(), value.size()) < 0) {
94 return false;
95 }
96
97 (*d_statnumhit)++;
98 cached.spoofQuestion(p); // for correct case
99 cached.qdomain = p.qdomain;
100 cached.qtype = p.qtype;
101
102 return true;
103 }
104
105 bool AuthPacketCache::entryMatches(cmap_t::index<HashTag>::type::iterator& iter, const std::string& query, const DNSName& qname, uint16_t qtype, bool tcp)
106 {
107 return iter->tcp == tcp && iter->qtype == qtype && iter->qname == qname && queryMatches(iter->query, query, qname);
108 }
109
110 void AuthPacketCache::insert(DNSPacket& q, DNSPacket& r, unsigned int maxTTL)
111 {
112 if(!d_ttl) {
113 return;
114 }
115
116 cleanupIfNeeded();
117
118 if (ntohs(q.d.qdcount) != 1) {
119 return; // do not try to cache packets with multiple questions
120 }
121
122 if (q.qclass != QClass::IN) // we only cache the INternet
123 return;
124
125 uint32_t ourttl = std::min(d_ttl, maxTTL);
126 if (ourttl == 0) {
127 return;
128 }
129
130 uint32_t hash = q.getHash();
131 time_t now = time(nullptr);
132 CacheEntry entry;
133 entry.hash = hash;
134 entry.created = now;
135 entry.ttd = now + ourttl;
136 entry.qname = q.qdomain;
137 entry.qtype = q.qtype.getCode();
138 entry.value = r.getString();
139 entry.tcp = r.d_tcp;
140 entry.query = q.getString();
141
142 auto& mc = getMap(entry.qname);
143 {
144 TryWriteLock l(&mc.d_mut);
145 if (!l.gotIt()) {
146 S.inc("deferred-packetcache-inserts");
147 return;
148 }
149
150 auto& idx = mc.d_map.get<HashTag>();
151 auto range = idx.equal_range(hash);
152 auto iter = range.first;
153
154 for( ; iter != range.second ; ++iter) {
155 if (!entryMatches(iter, entry.query, entry.qname, entry.qtype, entry.tcp)) {
156 continue;
157 }
158
159 iter->value = entry.value;
160 iter->ttd = now + ourttl;
161 iter->created = now;
162 return;
163 }
164
165 /* no existing entry found to refresh */
166 mc.d_map.insert(entry);
167 (*d_statnumentries)++;
168 }
169 }
170
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)
172 {
173 auto& idx = map.get<HashTag>();
174 auto range = idx.equal_range(hash);
175
176 for(auto iter = range.first; iter != range.second ; ++iter) {
177 if (iter->ttd < now) {
178 continue;
179 }
180
181 if (!entryMatches(iter, query, qname, qtype, tcp)) {
182 continue;
183 }
184
185 value = iter->value;
186 return true;
187 }
188
189 return false;
190 }
191
192 /* clears the entire cache. */
193 uint64_t AuthPacketCache::purge()
194 {
195 if(!d_ttl) {
196 return 0;
197 }
198
199 d_statnumentries->store(0);
200
201 return purgeLockedCollectionsVector(d_maps);
202 }
203
204 uint64_t AuthPacketCache::purgeExact(const DNSName& qname)
205 {
206 auto& mc = getMap(qname);
207 uint64_t delcount = purgeExactLockedCollection<NameTag>(mc, qname);
208
209 *d_statnumentries -= delcount;
210
211 return delcount;
212 }
213
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)
216 {
217 if(!d_ttl) {
218 return 0;
219 }
220
221 uint64_t delcount = 0;
222
223 if(ends_with(match, "$")) {
224 delcount = purgeLockedCollectionsVector<NameTag>(d_maps, match);
225 *d_statnumentries -= delcount;
226 }
227 else {
228 delcount = purgeExact(DNSName(match));
229 }
230
231 return delcount;
232 }
233
234 void AuthPacketCache::cleanup()
235 {
236 uint64_t maxCached = d_maxEntries;
237 uint64_t cacheSize = *d_statnumentries;
238 uint64_t totErased = 0;
239
240 totErased = pruneLockedCollectionsVector<SequencedTag>(d_maps, maxCached, cacheSize);
241 *d_statnumentries -= totErased;
242
243 DLOG(g_log<<"Done with cache clean, cacheSize: "<<(*d_statnumentries)<<", totErased"<<totErased<<endl);
244 }
245
246 /* the logic:
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.
249
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.
253 */
254
255 void AuthPacketCache::cleanupIfNeeded()
256 {
257 if (d_ops++ == d_nextclean) {
258 time_t now = time(nullptr);
259 int timediff = max((int)(now - d_lastclean), 1);
260
261 DLOG(g_log<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
262
263 if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
264 d_cleanskipped = true;
265 d_nextclean += d_cleaninterval;
266
267 DLOG(g_log<<"cleaning skipped, timediff: "<<timediff<<endl);
268
269 return;
270 }
271
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);
276
277 DLOG(g_log<<"new cleaninterval: "<<d_cleaninterval<<endl);
278 } else {
279 d_cleanskipped = false;
280 }
281
282 d_nextclean += d_cleaninterval;
283 d_lastclean=now;
284 cleanup();
285 }
286 }