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