]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdist-cache.cc
Merge pull request #5523 from rubenk/fix-typos-in-logmessage
[thirdparty/pdns.git] / pdns / dnsdist-cache.cc
CommitLineData
12471842
PL
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 */
1ea747c0 22#include "dnsdist.hh"
886e2cf2 23#include "dolog.hh"
886e2cf2 24#include "dnsparser.hh"
1ea747c0 25#include "dnsdist-cache.hh"
886e2cf2 26
2b67180c 27DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t staleTTL, bool dontAge): d_maxEntries(maxEntries), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge)
886e2cf2
RG
28{
29 pthread_rwlock_init(&d_lock, 0);
ccac98a0 30 /* we reserve maxEntries + 1 to avoid rehashing from occurring
886e2cf2
RG
31 when we get to maxEntries, as it means a load factor of 1 */
32 d_map.reserve(maxEntries + 1);
33}
34
35DNSDistPacketCache::~DNSDistPacketCache()
36{
737a287f
RG
37 try {
38 WriteLock l(&d_lock);
39 }
40 catch(const PDNSException& pe) {
41 }
886e2cf2
RG
42}
43
a176d205 44bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp)
886e2cf2 45{
a176d205 46 if (cachedValue.tcp != tcp || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname)
886e2cf2
RG
47 return false;
48 return true;
49}
50
2714396e 51void DNSDistPacketCache::insert(uint32_t key, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode)
886e2cf2 52{
f87c4aff 53 if (responseLen < sizeof(dnsheader))
886e2cf2
RG
54 return;
55
0f08e82b 56 uint32_t minTTL;
886e2cf2 57
2714396e
RG
58 if (rcode == RCode::ServFail || rcode == RCode::Refused) {
59 minTTL = d_tempFailureTTL;
f4e5b47d
RG
60 if (minTTL == 0) {
61 return;
62 }
0f08e82b
RG
63 }
64 else {
65 minTTL = getMinTTL(response, responseLen);
a3824e43
RG
66
67 /* no TTL found, we don't want to cache this */
68 if (minTTL == std::numeric_limits<uint32_t>::max()) {
69 return;
70 }
71
72 if (minTTL > d_maxTTL) {
0f08e82b 73 minTTL = d_maxTTL;
a3824e43 74 }
0f08e82b 75
cc8cefe1
RG
76 if (minTTL < d_minTTL) {
77 d_ttlTooShorts++;
0f08e82b 78 return;
cc8cefe1 79 }
0f08e82b 80 }
886e2cf2
RG
81
82 {
83 TryReadLock r(&d_lock);
84 if (!r.gotIt()) {
85 d_deferredInserts++;
86 return;
87 }
88 if (d_map.size() >= d_maxEntries) {
89 return;
90 }
91 }
92
93 const time_t now = time(NULL);
94 std::unordered_map<uint32_t,CacheValue>::iterator it;
95 bool result;
96 time_t newValidity = now + minTTL;
97 CacheValue newValue;
98 newValue.qname = qname;
99 newValue.qtype = qtype;
100 newValue.qclass = qclass;
101 newValue.len = responseLen;
102 newValue.validity = newValidity;
103 newValue.added = now;
a176d205 104 newValue.tcp = tcp;
886e2cf2
RG
105 newValue.value = std::string(response, responseLen);
106
107 {
108 TryWriteLock w(&d_lock);
109
110 if (!w.gotIt()) {
111 d_deferredInserts++;
112 return;
113 }
114
115 tie(it, result) = d_map.insert({key, newValue});
116
117 if (result) {
118 return;
119 }
120
121 /* in case of collision, don't override the existing entry
122 except if it has expired */
123 CacheValue& value = it->second;
124 bool wasExpired = value.validity <= now;
125
a176d205 126 if (!wasExpired && !cachedValueMatches(value, qname, qtype, qclass, tcp)) {
886e2cf2
RG
127 d_insertCollisions++;
128 return;
129 }
130
131 /* if the existing entry had a longer TTD, keep it */
9e9be156 132 if (newValidity <= value.validity) {
886e2cf2 133 return;
9e9be156 134 }
886e2cf2
RG
135
136 value = newValue;
137 }
138}
139
1ea747c0 140bool DNSDistPacketCache::get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, uint32_t allowExpired, bool skipAging)
886e2cf2 141{
1ea747c0 142 uint32_t key = getKey(*dq.qname, consumed, (const unsigned char*)dq.dh, dq.len, dq.tcp);
886e2cf2
RG
143 if (keyOut)
144 *keyOut = key;
145
146 time_t now = time(NULL);
147 time_t age;
1ea747c0 148 bool stale = false;
886e2cf2
RG
149 {
150 TryReadLock r(&d_lock);
151 if (!r.gotIt()) {
152 d_deferredLookups++;
153 return false;
154 }
155
156 std::unordered_map<uint32_t,CacheValue>::const_iterator it = d_map.find(key);
157 if (it == d_map.end()) {
158 d_misses++;
159 return false;
160 }
161
162 const CacheValue& value = it->second;
163 if (value.validity < now) {
a1a0a75a 164 if ((now - value.validity) >= static_cast<time_t>(allowExpired)) {
1ea747c0
RG
165 d_misses++;
166 return false;
167 }
168 else {
169 stale = true;
170 }
886e2cf2
RG
171 }
172
39a21975 173 if (*responseLen < value.len || value.len < sizeof(dnsheader)) {
886e2cf2
RG
174 return false;
175 }
176
177 /* check for collision */
1ea747c0 178 if (!cachedValueMatches(value, *dq.qname, dq.qtype, dq.qclass, dq.tcp)) {
886e2cf2
RG
179 d_lookupCollisions++;
180 return false;
181 }
182
c8c3d4e4
RG
183 memcpy(response, &queryId, sizeof(queryId));
184 memcpy(response + sizeof(queryId), value.value.c_str() + sizeof(queryId), sizeof(dnsheader) - sizeof(queryId));
185
186 if (value.len == sizeof(dnsheader)) {
187 /* DNS header only, our work here is done */
188 *responseLen = value.len;
189 d_hits++;
190 return true;
191 }
192
1ea747c0 193 string dnsQName(dq.qname->toDNSString());
f87c4aff
RG
194 const size_t dnsQNameLen = dnsQName.length();
195 if (value.len < (sizeof(dnsheader) + dnsQNameLen)) {
196 return false;
197 }
198
f87c4aff
RG
199 memcpy(response + sizeof(dnsheader), dnsQName.c_str(), dnsQNameLen);
200 if (value.len > (sizeof(dnsheader) + dnsQNameLen)) {
201 memcpy(response + sizeof(dnsheader) + dnsQNameLen, value.value.c_str() + sizeof(dnsheader) + dnsQNameLen, value.len - (sizeof(dnsheader) + dnsQNameLen));
202 }
886e2cf2 203 *responseLen = value.len;
1ea747c0
RG
204 if (!stale) {
205 age = now - value.added;
206 }
207 else {
208 age = (value.validity - value.added) - d_staleTTL;
209 }
886e2cf2
RG
210 }
211
2b67180c 212 if (!d_dontAge && !skipAging) {
886e2cf2 213 ageDNSPacket(response, *responseLen, age);
1ea747c0
RG
214 }
215
886e2cf2
RG
216 d_hits++;
217 return true;
218}
219
4275aaba
RG
220/* Remove expired entries, until the cache has at most
221 upTo entries in it.
222*/
223void DNSDistPacketCache::purgeExpired(size_t upTo)
886e2cf2
RG
224{
225 time_t now = time(NULL);
226 WriteLock w(&d_lock);
4275aaba 227 if (upTo >= d_map.size()) {
886e2cf2 228 return;
4275aaba 229 }
886e2cf2
RG
230
231 size_t toRemove = d_map.size() - upTo;
232 for(auto it = d_map.begin(); toRemove > 0 && it != d_map.end(); ) {
233 const CacheValue& value = it->second;
234
235 if (value.validity < now) {
236 it = d_map.erase(it);
237 --toRemove;
238 } else {
239 ++it;
240 }
241 }
242}
243
4275aaba
RG
244/* Remove all entries, keeping only upTo
245 entries in the cache */
246void DNSDistPacketCache::expunge(size_t upTo)
247{
248 WriteLock w(&d_lock);
249
250 if (upTo >= d_map.size()) {
251 return;
252 }
253
254 size_t toRemove = d_map.size() - upTo;
255 auto beginIt = d_map.begin();
256 auto endIt = beginIt;
257 std::advance(endIt, toRemove);
258 d_map.erase(beginIt, endIt);
259}
260
490dc586 261void DNSDistPacketCache::expungeByName(const DNSName& name, uint16_t qtype, bool suffixMatch)
886e2cf2
RG
262{
263 WriteLock w(&d_lock);
264
265 for(auto it = d_map.begin(); it != d_map.end(); ) {
266 const CacheValue& value = it->second;
886e2cf2 267
490dc586
RG
268 if ((value.qname == name || (suffixMatch && value.qname.isPartOf(name))) && (qtype == QType::ANY || qtype == value.qtype)) {
269 it = d_map.erase(it);
886e2cf2
RG
270 } else {
271 ++it;
272 }
273 }
274}
275
276bool DNSDistPacketCache::isFull()
277{
278 ReadLock r(&d_lock);
279 return (d_map.size() >= d_maxEntries);
280}
281
282uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length)
283{
0766890a 284 return getDNSPacketMinTTL(packet, length);
886e2cf2
RG
285}
286
a176d205 287uint32_t DNSDistPacketCache::getKey(const DNSName& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp)
886e2cf2
RG
288{
289 uint32_t result = 0;
290 /* skip the query ID */
cceddbef
RG
291 if (packetLen < sizeof(dnsheader))
292 throw std::range_error("Computing packet cache key for an invalid packet size");
886e2cf2
RG
293 result = burtle(packet + 2, sizeof(dnsheader) - 2, result);
294 string lc(qname.toDNSStringLC());
295 result = burtle((const unsigned char*) lc.c_str(), lc.length(), result);
cceddbef
RG
296 if (packetLen < sizeof(dnsheader) + consumed) {
297 throw std::range_error("Computing packet cache key for an invalid packet");
298 }
299 if (packetLen > ((sizeof(dnsheader) + consumed))) {
300 result = burtle(packet + sizeof(dnsheader) + consumed, packetLen - (sizeof(dnsheader) + consumed), result);
301 }
a176d205 302 result = burtle((const unsigned char*) &tcp, sizeof(tcp), result);
886e2cf2
RG
303 return result;
304}
305
306string DNSDistPacketCache::toString()
307{
308 ReadLock r(&d_lock);
309 return std::to_string(d_map.size()) + "/" + std::to_string(d_maxEntries);
310}
9e9be156
RG
311
312uint64_t DNSDistPacketCache::getEntriesCount()
313{
314 ReadLock r(&d_lock);
315 return d_map.size();
316}