• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 12595591960

03 Jan 2025 09:27AM UTC coverage: 62.774% (+2.5%) from 60.245%
12595591960

Pull #15008

github

web-flow
Merge c2a2749d3 into 788f396a7
Pull Request #15008: Do not follow CNAME records for ANY or CNAME queries

30393 of 78644 branches covered (38.65%)

Branch coverage included in aggregate %.

105822 of 138350 relevant lines covered (76.49%)

4613078.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.07
/pdns/recursordist/recursor_cache.hh
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
#pragma once
23
#include <string>
24
#include "dns.hh"
25
#include "qtype.hh"
26
#include "misc.hh"
27
#include "dnsname.hh"
28
#include "dnsrecords.hh"
29
#include <boost/utility.hpp>
30
#include <boost/multi_index_container.hpp>
31
#include <boost/multi_index/ordered_index.hpp>
32
#include <boost/multi_index/hashed_index.hpp>
33
#include <boost/multi_index/key_extractors.hpp>
34
#include <boost/multi_index/sequenced_index.hpp>
35
#include <boost/version.hpp>
36
#include "iputils.hh"
37
#include "lock.hh"
38
#include "stat_t.hh"
39
#include "validate.hh"
40
#undef max
41

42
#include "namespaces.hh"
43
using namespace ::boost::multi_index;
44

