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