]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/auth-packetcache.cc
auth: update canHashPacket() calls accordingly
[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", StatType::gauge);
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 void AuthPacketCache::MapCombo::reserve(size_t numberOfEntries)
47 {
48 #if BOOST_VERSION >= 105600
49 d_map.write_lock()->get<HashTag>().reserve(numberOfEntries);
50 #endif /* BOOST_VERSION >= 105600 */
51 }
52
53 bool AuthPacketCache::get(DNSPacket& p, DNSPacket& cached)
54 {
55 if(!d_ttl) {
56 return false;
57 }
58
59 cleanupIfNeeded();
60
61 static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE};
62 uint32_t hash = canHashPacket(p.getString(), /* don't skip ECS */optionsToSkip);
63 p.setHash(hash);
64
65 string value;
66 bool haveSomething;
67 time_t now = time(nullptr);
68 auto& mc = getMap(p.qdomain);
69 {
70 auto map = mc.d_map.try_read_lock();
71 if (!map.owns_lock()) {
72 S.inc("deferred-packetcache-lookup");
73 return false;
74 }
75
76 haveSomething = getEntryLocked(*map, p.getString(), hash, p.qdomain, p.qtype.getCode(), p.d_tcp, now, value);
77 }
78
79 if (!haveSomething) {
80 (*d_statnummiss)++;
81 return false;
82 }
83
84 if(cached.noparse(value.c_str(), value.size()) < 0) {
85 return false;
86 }
87
88 (*d_statnumhit)++;
89 cached.spoofQuestion(p); // for correct case
90 cached.qdomain = p.qdomain;
91 cached.qtype = p.qtype;
92
93 return true;
94 }
95
96 bool AuthPacketCache::entryMatches(cmap_t::index<HashTag>::type::iterator& iter, const std::string& query, const DNSName& qname, uint16_t qtype, bool tcp)
97 {
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);
100 }
101
102 void AuthPacketCache::insert(DNSPacket& q, DNSPacket& r, unsigned int maxTTL)
103 {
104 if(!d_ttl) {
105 return;
106 }
107
108 cleanupIfNeeded();
109
110 if (ntohs(q.d.qdcount) != 1) {
111 return; // do not try to cache packets with multiple questions
112 }
113
114 if (q.qclass != QClass::IN) // we only cache the INternet
115 return;
116
117 uint32_t ourttl = std::min(d_ttl, maxTTL);
118 if (ourttl == 0) {
119 return;
120 }
121
122 uint32_t hash = q.getHash();
123 time_t now = time(nullptr);
124 CacheEntry entry;
125 entry.hash = hash;
126 entry.created = now;
127 entry.ttd = now + ourttl;
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();
133
134 auto& mc = getMap(entry.qname);
135 {
136 auto map = mc.d_map.try_write_lock();
137 if (!map.owns_lock()) {
138 S.inc("deferred-packetcache-inserts");
139 return;
140 }
141
142 auto& idx = map->get<HashTag>();
143 auto range = idx.equal_range(hash);
144 auto iter = range.first;
145
146 for( ; iter != range.second ; ++iter) {
147 if (!entryMatches(iter, entry.query, entry.qname, entry.qtype, entry.tcp)) {
148 continue;
149 }
150
151 moveCacheItemToBack<SequencedTag>(*map, iter);
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 */
159 map->insert(std::move(entry));
160
161 if (*d_statnumentries >= d_maxEntries) {
162 /* remove the least recently inserted or replaced entry */
163 auto& sidx = map->get<SequencedTag>();
164 sidx.pop_front();
165 }
166 else {
167 ++(*d_statnumentries);
168 }
169 }
170 }
171
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)
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) {
178 if (iter->ttd < now) {
179 continue;
180 }
181
182 if (!entryMatches(iter, query, qname, qtype, tcp)) {
183 continue;
184 }
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 {
196 if(!d_ttl) {
197 return 0;
198 }
199
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);
208 uint64_t delcount = purgeExactLockedCollection<NameTag>(mc, qname);
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 {
218 if(!d_ttl) {
219 return 0;
220 }
221
222 uint64_t delcount = 0;
223
224 if(boost::ends_with(match, "$")) {
225 delcount = purgeLockedCollectionsVector<NameTag>(d_maps, match);
226 *d_statnumentries -= delcount;
227 }
228 else {
229 delcount = purgeExact(DNSName(match));
230 }
231
232 return delcount;
233 }
234
235 void AuthPacketCache::cleanup()
236 {
237 uint64_t totErased = pruneLockedCollectionsVector<SequencedTag>(d_maps);
238 *d_statnumentries -= totErased;
239
240 DLOG(g_log<<"Done with cache clean, cacheSize: "<<(*d_statnumentries)<<", totErased"<<totErased<<endl);
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
258 DLOG(g_log<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
259
260 if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
261 d_cleanskipped = true;
262 d_nextclean += d_cleaninterval;
263
264 DLOG(g_log<<"cleaning skipped, timediff: "<<timediff<<endl);
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
274 DLOG(g_log<<"new cleaninterval: "<<d_cleaninterval<<endl);
275 } else {
276 d_cleanskipped = false;
277 }
278
279 d_nextclean += d_cleaninterval;
280 d_lastclean=now;
281 cleanup();
282 }
283 }