File: recursor_cache.hh

package info (click to toggle)
pdns-recursor 5.3.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,108 kB
  • sloc: cpp: 109,513; javascript: 20,651; python: 5,657; sh: 5,069; makefile: 780; ansic: 582; xml: 37
file content (385 lines) | stat: -rw-r--r-- 14,012 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
 * This file is part of PowerDNS or dnsdist.
 * Copyright -- PowerDNS.COM B.V. and its contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * In addition, for the avoidance of any doubt, permission is granted to
 * link this program with OpenSSL and to (re)distribute the binaries
 * produced as the result of such linking.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#pragma once
#include <string>
#include "dns.hh"
#include "qtype.hh"
#include "misc.hh"
#include "dnsname.hh"
#include "dnsrecords.hh"
#include <boost/utility.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/key_extractors.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/version.hpp>
#include "iputils.hh"
#include "lock.hh"
#include "stat_t.hh"
#include "validate.hh"
#undef max

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

class MemRecursorCache : public boost::noncopyable //  : public RecursorCache
{
public:
  MemRecursorCache(size_t mapsCount = 1024);

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

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

  [[nodiscard]] size_t size() const;
  [[nodiscard]] size_t bytes();
  [[nodiscard]] pair<uint64_t, uint64_t> stats();
  [[nodiscard]] size_t ecsIndexSize();

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

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

  using Flags = uint8_t;
  static constexpr Flags None = 0;
  static constexpr Flags RequireAuth = 1 << 0;
  static constexpr Flags Refresh = 1 << 1;
  static constexpr Flags ServeStale = 1 << 2;

  // The type used to pass auth record data to replace(); If the vector is non-empty, the cache will
  // store a shared pointer to the copied data. The shared pointer will be returned by get().  There
  // are optimizations: an empty vector will be stored as a nullptr, but get() will return a pointer
  // to an already existing empty vector in that case, this is more convenient for the caller, since
  // it avoid checking for nullptr, just iterate as for the non-empty case.
  //
  // get() will return a shared vector to a const vector of shared pointers. Only a single shared
  // pointer gets copied, while earlier code would copy all shared pointer in the vector.
  //
  // In the current SyncRes code, AuthRecs never get appended to a non-empty vector while SigRecs do
  // get appended in some cases; the handleHit() code will take measures. In the future we might
  // want a more specialized data structure than a vector, it would require another level of
  // indirection though, so for now we construct a new shared vector if appending is needed. See
  // handleHit() for details.
  using AuthRecsVec = std::vector<DNSRecord>;
  using AuthRecs = std::shared_ptr<const AuthRecsVec>; // const to avoid modifying the vector, which would be bad for shared data
  const static AuthRecs s_emptyAuthRecs;

  // Use same setup as AuthRecs.
  using SigRecsVec = std::vector<std::shared_ptr<const RRSIGRecordContent>>;
  using SigRecs = std::shared_ptr<const SigRecsVec>; // Also const as it is shared
  const static SigRecs s_emptySigRecs;

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

  void replace(time_t, const DNSName& qname, QType qtype, const vector<DNSRecord>& content, const SigRecsVec& signatures, const AuthRecsVec& 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));

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

  size_t doWipeCache(const DNSName& name, bool sub, QType qtype = 0xffff);
  bool doAgeCache(time_t now, const DNSName& name, QType qtype, uint32_t newTTL);
  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);

  static void resetStaticsForTests();

  [[nodiscard]] auto getCacheHits() const
  {
    return cacheHits.load();
  }
  [[nodiscard]] auto getCacheMisses() const
  {
    return cacheMisses.load();
  }

  void incCacheHits()
  {
    ++cacheHits;
  }
  void incCacheMisses()
  {
    ++cacheMisses;
  }