45
class MemRecursorCache : public boost::noncopyable //  : public RecursorCache
46
{
47
public:
48
  MemRecursorCache(size_t mapsCount = 1024);
49

50
  // The number of times a stale cache entry is extended
51
  static uint16_t s_maxServedStaleExtensions;
52
  // The time a stale cache entry is extended
53
  static constexpr uint32_t s_serveStaleExtensionPeriod = 30;
54

55
  // Maximum size of RRSet we are willing to cache. If the RRSet is larger, we do create an entry,
56
  // but mark it as too big. Subsequent gets will cause an ImmediateServFailException to be thrown.
57
  static uint16_t s_maxRRSetSize;
58
  static bool s_limitQTypeAny;
59

60
  [[nodiscard]] size_t size() const;
61
  [[nodiscard]] size_t bytes();
62
  [[nodiscard]] pair<uint64_t, uint64_t> stats();
63
  [[nodiscard]] size_t ecsIndexSize();
64

65
  size_t getRecordSets(size_t perShard, size_t maxSize, std::string& ret);
66
  size_t putRecordSets(const std::string& pbuf);
67

68
  using OptTag = boost::optional<std::string>;
69

70
  using Flags = uint8_t;
71
  static constexpr Flags None = 0;
72
  static constexpr Flags RequireAuth = 1 << 0;
73
  static constexpr Flags Refresh = 1 << 1;
74
  static constexpr Flags ServeStale = 1 << 2;
75

76
  [[nodiscard]] time_t get(time_t, const DNSName& qname, QType qtype, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag = boost::none, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures = nullptr, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs = nullptr, bool* variable = nullptr, vState* state = nullptr, bool* wasAuth = nullptr, DNSName* fromAuthZone = nullptr, ComboAddress* fromAuthIP = nullptr);
77

78
  void replace(time_t, const DNSName& qname, QType qtype, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask = boost::none, const OptTag& routingTag = boost::none, vState state = vState::Indeterminate, boost::optional<ComboAddress> from = boost::none, bool refresh = false, time_t ttl_time = time(nullptr));
79

80
  void doPrune(time_t now, size_t keep);
81
  uint64_t doDump(int fileDesc, size_t maxCacheEntries);
82

83
  size_t doWipeCache(const DNSName& name, bool sub, QType qtype = 0xffff);
84
  bool doAgeCache(time_t now, const DNSName& name, QType qtype, uint32_t newTTL);
85
  bool updateValidationStatus(time_t now, const DNSName& qname, QType qtype, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
86

87
  static void resetStaticsForTests();
88

89
  [[nodiscard]] auto getCacheHits() const
90
  {
172✔
91
    return cacheHits.load();
172✔
92
  }
172✔
93
  [[nodiscard]] auto getCacheMisses() const
94
  {
172✔
95
    return cacheMisses.load();
172✔
96
  }
172✔
97

98
  void incCacheHits()
99
  {
445✔
100
    ++cacheHits;
445✔
101
  }
445✔
102
  void incCacheMisses()
103
  {
1,895✔
104
    ++cacheMisses;
1,895✔
105
  }
1,895✔
106

107
private:
108
  pdns::stat_t cacheHits{0}, cacheMisses{0};
109

110
  struct CacheEntry
111
  {
112
    CacheEntry(const std::tuple<DNSName, QType, OptTag, Netmask>& key, bool auth) :
113
      d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_qtype(std::get<1>(key)), d_auth(auth)
114
    {
1,247,273✔
115
    }
1,247,273✔
116

117
    using records_t = vector<std::shared_ptr<const DNSRecordContent>>;
118

119
    bool isStale(time_t now) const
120
    {
5,031✔
121
      // We like to keep things in cache when we (potentially) should serve stale
122
      if (s_maxServedStaleExtensions > 0) {
5,031!
123
        return d_ttd + static_cast<time_t>(s_maxServedStaleExtensions) * std::min(s_serveStaleExtensionPeriod, d_orig_ttl) < now;
×
124
      }
×
125
      return d_ttd < now;
5,031✔
126
    }
5,031✔
127

128
    bool isEntryUsable(time_t now, bool serveStale) const
129
    {
807,684✔
130
      // When serving stale, we consider expired records
131
      return d_ttd > now || serveStale || d_servedStale != 0;
807,684✔
132
    }
807,684✔
133

134
    bool shouldReplace(time_t now, bool auth, vState state, bool refresh);
135

136
    records_t d_records;
137
    std::vector<std::shared_ptr<const RRSIGRecordContent>> d_signatures;
138
    std::vector<std::shared_ptr<DNSRecord>> d_authorityRecs;
139
    DNSName d_qname;
140
    DNSName d_authZone;
141
    ComboAddress d_from;
142
    Netmask d_netmask;
143
    OptTag d_rtag;
144
    mutable vState d_state{vState::Indeterminate};
145
    mutable time_t d_ttd{0};
146
    uint32_t d_orig_ttl{0};
147
    mutable uint16_t d_servedStale{0};
148
    QType d_qtype;
149
    bool d_auth;
150
    mutable bool d_submitted{false}; // whether this entry has been queued for refetch
151
    bool d_tooBig{false};
152
  };
153

154
  bool replace(CacheEntry&& entry);
155
  // Using templates to avoid exposing protozero types in this header file
156
  template <typename T>
157
  bool putRecordSet(T&);
158
  template <typename T, typename U>
159
  void getRecordSet(T&, U);
160

161
  /* The ECS Index (d_ecsIndex) keeps track of whether there is any ECS-specific
162
     entry for a given (qname,qtype) entry in the cache (d_map), and if so
163
     provides a NetmaskTree of those ECS entries.
164
     This allows figuring out quickly if we should look for an entry
165
     specific to the requestor IP, and if so which entry is the most
166
     specific one.
167
     Keeping the entries in the regular cache is currently necessary
168
     because of the way we manage expired entries (moving them to the
169
     front of the expunge queue to be deleted at a regular interval).
170
  */
171
  class ECSIndexEntry
172
  {
173
  public:
174
    ECSIndexEntry(DNSName qname, QType qtype) :
175
      d_qname(std::move(qname)), d_qtype(qtype)
176
    {
83✔
177
    }
83✔
178

179
    [[nodiscard]] Netmask lookupBestMatch(const ComboAddress& addr) const
180
    {
681✔
181
      const auto* best = d_nmt.lookup(addr);
681✔
182
      if (best != nullptr) {
681✔
183
        return best->first;
365✔
184
      }
365✔
185

186
      return {};
316✔
187
    }
681✔
188

189
    void addMask(const Netmask& netmask) const
190
    {
620✔
191
      d_nmt.insert(netmask).second = true;
620✔
192
    }
620✔
193

194
    void removeNetmask(const Netmask& netmask) const
195
    {
524✔
196
      d_nmt.erase(netmask);
524✔
197
    }
524✔
198

199
    [[nodiscard]] bool isEmpty() const
200
    {
1,203✔
201
      return d_nmt.empty();
1,203✔
202
    }
1,203✔
203

204
    mutable NetmaskTree<bool> d_nmt;
205
    DNSName d_qname;
206
    QType d_qtype;
207
  };
208

209
  struct HashedTag
210
  {
211
  };
212
  struct SequencedTag
213
  {
214
  };
215
  struct NameAndRTagOnlyHashedTag
216
  {
217
  };
218
  struct OrderedTag
219
  {
220
  };
221

222
  using cache_t = multi_index_container<
223
    CacheEntry,
224
    indexed_by<
225
      ordered_unique<tag<OrderedTag>,
226
                     composite_key<
227
                       CacheEntry,
228
                       member<CacheEntry, DNSName, &CacheEntry::d_qname>,
229
                       member<CacheEntry, QType, &CacheEntry::d_qtype>,
230
                       member<CacheEntry, OptTag, &CacheEntry::d_rtag>,
231
                       member<CacheEntry, Netmask, &CacheEntry::d_netmask>>,
232
                     composite_key_compare<CanonDNSNameCompare, std::less<>, std::less<>, std::less<>>>,
233
      sequenced<tag<SequencedTag>>,
234
      hashed_non_unique<tag<NameAndRTagOnlyHashedTag>,
235
                        composite_key<
236
                          CacheEntry,
237
                          member<CacheEntry, DNSName, &CacheEntry::d_qname>,
238
                          member<CacheEntry, OptTag, &CacheEntry::d_rtag>>>>>;
239

240
  using OrderedTagIterator_t = MemRecursorCache::cache_t::index<MemRecursorCache::OrderedTag>::type::iterator;
241
  using NameAndRTagOnlyHashedTagIterator_t = MemRecursorCache::cache_t::index<MemRecursorCache::NameAndRTagOnlyHashedTag>::type::iterator;
242

243
  using ecsIndex_t = multi_index_container<
244
    ECSIndexEntry,
245
    indexed_by<
246
      hashed_unique<tag<HashedTag>,
247
                    composite_key<
248
                      ECSIndexEntry,
249
                      member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
250
                      member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>>,
251
      ordered_unique<tag<OrderedTag>,
252
                     composite_key<
253
                       ECSIndexEntry,
254
                       member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
255
                       member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>,
256
                     composite_key_compare<CanonDNSNameCompare, std::less<>>>>>;
257

258
  using Entries = std::pair<NameAndRTagOnlyHashedTagIterator_t, NameAndRTagOnlyHashedTagIterator_t>;
259

260
  struct MapCombo
261
  {
262
    MapCombo() = default;
820,242✔
263
    ~MapCombo() = default;
669,702✔
264
    MapCombo(const MapCombo&) = delete;
265
    MapCombo& operator=(const MapCombo&) = delete;
266
    MapCombo(MapCombo&&) = delete;
267
    MapCombo& operator=(MapCombo&&) = delete;
268

269
    struct LockedContent
270
    {
271
      cache_t d_map;
272
      ecsIndex_t d_ecsIndex;
273
      DNSName d_cachedqname;
274
      OptTag d_cachedrtag;
275
      Entries d_cachecache;
276
      uint64_t d_contended_count{0};
277
      uint64_t d_acquired_count{0};
278
      bool d_cachecachevalid{false};
279

280
      void invalidate()
281
      {
87,108✔
282
        d_cachecachevalid = false;
87,108✔
283
      }
87,108✔
284

285
      void preRemoval(const CacheEntry& entry)
286
      {
548✔
287
        if (entry.d_netmask.empty()) {
548✔
288
          return;
28✔
289
        }
28✔
290

291
        auto key = std::tie(entry.d_qname, entry.d_qtype);
520✔
292
        auto ecsIndexEntry = d_ecsIndex.find(key);
520✔
293
        if (ecsIndexEntry != d_ecsIndex.end()) {
520!
294
          ecsIndexEntry->removeNetmask(entry.d_netmask);
520✔
295
          if (ecsIndexEntry->isEmpty()) {
520✔
296
            d_ecsIndex.erase(ecsIndexEntry);
8✔
297
          }
8✔
298
        }
520✔
299
      }
520✔
300
    };
301

302
    LockGuardedTryHolder<LockedContent> lock()
303
    {
4,243,960✔
304
      auto locked = d_content.try_lock();
4,243,960✔
305
      if (!locked.owns_lock()) {
4,243,960✔
306
        locked.lock();
4,855✔
307
        ++locked->d_contended_count;
4,855✔
308
      }
4,855✔
309
      ++locked->d_acquired_count;
4,243,960✔
310
      return locked;
4,243,960✔
311
    }
4,243,960✔
312

313
    [[nodiscard]] auto getEntriesCount() const
314
    {
508,106✔
315
      return d_entriesCount.load();
508,106✔
316
    }
508,106✔
317

318
    void incEntriesCount()
319
    {
1,247,273✔
320
      ++d_entriesCount;
1,247,273✔
321
    }
1,247,273✔
322

323
    void decEntriesCount()
324
    {
1,203,123✔
325
      --d_entriesCount;
1,203,123✔
326
    }
1,203,123✔
327

328
    void clearEntriesCount()
329
    {
×
330
      d_entriesCount = 0;
×
331
    }
×
332

333
  private:
334
    LockGuarded<LockedContent> d_content;
335
    pdns::stat_t d_entriesCount{0};
336
  };
337

338
  vector<MapCombo> d_maps;
339
  MapCombo& getMap(const DNSName& qname)
340
  {
2,638,086✔
341
    return d_maps.at(qname.hash() % d_maps.size());
2,638,086✔
342
  }
2,638,086✔
343

344
  static time_t fakeTTD(OrderedTagIterator_t& entry, const DNSName& qname, QType qtype, time_t ret, time_t now, uint32_t origTTL, bool refresh);
345

346
  static bool entryMatches(OrderedTagIterator_t& entry, QType qtype, bool requireAuth, const ComboAddress& who);
347
  static Entries getEntries(MapCombo::LockedContent& map, const DNSName& qname, QType qtype, const OptTag& rtag);
348
  static cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale);
349

350
  static time_t handleHit(time_t now, MapCombo::LockedContent& content, OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* authZone, ComboAddress* fromAuthIP);
351
  static void updateStaleEntry(time_t now, OrderedTagIterator_t& entry);
352
  static void handleServeStaleBookkeeping(time_t, bool, OrderedTagIterator_t&);
353
};
354

355
namespace boost
356
{
357
size_t hash_value(const MemRecursorCache::OptTag& rtag);
358
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc