]>
Commit | Line | Data |
---|---|---|
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 | 27 | DNSDistPacketCache::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 | ||
35 | DNSDistPacketCache::~DNSDistPacketCache() | |
36 | { | |
737a287f RG |
37 | try { |
38 | WriteLock l(&d_lock); | |
39 | } | |
40 | catch(const PDNSException& pe) { | |
41 | } | |
886e2cf2 RG |
42 | } |
43 | ||
a176d205 | 44 | bool 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 | 51 | void 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 | 140 | bool 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 | */ | |
223 | void 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 */ | |
246 | void 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 | 261 | void 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 | ||
276 | bool DNSDistPacketCache::isFull() | |
277 | { | |
278 | ReadLock r(&d_lock); | |
279 | return (d_map.size() >= d_maxEntries); | |
280 | } | |
281 | ||
282 | uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length) | |
283 | { | |
0766890a | 284 | return getDNSPacketMinTTL(packet, length); |
886e2cf2 RG |
285 | } |
286 | ||
a176d205 | 287 | uint32_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 | ||
306 | string 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 | |
312 | uint64_t DNSDistPacketCache::getEntriesCount() | |
313 | { | |
314 | ReadLock r(&d_lock); | |
315 | return d_map.size(); | |
316 | } |