]>
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 | */ | |
6432820c RG |
22 | #include <cinttypes> |
23 | ||
1ea747c0 | 24 | #include "dnsdist.hh" |
886e2cf2 | 25 | #include "dolog.hh" |
886e2cf2 | 26 | #include "dnsparser.hh" |
1ea747c0 | 27 | #include "dnsdist-cache.hh" |
78e3ac9e | 28 | #include "dnsdist-ecs.hh" |
78e3ac9e | 29 | #include "ednssubnet.hh" |
fa980c59 | 30 | #include "packetcache.hh" |
886e2cf2 | 31 | |
78e3ac9e | 32 | DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t maxNegativeTTL, uint32_t staleTTL, bool dontAge, uint32_t shards, bool deferrableInsertLock, bool parseECS): d_maxEntries(maxEntries), d_shardCount(shards), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_maxNegativeTTL(maxNegativeTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge), d_deferrableInsertLock(deferrableInsertLock), d_parseECS(parseECS) |
886e2cf2 | 33 | { |
ffae2ddc RG |
34 | if (d_maxEntries == 0) { |
35 | throw std::runtime_error("Trying to create a 0-sized packet-cache"); | |
36 | } | |
37 | ||
2b3eefc3 RG |
38 | d_shards.resize(d_shardCount); |
39 | ||
ccac98a0 | 40 | /* we reserve maxEntries + 1 to avoid rehashing from occurring |
886e2cf2 | 41 | when we get to maxEntries, as it means a load factor of 1 */ |
2b3eefc3 RG |
42 | for (auto& shard : d_shards) { |
43 | shard.setSize((maxEntries / d_shardCount) + 1); | |
44 | } | |
886e2cf2 RG |
45 | } |
46 | ||
32fbb2ab | 47 | bool DNSDistPacketCache::getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet) |
78e3ac9e | 48 | { |
cbf4e13a | 49 | uint16_t optRDPosition; |
78e3ac9e RG |
50 | size_t remaining = 0; |
51 | ||
341d2553 | 52 | int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining); |
78e3ac9e RG |
53 | |
54 | if (res == 0) { | |
341d2553 | 55 | size_t ecsOptionStartPosition = 0; |
78e3ac9e RG |
56 | size_t ecsOptionSize = 0; |
57 | ||
341d2553 | 58 | res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize); |
78e3ac9e RG |
59 | |
60 | if (res == 0 && ecsOptionSize > (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) { | |
61 | ||
62 | EDNSSubnetOpts eso; | |
341d2553 | 63 | if (getEDNSSubnetOptsFromString(reinterpret_cast<const char*>(&packet.at(optRDPosition + ecsOptionStartPosition + (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))), ecsOptionSize - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), &eso) == true) { |
78e3ac9e RG |
64 | subnet = eso.source; |
65 | return true; | |
66 | } | |
67 | } | |
68 | } | |
69 | ||
70 | return false; | |
71 | } | |
72 | ||
d84ea5be | 73 | bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const |
886e2cf2 | 74 | { |
d84ea5be | 75 | if (cachedValue.queryFlags != queryFlags || cachedValue.dnssecOK != dnssecOK || cachedValue.receivedOverUDP != receivedOverUDP || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) { |
886e2cf2 | 76 | return false; |
8dcdbdb1 RG |
77 | } |
78 | ||
78e3ac9e RG |
79 | if (d_parseECS && cachedValue.subnet != subnet) { |
80 | return false; | |
81 | } | |
82 | ||
886e2cf2 RG |
83 | return true; |
84 | } | |
85 | ||
0310790c | 86 | void DNSDistPacketCache::insertLocked(CacheShard& shard, std::unordered_map<uint32_t,CacheValue>& map, uint32_t key, CacheValue& newValue) |
2b3eefc3 | 87 | { |
2b3eefc3 RG |
88 | /* check again now that we hold the lock to prevent a race */ |
89 | if (map.size() >= (d_maxEntries / d_shardCount)) { | |
90 | return; | |
91 | } | |
92 | ||
93 | std::unordered_map<uint32_t,CacheValue>::iterator it; | |
94 | bool result; | |
21f26fff | 95 | std::tie(it, result) = map.insert({key, newValue}); |
2b3eefc3 RG |
96 | |
97 | if (result) { | |
c7803194 | 98 | ++shard.d_entriesCount; |
2b3eefc3 RG |
99 | return; |
100 | } | |
101 | ||
102 | /* in case of collision, don't override the existing entry | |
103 | except if it has expired */ | |
104 | CacheValue& value = it->second; | |
78e3ac9e | 105 | bool wasExpired = value.validity <= newValue.added; |
2b3eefc3 | 106 | |
d84ea5be | 107 | if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.receivedOverUDP, newValue.dnssecOK, newValue.subnet)) { |
26512612 | 108 | ++d_insertCollisions; |
2b3eefc3 RG |
109 | return; |
110 | } | |
111 | ||
112 | /* if the existing entry had a longer TTD, keep it */ | |
78e3ac9e | 113 | if (newValue.validity <= value.validity) { |
2b3eefc3 RG |
114 | return; |
115 | } | |
116 | ||
117 | value = newValue; | |
118 | } | |
119 | ||
d84ea5be | 120 | void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL) |
886e2cf2 | 121 | { |
7b71c89b | 122 | if (response.size() < sizeof(dnsheader) || response.size() > getMaximumEntrySize()) { |
886e2cf2 | 123 | return; |
8dcdbdb1 | 124 | } |
7b71c89b | 125 | |
8eff5c8d RG |
126 | if (qtype == QType::AXFR || qtype == QType::IXFR) { |
127 | return; | |
128 | } | |
886e2cf2 | 129 | |
0f08e82b | 130 | uint32_t minTTL; |
886e2cf2 | 131 | |
2714396e | 132 | if (rcode == RCode::ServFail || rcode == RCode::Refused) { |
acb8f5d5 | 133 | minTTL = tempFailureTTL == boost::none ? d_tempFailureTTL : *tempFailureTTL; |
f4e5b47d RG |
134 | if (minTTL == 0) { |
135 | return; | |
136 | } | |
0f08e82b RG |
137 | } |
138 | else { | |
47698274 | 139 | bool seenAuthSOA = false; |
341d2553 | 140 | minTTL = getMinTTL(reinterpret_cast<const char*>(response.data()), response.size(), &seenAuthSOA); |
a3824e43 RG |
141 | |
142 | /* no TTL found, we don't want to cache this */ | |
143 | if (minTTL == std::numeric_limits<uint32_t>::max()) { | |
144 | return; | |
145 | } | |
146 | ||
47698274 RG |
147 | if (rcode == RCode::NXDomain || (rcode == RCode::NoError && seenAuthSOA)) { |
148 | minTTL = std::min(minTTL, d_maxNegativeTTL); | |
149 | } | |
150 | else if (minTTL > d_maxTTL) { | |
0f08e82b | 151 | minTTL = d_maxTTL; |
a3824e43 | 152 | } |
0f08e82b | 153 | |
cc8cefe1 | 154 | if (minTTL < d_minTTL) { |
26512612 | 155 | ++d_ttlTooShorts; |
0f08e82b | 156 | return; |
cc8cefe1 | 157 | } |
0f08e82b | 158 | } |
886e2cf2 | 159 | |
2b3eefc3 RG |
160 | uint32_t shardIndex = getShardIndex(key); |
161 | ||
c7803194 | 162 | if (d_shards.at(shardIndex).d_entriesCount >= (d_maxEntries / d_shardCount)) { |
2b3eefc3 | 163 | return; |
886e2cf2 RG |
164 | } |
165 | ||
c1b81381 | 166 | const time_t now = time(nullptr); |
886e2cf2 RG |
167 | time_t newValidity = now + minTTL; |
168 | CacheValue newValue; | |
169 | newValue.qname = qname; | |
170 | newValue.qtype = qtype; | |
171 | newValue.qclass = qclass; | |
8dcdbdb1 | 172 | newValue.queryFlags = queryFlags; |
341d2553 | 173 | newValue.len = response.size(); |
886e2cf2 RG |
174 | newValue.validity = newValidity; |
175 | newValue.added = now; | |
d84ea5be | 176 | newValue.receivedOverUDP = receivedOverUDP; |
d7728daf | 177 | newValue.dnssecOK = dnssecOK; |
341d2553 | 178 | newValue.value = std::string(response.begin(), response.end()); |
78e3ac9e | 179 | newValue.subnet = subnet; |
886e2cf2 | 180 | |
2b3eefc3 RG |
181 | auto& shard = d_shards.at(shardIndex); |
182 | ||
183 | if (d_deferrableInsertLock) { | |
6e41eb90 | 184 | auto w = shard.d_map.try_write_lock(); |
886e2cf2 | 185 | |
0310790c | 186 | if (!w.owns_lock()) { |
26512612 | 187 | ++d_deferredInserts; |
886e2cf2 RG |
188 | return; |
189 | } | |
0310790c | 190 | insertLocked(shard, *w, key, newValue); |
2b3eefc3 RG |
191 | } |
192 | else { | |
6e41eb90 | 193 | auto w = shard.d_map.write_lock(); |
886e2cf2 | 194 | |
0310790c | 195 | insertLocked(shard, *w, key, newValue); |
886e2cf2 RG |
196 | } |
197 | } | |
198 | ||
ad9a0329 | 199 | bool DNSDistPacketCache::get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired, bool skipAging, bool truncatedOK, bool recordMiss) |
886e2cf2 | 200 | { |
8eff5c8d | 201 | if (dq.ids.qtype == QType::AXFR || dq.ids.qtype == QType::IXFR) { |
26512612 | 202 | ++d_misses; |
8eff5c8d RG |
203 | return false; |
204 | } | |
205 | ||
592b1d99 RG |
206 | const auto& dnsQName = dq.ids.qname.getStorage(); |
207 | uint32_t key = getKey(dnsQName, dq.ids.qname.wirelength(), dq.getData(), receivedOverUDP); | |
f037144c | 208 | |
fa980c59 | 209 | if (keyOut) { |
886e2cf2 | 210 | *keyOut = key; |
fa980c59 | 211 | } |
886e2cf2 | 212 | |
78e3ac9e | 213 | if (d_parseECS) { |
592b1d99 | 214 | getClientSubnet(dq.getData(), dq.ids.qname.wirelength(), subnet); |
78e3ac9e RG |
215 | } |
216 | ||
2b3eefc3 | 217 | uint32_t shardIndex = getShardIndex(key); |
c1b81381 | 218 | time_t now = time(nullptr); |
886e2cf2 | 219 | time_t age; |
1ea747c0 | 220 | bool stale = false; |
341d2553 | 221 | auto& response = dq.getMutableData(); |
2b3eefc3 | 222 | auto& shard = d_shards.at(shardIndex); |
886e2cf2 | 223 | { |
0310790c RG |
224 | auto map = shard.d_map.try_read_lock(); |
225 | if (!map.owns_lock()) { | |
26512612 | 226 | ++d_deferredLookups; |
886e2cf2 RG |
227 | return false; |
228 | } | |
229 | ||
0310790c RG |
230 | std::unordered_map<uint32_t,CacheValue>::const_iterator it = map->find(key); |
231 | if (it == map->end()) { | |
ad9a0329 | 232 | if (recordMiss) { |
26512612 | 233 | ++d_misses; |
ad9a0329 | 234 | } |
886e2cf2 RG |
235 | return false; |
236 | } | |
237 | ||
238 | const CacheValue& value = it->second; | |
07a262c6 | 239 | if (value.validity <= now) { |
a1a0a75a | 240 | if ((now - value.validity) >= static_cast<time_t>(allowExpired)) { |
ad9a0329 | 241 | if (recordMiss) { |
26512612 | 242 | ++d_misses; |
ad9a0329 | 243 | } |
1ea747c0 RG |
244 | return false; |
245 | } | |
246 | else { | |
247 | stale = true; | |
248 | } | |
886e2cf2 RG |
249 | } |
250 | ||
341d2553 | 251 | if (value.len < sizeof(dnsheader)) { |
886e2cf2 RG |
252 | return false; |
253 | } | |
254 | ||
255 | /* check for collision */ | |
90686725 | 256 | if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.getHeader().get())), dq.ids.qname, dq.ids.qtype, dq.ids.qclass, receivedOverUDP, dnssecOK, subnet)) { |
26512612 | 257 | ++d_lookupCollisions; |
886e2cf2 RG |
258 | return false; |
259 | } | |
260 | ||
0c8759d8 RG |
261 | if (!truncatedOK) { |
262 | dnsheader dh; | |
263 | memcpy(&dh, value.value.data(), sizeof(dh)); | |
264 | if (dh.tc != 0) { | |
265 | return false; | |
266 | } | |
267 | } | |
268 | ||
341d2553 RG |
269 | response.resize(value.len); |
270 | memcpy(&response.at(0), &queryId, sizeof(queryId)); | |
271 | memcpy(&response.at(sizeof(queryId)), &value.value.at(sizeof(queryId)), sizeof(dnsheader) - sizeof(queryId)); | |
c8c3d4e4 RG |
272 | |
273 | if (value.len == sizeof(dnsheader)) { | |
274 | /* DNS header only, our work here is done */ | |
26512612 | 275 | ++d_hits; |
c8c3d4e4 RG |
276 | return true; |
277 | } | |
278 | ||
f87c4aff RG |
279 | const size_t dnsQNameLen = dnsQName.length(); |
280 | if (value.len < (sizeof(dnsheader) + dnsQNameLen)) { | |
281 | return false; | |
282 | } | |
283 | ||
341d2553 | 284 | memcpy(&response.at(sizeof(dnsheader)), dnsQName.c_str(), dnsQNameLen); |
f87c4aff | 285 | if (value.len > (sizeof(dnsheader) + dnsQNameLen)) { |
341d2553 | 286 | memcpy(&response.at(sizeof(dnsheader) + dnsQNameLen), &value.value.at(sizeof(dnsheader) + dnsQNameLen), value.len - (sizeof(dnsheader) + dnsQNameLen)); |
f87c4aff | 287 | } |
341d2553 | 288 | |
1ea747c0 RG |
289 | if (!stale) { |
290 | age = now - value.added; | |
291 | } | |
292 | else { | |
293 | age = (value.validity - value.added) - d_staleTTL; | |
294 | } | |
886e2cf2 RG |
295 | } |
296 | ||
2b67180c | 297 | if (!d_dontAge && !skipAging) { |
9a3abf5a | 298 | if (!stale) { |
56b827c5 | 299 | // coverity[store_truncates_time_t] |
100ff9c7 OM |
300 | dnsheader_aligned dh_aligned(response.data()); |
301 | ageDNSPacket(reinterpret_cast<char *>(&response[0]), response.size(), age, dh_aligned); | |
9a3abf5a O |
302 | } |
303 | else { | |
d73de874 FM |
304 | editDNSPacketTTL(reinterpret_cast<char*>(&response[0]), response.size(), |
305 | [staleTTL = d_staleTTL](uint8_t /* section */, uint16_t /* class_ */, uint16_t /* type */, uint32_t /* ttl */) { return staleTTL; }); | |
9a3abf5a | 306 | } |
1ea747c0 RG |
307 | } |
308 | ||
26512612 | 309 | ++d_hits; |
886e2cf2 RG |
310 | return true; |
311 | } | |
312 | ||
4275aaba RG |
313 | /* Remove expired entries, until the cache has at most |
314 | upTo entries in it. | |
f6e76e12 RG |
315 | If the cache has more than one shard, we will try hard |
316 | to make sure that every shard has free space remaining. | |
4275aaba | 317 | */ |
f6e76e12 | 318 | size_t DNSDistPacketCache::purgeExpired(size_t upTo, const time_t now) |
886e2cf2 | 319 | { |
f6e76e12 | 320 | const size_t maxPerShard = upTo / d_shardCount; |
886e2cf2 | 321 | |
f6e76e12 | 322 | size_t removed = 0; |
f6e76e12 | 323 | |
26512612 | 324 | ++d_cleanupCount; |
f23ed0a6 | 325 | for (auto& shard : d_shards) { |
6e41eb90 | 326 | auto map = shard.d_map.write_lock(); |
0310790c | 327 | if (map->size() <= maxPerShard) { |
f6e76e12 RG |
328 | continue; |
329 | } | |
330 | ||
0310790c | 331 | size_t toRemove = map->size() - maxPerShard; |
2b3eefc3 | 332 | |
0310790c | 333 | for (auto it = map->begin(); toRemove > 0 && it != map->end(); ) { |
2b3eefc3 RG |
334 | const CacheValue& value = it->second; |
335 | ||
07a262c6 | 336 | if (value.validity <= now) { |
0310790c | 337 | it = map->erase(it); |
886e2cf2 | 338 | --toRemove; |
1942c541 | 339 | --shard.d_entriesCount; |
f627611d | 340 | ++removed; |
2b3eefc3 RG |
341 | } else { |
342 | ++it; | |
343 | } | |
886e2cf2 RG |
344 | } |
345 | } | |
f627611d RG |
346 | |
347 | return removed; | |
886e2cf2 RG |
348 | } |
349 | ||
4275aaba | 350 | /* Remove all entries, keeping only upTo |
c9134a29 RG |
351 | entries in the cache. |
352 | If the cache has more than one shard, we will try hard | |
353 | to make sure that every shard has free space remaining. | |
354 | */ | |
f627611d | 355 | size_t DNSDistPacketCache::expunge(size_t upTo) |
4275aaba | 356 | { |
c9134a29 RG |
357 | const size_t maxPerShard = upTo / d_shardCount; |
358 | ||
6d1a9248 | 359 | size_t removed = 0; |
4275aaba | 360 | |
c9134a29 | 361 | for (auto& shard : d_shards) { |
6e41eb90 | 362 | auto map = shard.d_map.write_lock(); |
4275aaba | 363 | |
0310790c | 364 | if (map->size() <= maxPerShard) { |
c9134a29 RG |
365 | continue; |
366 | } | |
367 | ||
0310790c | 368 | size_t toRemove = map->size() - maxPerShard; |
2b3eefc3 | 369 | |
0310790c | 370 | auto beginIt = map->begin(); |
2b3eefc3 | 371 | auto endIt = beginIt; |
c9134a29 | 372 | |
0310790c | 373 | if (map->size() >= toRemove) { |
c9134a29 | 374 | std::advance(endIt, toRemove); |
0310790c | 375 | map->erase(beginIt, endIt); |
c7803194 | 376 | shard.d_entriesCount -= toRemove; |
c9134a29 | 377 | removed += toRemove; |
2b3eefc3 RG |
378 | } |
379 | else { | |
0310790c RG |
380 | removed += map->size(); |
381 | map->clear(); | |
c7803194 | 382 | shard.d_entriesCount = 0; |
2b3eefc3 RG |
383 | } |
384 | } | |
f627611d RG |
385 | |
386 | return removed; | |
4275aaba RG |
387 | } |
388 | ||
f627611d | 389 | size_t DNSDistPacketCache::expungeByName(const DNSName& name, uint16_t qtype, bool suffixMatch) |
886e2cf2 | 390 | { |
f627611d RG |
391 | size_t removed = 0; |
392 | ||
f23ed0a6 | 393 | for (auto& shard : d_shards) { |
6e41eb90 | 394 | auto map = shard.d_map.write_lock(); |
2b3eefc3 | 395 | |
0310790c | 396 | for(auto it = map->begin(); it != map->end(); ) { |
2b3eefc3 RG |
397 | const CacheValue& value = it->second; |
398 | ||
399 | if ((value.qname == name || (suffixMatch && value.qname.isPartOf(name))) && (qtype == QType::ANY || qtype == value.qtype)) { | |
0310790c | 400 | it = map->erase(it); |
f23ed0a6 | 401 | --shard.d_entriesCount; |
f627611d | 402 | ++removed; |
2b3eefc3 RG |
403 | } else { |
404 | ++it; | |
405 | } | |
886e2cf2 RG |
406 | } |
407 | } | |
f627611d RG |
408 | |
409 | return removed; | |
886e2cf2 RG |
410 | } |
411 | ||
412 | bool DNSDistPacketCache::isFull() | |
413 | { | |
2b3eefc3 RG |
414 | return (getSize() >= d_maxEntries); |
415 | } | |
416 | ||
417 | uint64_t DNSDistPacketCache::getSize() | |
418 | { | |
419 | uint64_t count = 0; | |
420 | ||
57533af1 | 421 | for (auto& shard : d_shards) { |
c7803194 | 422 | count += shard.d_entriesCount; |
2b3eefc3 RG |
423 | } |
424 | ||
425 | return count; | |
886e2cf2 RG |
426 | } |
427 | ||
47698274 | 428 | uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA) |
886e2cf2 | 429 | { |
47698274 | 430 | return getDNSPacketMinTTL(packet, length, seenNoDataSOA); |
886e2cf2 RG |
431 | } |
432 | ||
d84ea5be | 433 | uint32_t DNSDistPacketCache::getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP) |
886e2cf2 RG |
434 | { |
435 | uint32_t result = 0; | |
436 | /* skip the query ID */ | |
341d2553 RG |
437 | if (packet.size() < sizeof(dnsheader)) { |
438 | throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packet.size()) +")"); | |
fa980c59 RG |
439 | } |
440 | ||
341d2553 | 441 | result = burtle(&packet.at(2), sizeof(dnsheader) - 2, result); |
83c22ef3 | 442 | result = burtleCI((const unsigned char*) qname.c_str(), qname.length(), result); |
341d2553 RG |
443 | if (packet.size() < sizeof(dnsheader) + qnameWireLength) { |
444 | throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packet.size()) + " < " + std::to_string(sizeof(dnsheader) + qnameWireLength) + ")"); | |
cceddbef | 445 | } |
341d2553 | 446 | if (packet.size() > ((sizeof(dnsheader) + qnameWireLength))) { |
56f16502 CHB |
447 | if (!d_optionsToSkip.empty()) { |
448 | /* skip EDNS options if any */ | |
fa5a722b | 449 | result = PacketCache::hashAfterQname(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_optionsToSkip); |
fa980c59 RG |
450 | } |
451 | else { | |
341d2553 | 452 | result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), packet.size() - (sizeof(dnsheader) + qnameWireLength), result); |
fa980c59 | 453 | } |
cceddbef | 454 | } |
d84ea5be | 455 | result = burtle((const unsigned char*) &receivedOverUDP, sizeof(receivedOverUDP), result); |
886e2cf2 RG |
456 | return result; |
457 | } | |
458 | ||
2b3eefc3 RG |
459 | uint32_t DNSDistPacketCache::getShardIndex(uint32_t key) const |
460 | { | |
461 | return key % d_shardCount; | |
462 | } | |
463 | ||
886e2cf2 RG |
464 | string DNSDistPacketCache::toString() |
465 | { | |
2b3eefc3 | 466 | return std::to_string(getSize()) + "/" + std::to_string(d_maxEntries); |
886e2cf2 | 467 | } |
9e9be156 RG |
468 | |
469 | uint64_t DNSDistPacketCache::getEntriesCount() | |
470 | { | |
2b3eefc3 | 471 | return getSize(); |
9e9be156 | 472 | } |
f037144c RG |
473 | |
474 | uint64_t DNSDistPacketCache::dump(int fd) | |
475 | { | |
e30bc0cf | 476 | auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose); |
f037144c RG |
477 | if (fp == nullptr) { |
478 | return 0; | |
479 | } | |
480 | ||
e30bc0cf | 481 | fprintf(fp.get(), "; dnsdist's packet cache dump follows\n;\n"); |
f037144c RG |
482 | |
483 | uint64_t count = 0; | |
484 | time_t now = time(nullptr); | |
57533af1 | 485 | for (auto& shard : d_shards) { |
0310790c | 486 | auto map = shard.d_map.read_lock(); |
f037144c | 487 | |
0310790c | 488 | for (const auto& entry : *map) { |
f037144c RG |
489 | const CacheValue& value = entry.second; |
490 | count++; | |
491 | ||
492 | try { | |
d372faa2 RG |
493 | uint8_t rcode = 0; |
494 | if (value.len >= sizeof(dnsheader)) { | |
495 | dnsheader dh; | |
496 | memcpy(&dh, value.value.data(), sizeof(dnsheader)); | |
497 | rcode = dh.rcode; | |
498 | } | |
499 | ||
d84ea5be | 500 | fprintf(fp.get(), "%s %" PRId64 " %s ; rcode %" PRIu8 ", key %" PRIu32 ", length %" PRIu16 ", received over UDP %d, added %" PRId64 "\n", value.qname.toString().c_str(), static_cast<int64_t>(value.validity - now), QType(value.qtype).toString().c_str(), rcode, entry.first, value.len, value.receivedOverUDP, static_cast<int64_t>(value.added)); |
f037144c RG |
501 | } |
502 | catch(...) { | |
e30bc0cf | 503 | fprintf(fp.get(), "; error printing '%s'\n", value.qname.empty() ? "EMPTY" : value.qname.toString().c_str()); |
f037144c RG |
504 | } |
505 | } | |
506 | } | |
507 | ||
f037144c RG |
508 | return count; |
509 | } | |
56f16502 | 510 | |
c1f09824 | 511 | void DNSDistPacketCache::setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip) |
56f16502 | 512 | { |
56f16502 | 513 | d_optionsToSkip = optionsToSkip; |
56f16502 | 514 | } |
fec4382e RG |
515 | |
516 | std::set<DNSName> DNSDistPacketCache::getDomainsContainingRecords(const ComboAddress& addr) | |
517 | { | |
518 | std::set<DNSName> domains; | |
519 | ||
520 | for (auto& shard : d_shards) { | |
521 | auto map = shard.d_map.read_lock(); | |
522 | ||
523 | for (const auto& entry : *map) { | |
524 | const CacheValue& value = entry.second; | |
525 | ||
526 | try { | |
527 | dnsheader dh; | |
6efda4bf RG |
528 | if (value.len < sizeof(dnsheader)) { |
529 | continue; | |
fec4382e | 530 | } |
6efda4bf RG |
531 | |
532 | memcpy(&dh, value.value.data(), sizeof(dnsheader)); | |
fec4382e RG |
533 | if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) { |
534 | continue; | |
535 | } | |
536 | ||
537 | bool found = false; | |
d73de874 | 538 | bool valid = visitDNSPacket(value.value, [addr, &found](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) { |
fec4382e RG |
539 | if (qtype == QType::A && qclass == QClass::IN && addr.isIPv4() && rdatalength == 4 && rdata != nullptr) { |
540 | ComboAddress parsed; | |
541 | parsed.sin4.sin_family = AF_INET; | |
542 | memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength); | |
543 | if (parsed == addr) { | |
544 | found = true; | |
545 | return true; | |
546 | } | |
547 | } | |
548 | else if (qtype == QType::AAAA && qclass == QClass::IN && addr.isIPv6() && rdatalength == 16 && rdata != nullptr) { | |
549 | ComboAddress parsed; | |
550 | parsed.sin6.sin6_family = AF_INET6; | |
551 | memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength); | |
552 | if (parsed == addr) { | |
553 | found = true; | |
554 | return true; | |
555 | } | |
556 | } | |
557 | ||
558 | return false; | |
559 | }); | |
560 | ||
561 | if (valid && found) { | |
562 | domains.insert(value.qname); | |
563 | } | |
564 | } | |
565 | catch (...) { | |
566 | continue; | |
567 | } | |
568 | } | |
569 | } | |
570 | ||
571 | return domains; | |
572 | } | |
573 | ||
574 | std::set<ComboAddress> DNSDistPacketCache::getRecordsForDomain(const DNSName& domain) | |
575 | { | |
576 | std::set<ComboAddress> addresses; | |
577 | ||
578 | for (auto& shard : d_shards) { | |
579 | auto map = shard.d_map.read_lock(); | |
580 | ||
581 | for (const auto& entry : *map) { | |
582 | const CacheValue& value = entry.second; | |
583 | ||
584 | try { | |
585 | if (value.qname != domain) { | |
586 | continue; | |
587 | } | |
588 | ||
589 | dnsheader dh; | |
6efda4bf RG |
590 | if (value.len < sizeof(dnsheader)) { |
591 | continue; | |
fec4382e | 592 | } |
6efda4bf RG |
593 | |
594 | memcpy(&dh, value.value.data(), sizeof(dnsheader)); | |
fec4382e RG |
595 | if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) { |
596 | continue; | |
597 | } | |
598 | ||
d73de874 | 599 | visitDNSPacket(value.value, [&addresses](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) { |
fec4382e RG |
600 | if (qtype == QType::A && qclass == QClass::IN && rdatalength == 4 && rdata != nullptr) { |
601 | ComboAddress parsed; | |
602 | parsed.sin4.sin_family = AF_INET; | |
603 | memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength); | |
604 | addresses.insert(parsed); | |
605 | } | |
606 | else if (qtype == QType::AAAA && qclass == QClass::IN && rdatalength == 16 && rdata != nullptr) { | |
607 | ComboAddress parsed; | |
608 | parsed.sin6.sin6_family = AF_INET6; | |
609 | memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength); | |
610 | addresses.insert(parsed); | |
611 | } | |
612 | ||
613 | return false; | |
614 | }); | |
615 | } | |
616 | catch (...) { | |
617 | continue; | |
618 | } | |
619 | } | |
620 | } | |
621 | ||
622 | return addresses; | |
623 | } | |
7b71c89b RG |
624 | |
625 | void DNSDistPacketCache::setMaximumEntrySize(size_t maxSize) | |
626 | { | |
627 | d_maximumEntrySize = maxSize; | |
628 | } |