]>
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 | ||
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 | ||
08b02366 | 75 | uint32_t hash = canHashPacket(p->getString()); |
bf269e28 RG |
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 | ||
08b02366 | 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 | ||
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 | ||
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 | ||
bf269e28 RG |
114 | void AuthPacketCache::insert(DNSPacket *q, DNSPacket *r, unsigned int maxTTL) |
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; | |
08b02366 | 140 | entry.query = q->getString(); |
bf269e28 RG |
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) { | |
08b02366 | 155 | if (!entryMatches(iter, entry.query, entry.qname, entry.qtype, entry.tcp)) { |
bf269e28 | 156 | continue; |
08b02366 | 157 | } |
bf269e28 RG |
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 | ||
08b02366 | 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) |
bf269e28 RG |
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) { | |
08b02366 | 177 | if (iter->ttd < now) { |
bf269e28 | 178 | continue; |
08b02366 | 179 | } |
bf269e28 | 180 | |
08b02366 | 181 | if (!entryMatches(iter, query, qname, qtype, tcp)) { |
bf269e28 | 182 | continue; |
08b02366 | 183 | } |
bf269e28 RG |
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 | d_statnumentries->store(0); | |
196 | ||
197 | return purgeLockedCollectionsVector(d_maps); | |
198 | } | |
199 | ||
200 | uint64_t AuthPacketCache::purgeExact(const DNSName& qname) | |
201 | { | |
202 | auto& mc = getMap(qname); | |
203 | uint64_t delcount = purgeExactLockedCollection(mc, qname); | |
204 | ||
205 | *d_statnumentries -= delcount; | |
206 | ||
207 | return delcount; | |
208 | } | |
209 | ||
210 | /* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */ | |
211 | uint64_t AuthPacketCache::purge(const string &match) | |
212 | { | |
213 | uint64_t delcount = 0; | |
214 | ||
215 | if(ends_with(match, "$")) { | |
216 | delcount = purgeLockedCollectionsVector(d_maps, match); | |
217 | *d_statnumentries -= delcount; | |
218 | } | |
219 | else { | |
220 | delcount = purgeExact(DNSName(match)); | |
221 | } | |
222 | ||
223 | return delcount; | |
224 | } | |
225 | ||
226 | void AuthPacketCache::cleanup() | |
227 | { | |
228 | uint64_t maxCached = d_maxEntries; | |
229 | uint64_t cacheSize = *d_statnumentries; | |
230 | uint64_t totErased = 0; | |
231 | ||
232 | totErased = pruneLockedCollectionsVector(d_maps, maxCached, cacheSize); | |
233 | *d_statnumentries -= totErased; | |
234 | ||
e6a9dde5 | 235 | DLOG(g_log<<"Done with cache clean, cacheSize: "<<(*d_statnumentries)<<", totErased"<<totErased<<endl); |
bf269e28 RG |
236 | } |
237 | ||
238 | /* the logic: | |
239 | after d_nextclean operations, we clean. We also adjust the cleaninterval | |
240 | a bit so we slowly move it to a value where we clean roughly every 30 seconds. | |
241 | ||
242 | If d_nextclean has reached its maximum value, we also test if we were called | |
243 | within 30 seconds, and if so, we skip cleaning. This means that under high load, | |
244 | we will not clean more often than every 30 seconds anyhow. | |
245 | */ | |
246 | ||
247 | void AuthPacketCache::cleanupIfNeeded() | |
248 | { | |
249 | if (d_ops++ == d_nextclean) { | |
250 | time_t now = time(nullptr); | |
251 | int timediff = max((int)(now - d_lastclean), 1); | |
252 | ||
e6a9dde5 | 253 | DLOG(g_log<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl); |
bf269e28 RG |
254 | |
255 | if (d_cleaninterval == s_maxcleaninterval && timediff < 30) { | |
256 | d_cleanskipped = true; | |
257 | d_nextclean += d_cleaninterval; | |
258 | ||
e6a9dde5 | 259 | DLOG(g_log<<"cleaning skipped, timediff: "<<timediff<<endl); |
bf269e28 RG |
260 | |
261 | return; | |
262 | } | |
263 | ||
264 | if(!d_cleanskipped) { | |
265 | d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff)); | |
266 | d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval); | |
267 | d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval); | |
268 | ||
e6a9dde5 | 269 | DLOG(g_log<<"new cleaninterval: "<<d_cleaninterval<<endl); |
bf269e28 RG |
270 | } else { |
271 | d_cleanskipped = false; | |
272 | } | |
273 | ||
274 | d_nextclean += d_cleaninterval; | |
275 | d_lastclean=now; | |
276 | cleanup(); | |
277 | } | |
278 | } |