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);
}
|