private:
  pdns::stat_t cacheHits{0}, cacheMisses{0};

  struct CacheEntry
  {
    CacheEntry(const std::tuple<DNSName, QType, OptTag, Netmask>& key, bool auth) :
      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)
    {
    }

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

    bool isStale(time_t now) const
    {
      // We like to keep things in cache when we (potentially) should serve stale
      if (s_maxServedStaleExtensions > 0) {
        return d_ttd + static_cast<time_t>(s_maxServedStaleExtensions) * std::min(s_serveStaleExtensionPeriod, d_orig_ttl) < now;
      }
      return d_ttd < now;
    }

    bool isEntryUsable(time_t now, bool serveStale) const
    {
      // When serving stale, we consider expired records
      return d_ttd > now || serveStale || d_servedStale != 0;
    }

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

    [[nodiscard]] size_t sizeEstimate() const;
    [[nodiscard]] size_t authRecsSizeEstimate() const;
    [[nodiscard]] size_t sigRecsSizeEstimate() const;

    records_t d_records;
    SigRecs d_signatures;
    AuthRecs d_authorityRecs;
    DNSName d_qname;
    DNSName d_authZone;
    ComboAddress d_from;
    Netmask d_netmask;
    OptTag d_rtag;
    mutable vState d_state{vState::Indeterminate};
    mutable time_t d_ttd{0};
    uint32_t d_orig_ttl{0};
    mutable uint16_t d_servedStale{0};
    QType d_qtype;
    bool d_auth;
    mutable bool d_submitted{false}; // whether this entry has been queued for refetch
    bool d_tooBig{false};
  };

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

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

    [[nodiscard]] Netmask lookupBestMatch(const ComboAddress& addr) const
    {
      const auto* best = d_nmt.lookup(addr);
      if (best != nullptr) {
        return best->first;
      }

      return {};
    }

    void addMask(const Netmask& netmask) const
    {
      d_nmt.insert(netmask).second = true;
    }

    void removeNetmask(const Netmask& netmask) const
    {
      d_nmt.erase(netmask);
    }

    [[nodiscard]] bool isEmpty() const
    {
      return d_nmt.empty();
    }

    mutable NetmaskTree<bool> d_nmt;
    DNSName d_qname;
    QType d_qtype;
  };

  struct HashedTag
  {
  };
  struct SequencedTag
  {
  };
  struct NameAndRTagOnlyHashedTag
  {
  };
  struct OrderedTag
  {
  };

  using cache_t = multi_index_container<
    CacheEntry,
    indexed_by<
      ordered_unique<tag<OrderedTag>,
                     composite_key<
                       CacheEntry,
                       member<CacheEntry, DNSName, &CacheEntry::d_qname>,
                       member<CacheEntry, QType, &CacheEntry::d_qtype>,
                       member<CacheEntry, OptTag, &CacheEntry::d_rtag>,
                       member<CacheEntry, Netmask, &CacheEntry::d_netmask>>,
                     composite_key_compare<CanonDNSNameCompare, std::less<>, std::less<>, std::less<>>>,
      sequenced<tag<SequencedTag>>,
      hashed_non_unique<tag<NameAndRTagOnlyHashedTag>,
                        composite_key<
                          CacheEntry,
                          member<CacheEntry, DNSName, &CacheEntry::d_qname>,
                          member<CacheEntry, OptTag, &CacheEntry::d_rtag>>>>>;

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

  using ecsIndex_t = multi_index_container<
    ECSIndexEntry,
    indexed_by<
      hashed_unique<tag<HashedTag>,
                    composite_key<
                      ECSIndexEntry,
                      member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
                      member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>>,
      ordered_unique<tag<OrderedTag>,
                     composite_key<
                       ECSIndexEntry,
                       member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
                       member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>,
                     composite_key_compare<CanonDNSNameCompare, std::less<>>>>>;

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

  struct MapCombo
  {
    MapCombo() = default;
    ~MapCombo() = default;
    MapCombo(const MapCombo&) = delete;
    MapCombo& operator=(const MapCombo&) = delete;
    MapCombo(MapCombo&&) = delete;
    MapCombo& operator=(MapCombo&&) = delete;

    struct LockedContent
    {
      cache_t d_map;
      ecsIndex_t d_ecsIndex;
      DNSName d_cachedqname;
      OptTag d_cachedrtag;
      Entries d_cachecache;
      uint64_t d_contended_count{0};
      uint64_t d_acquired_count{0};
      bool d_cachecachevalid{false};

      void invalidate()
      {
        d_cachecachevalid = false;
      }

      void preRemoval(const CacheEntry& entry)
      {
        if (entry.d_netmask.empty()) {
          return;
        }

        auto key = std::tie(entry.d_qname, entry.d_qtype);
        auto ecsIndexEntry = d_ecsIndex.find(key);
        if (ecsIndexEntry != d_ecsIndex.end()) {
          ecsIndexEntry->removeNetmask(entry.d_netmask);
          if (ecsIndexEntry->isEmpty()) {
            d_ecsIndex.erase(ecsIndexEntry);
          }
        }
      }
    };

    LockGuardedTryHolder<LockedContent> lock()
    {
      auto locked = d_content.try_lock();
      if (!locked.owns_lock()) {
        locked.lock();
        ++locked->d_contended_count;
      }
      ++locked->d_acquired_count;
      return locked;
    }

    [[nodiscard]] auto getEntriesCount() const
    {
      return d_entriesCount.load();
    }

    void incEntriesCount()
    {
      ++d_entriesCount;
    }

    void decEntriesCount()
    {
      --d_entriesCount;
    }

    void clearEntriesCount()
    {
      d_entriesCount = 0;
    }

  private:
    LockGuarded<LockedContent> d_content;
    pdns::stat_t d_entriesCount{0};
  };

  vector<MapCombo> d_maps;
  MapCombo& getMap(const DNSName& qname)
  {
    return d_maps.at(qname.hash() % d_maps.size());
  }

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

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

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

namespace boost
{
size_t hash_value(const MemRecursorCache::OptTag& rtag);
}