]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/recursordist/recpacketcache.cc
rec: Switch to `pdns::UniqueFilePtr`
[thirdparty/pdns.git] / pdns / recursordist / recpacketcache.cc
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 #include <iostream>
5 #include <cinttypes>
6
7 #include "recpacketcache.hh"
8 #include "cachecleaner.hh"
9 #include "dns.hh"
10 #include "namespaces.hh"
11 #include "rec-taskqueue.hh"
12
13 unsigned int RecursorPacketCache::s_refresh_ttlperc{0};
14
15 void RecursorPacketCache::setShardSizes(size_t shardSize)
16 {
17 for (auto& shard : d_maps) {
18 auto lock = shard.lock();
19 lock->d_shardSize = shardSize;
20 }
21 }
22
23 uint64_t RecursorPacketCache::size() const
24 {
25 uint64_t count = 0;
26 for (const auto& map : d_maps) {
27 count += map.getEntriesCount();
28 }
29 return count;
30 }
31
32 uint64_t RecursorPacketCache::bytes()
33 {
34 uint64_t sum = 0;
35 for (auto& shard : d_maps) {
36 auto lock = shard.lock();
37 for (const auto& entry : lock->d_map) {
38 sum += sizeof(entry) + entry.d_packet.length() + 4;
39 }
40 }
41 return sum;
42 }
43
44 uint64_t RecursorPacketCache::getHits()
45 {
46 uint64_t sum = 0;
47 for (auto& shard : d_maps) {
48 auto lock = shard.lock();
49 sum += lock->d_hits;
50 }
51 return sum;
52 }
53
54 uint64_t RecursorPacketCache::getMisses()
55 {
56 uint64_t sum = 0;
57 for (auto& shard : d_maps) {
58 auto lock = shard.lock();
59 sum += lock->d_misses;
60 }
61 return sum;
62 }
63
64 pair<uint64_t, uint64_t> RecursorPacketCache::stats()
65 {
66 uint64_t contended = 0;
67 uint64_t acquired = 0;
68 for (auto& shard : d_maps) {
69 auto content = shard.lock();
70 contended += content->d_contended_count;
71 acquired += content->d_acquired_count;
72 }
73 return {contended, acquired};
74 }
75
76 uint64_t RecursorPacketCache::doWipePacketCache(const DNSName& name, uint16_t qtype, bool subtree)
77 {
78 uint64_t count = 0;
79 for (auto& map : d_maps) {
80 auto shard = map.lock();
81 auto& idx = shard->d_map.get<NameTag>();
82 for (auto iter = idx.lower_bound(name); iter != idx.end();) {
83 if (subtree) {
84 if (!iter->d_name.isPartOf(name)) { // this is case insensitive
85 break;
86 }
87 }
88 else {
89 if (iter->d_name != name) {
90 break;
91 }
92 }
93 if (qtype == 0xffff || iter->d_type == qtype) {
94 iter = idx.erase(iter);
95 map.decEntriesCount();
96 count++;
97 }
98 else {
99 ++iter;
100 }
101 }
102 }
103 return count;
104 }
105
106 bool RecursorPacketCache::qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass)
107 {
108 // this ignores checking on the EDNS subnet flags!
109 if (qname != iter->d_name || iter->d_type != qtype || iter->d_class != qclass) {
110 return false;
111 }
112
113 static const std::unordered_set<uint16_t> optionsToSkip{EDNSOptionCode::COOKIE, EDNSOptionCode::ECS};
114 return queryMatches(iter->d_query, queryPacket, qname, optionsToSkip);
115 }
116
117 bool RecursorPacketCache::checkResponseMatches(MapCombo::LockedContent& shard, std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, OptPBData* pbdata)
118 {
119 for (auto iter = range.first; iter != range.second; ++iter) {
120 // the possibility is VERY real that we get hits that are not right - birthday paradox
121 if (!qrMatch(iter, queryPacket, qname, qtype, qclass)) {
122 continue;
123 }
124
125 if (now < iter->d_ttd) { // it is right, it is fresh!
126 // coverity[store_truncates_time_t]
127 *age = static_cast<uint32_t>(now - iter->d_creation);
128 // we know ttl is > 0
129 auto ttl = static_cast<uint32_t>(iter->d_ttd - now);
130 if (s_refresh_ttlperc > 0 && !iter->d_submitted && taskQTypeIsSupported(qtype)) {
131 const dnsheader_aligned header(iter->d_packet.data());
132 const auto* headerPtr = header.get();
133 if (headerPtr->rcode == RCode::NoError) {
134 const uint32_t deadline = iter->getOrigTTL() * s_refresh_ttlperc / 100;
135 const bool almostExpired = ttl <= deadline;
136 if (almostExpired) {
137 iter->d_submitted = true;
138 pushAlmostExpiredTask(qname, qtype, iter->d_ttd, Netmask());
139 }
140 }
141 }
142 *responsePacket = iter->d_packet;
143 responsePacket->replace(0, 2, queryPacket.c_str(), 2);
144 *valState = iter->d_vstate;
145
146 const size_t wirelength = qname.wirelength();
147 if (responsePacket->size() > (sizeof(dnsheader) + wirelength)) {
148 responsePacket->replace(sizeof(dnsheader), wirelength, queryPacket, sizeof(dnsheader), wirelength);
149 }
150
151 shard.d_hits++;
152 moveCacheItemToBack<SequencedTag>(shard.d_map, iter);
153
154 if (pbdata != nullptr) {
155 if (iter->d_pbdata) {
156 *pbdata = iter->d_pbdata;
157 }
158 else {
159 *pbdata = boost::none;
160 }
161 }
162
163 return true;
164 }
165 // We used to move the item to the front of "the to be deleted" sequence,
166 // but we very likely will update the entry very soon, so leave it
167 shard.d_misses++;
168 break;
169 }
170
171 return false;
172 }
173
174 static const std::unordered_set<uint16_t> s_skipOptions = {EDNSOptionCode::ECS, EDNSOptionCode::COOKIE};
175
176 bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now,
177 std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp)
178 {
179 *qhash = canHashPacket(queryPacket, s_skipOptions);
180 auto& map = getMap(tag, *qhash, tcp);
181 auto shard = map.lock();
182 const auto& idx = shard->d_map.get<HashTag>();
183 auto range = idx.equal_range(std::tie(tag, *qhash, tcp));
184
185 if (range.first == range.second) {
186 shard->d_misses++;
187 return false;
188 }
189
190 return checkResponseMatches(*shard, range, queryPacket, qname, qtype, qclass, now, responsePacket, age, valState, pbdata);
191 }
192
193 bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now,
194 std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp)
195 {
196 *qhash = canHashPacket(queryPacket, s_skipOptions);
197 auto& map = getMap(tag, *qhash, tcp);
198 auto shard = map.lock();
199 const auto& idx = shard->d_map.get<HashTag>();
200 auto range = idx.equal_range(std::tie(tag, *qhash, tcp));
201
202 if (range.first == range.second) {
203 shard->d_misses++;
204 return false;
205 }
206
207 qname = DNSName(queryPacket.c_str(), static_cast<int>(queryPacket.length()), sizeof(dnsheader), false, qtype, qclass);
208
209 return checkResponseMatches(*shard, range, queryPacket, qname, *qtype, *qclass, now, responsePacket, age, valState, pbdata);
210 }
211
212 void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, OptPBData&& pbdata, bool tcp)
213 {
214 auto& map = getMap(tag, qhash, tcp);
215 auto shard = map.lock();
216 auto& idx = shard->d_map.get<HashTag>();
217 auto range = idx.equal_range(std::tie(tag, qhash, tcp));
218 auto iter = range.first;
219
220 for (; iter != range.second; ++iter) {
221 if (iter->d_type != qtype || iter->d_class != qclass || iter->d_name != qname) {
222 continue;
223 }
224
225 moveCacheItemToBack<SequencedTag>(shard->d_map, iter);
226 iter->d_packet = std::move(responsePacket);
227 iter->d_query = std::move(query);
228 iter->d_ttd = now + ttl;
229 iter->d_creation = now;
230 iter->d_vstate = valState;
231 iter->d_submitted = false;
232 if (pbdata) {
233 iter->d_pbdata = std::move(*pbdata);
234 }
235
236 return;
237 }
238
239 struct Entry entry(DNSName(qname), qtype, qclass, std::move(responsePacket), std::move(query), tcp, qhash, now + ttl, now, tag, valState);
240 if (pbdata) {
241 entry.d_pbdata = std::move(*pbdata);
242 }
243
244 shard->d_map.insert(entry);
245 map.incEntriesCount();
246
247 if (shard->d_map.size() > shard->d_shardSize) {
248 auto& seq_idx = shard->d_map.get<SequencedTag>();
249 seq_idx.erase(seq_idx.begin());
250 map.decEntriesCount();
251 }
252 assert(map.getEntriesCount() == shard->d_map.size()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clib implementation
253 }
254
255 void RecursorPacketCache::doPruneTo(time_t now, size_t maxSize)
256 {
257 size_t cacheSize = size();
258 pruneMutexCollectionsVector<SequencedTag>(now, d_maps, maxSize, cacheSize);
259 }
260
261 uint64_t RecursorPacketCache::doDump(int file)
262 {
263 int fdupped = dup(file);
264 if (fdupped == -1) {
265 return 0;
266 }
267 auto filePtr = pdns::UniqueFilePtr(fdopen(fdupped, "w"));
268 if (!filePtr) {
269 close(fdupped);
270 return 0;
271 }
272
273 uint64_t count = 0;
274 time_t now = time(nullptr);
275
276 size_t shardNum = 0;
277 size_t min = std::numeric_limits<size_t>::max();
278 size_t max = 0;
279 uint64_t maxSize = 0;
280
281 for (auto& shard : d_maps) {
282 auto lock = shard.lock();
283 const auto& sidx = lock->d_map.get<SequencedTag>();
284 const auto shardSize = lock->d_map.size();
285 fprintf(filePtr.get(), "; packetcache shard %zu; size %zu/%zu\n", shardNum, shardSize, lock->d_shardSize);
286 min = std::min(min, shardSize);
287 max = std::max(max, shardSize);
288 maxSize += lock->d_shardSize;
289 shardNum++;
290 for (const auto& entry : sidx) {
291 count++;
292 try {
293 fprintf(filePtr.get(), "%s %" PRId64 " %s ; tag %d %s\n", entry.d_name.toString().c_str(), static_cast<int64_t>(entry.d_ttd - now), DNSRecordContent::NumberToType(entry.d_type).c_str(), entry.d_tag, entry.d_tcp ? "tcp" : "udp");
294 }
295 catch (...) {
296 fprintf(filePtr.get(), "; error printing '%s'\n", entry.d_name.empty() ? "EMPTY" : entry.d_name.toString().c_str());
297 }
298 }
299 }
300 fprintf(filePtr.get(), "; packetcache size: %" PRIu64 "/%" PRIu64 " shards: %zu min/max shard size: %zu/%zu\n", size(), maxSize, d_maps.size(), min, max);
301 return count;
302 }