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