}
}
+ if (d_settings.d_shuffle) {
+ dnsheader_aligned dh_aligned(response.data());
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ shuffleDNSPacket(reinterpret_cast<char*>(response.data()), response.size(), dh_aligned);
+ }
+
++d_hits;
return true;
}
bool d_deferrableInsertLock{true};
bool d_parseECS{false};
bool d_keepStaleData{false};
+ bool d_shuffle{false};
};
DNSDistPacketCache(CacheSettings settings);
.d_deferrableInsertLock = cache.deferrable_insert_lock,
.d_parseECS = cache.parse_ecs,
.d_keepStaleData = cache.keep_stale_data,
+ .d_shuffle = cache.shuffle,
};
std::unordered_set<uint16_t> ranks;
if (!cache.options_to_skip.empty()) {
{"newLMDBKVStore", true, "fname, dbName [, noLock]", "Return a new KeyValueStore object associated to the corresponding LMDB database"},
#endif
{"newNMG", true, "", "Returns a NetmaskGroup"},
- {"newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache"},
+ {"newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, shuffle=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache"},
{"newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity"},
{"newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`"},
{"newRuleAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`"},
getOptionalValue<bool>(vars, "deferrableInsertLock", settings.d_deferrableInsertLock);
getOptionalValue<bool>(vars, "dontAge", settings.d_dontAge);
getOptionalValue<bool>(vars, "keepStaleData", settings.d_keepStaleData);
+ getOptionalValue<bool>(vars, "shuffle", settings.d_shuffle);
getOptionalValue<size_t>(vars, "maxNegativeTTL", settings.d_maxNegativeTTL);
getOptionalValue<size_t>(vars, "maxTTL", settings.d_maxTTL);
getOptionalValue<size_t>(vars, "minTTL", settings.d_minTTL);
type: "bool"
default: "false"
description: "Whether to suspend the removal of expired entries from the cache when there is no backend available in at least one of the pools using this cache"
+ - name: "shuffle"
+ type: "bool"
+ default: "false"
+ description: "Whether A and AAAA records should be shuffled when serving from cache, for load-balancing. The cache might not be shuffled if the cached packet is too complex for the simple parser used for this feature."
- name: "max_negative_ttl"
type: "u32"
default: "3600"
It is enabled per-pool, but the same cache can be shared between several pools.
The first step is to define a cache with :func:`newPacketCache`, then to assign that cache to the chosen pool, the default one being represented by the empty string::
- pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false})
+ pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, shuffle=false})
getPool(""):setCache(pc)
+ The first parameter (10000) is the maximum number of entries stored in the cache, and is the only one required. All the other parameters are optional and in seconds, except the last one which is a boolean.
temporary_failure_ttl: 60
state_ttl: 60
dont_age: false
+ shuffle: false
pools:
- name: ""
packet_cache: "pc"
* ``skipOptions={10, 12}``: Extra list of EDNS option codes to skip when hashing the packet (if ``cookieHashing`` above is true, EDNS cookie option number will be removed from this list internally).
* ``maximumEntrySize=4096``: int - The maximum size, in bytes, of a DNS packet that can be inserted into the packet cache. Default is 4096 bytes, which was the fixed size before 1.9.0, and is also a hard limit for UDP responses.
* ``payloadRanks={}``: List of payload size used when hashing the packet. The list will be sorted in ascending order and searched to find a lower bound value for the payload size in the packet. If found then it will be used for packet hashing. Values less than 512 or greater than ``maximumEntrySize`` above will be discarded. This option is to enable cache entry sharing between clients using different payload sizes when needed.
+ * ``shuffle=false``: bool - Whether A and AAAA records should be shuffled when serving from cache, for load-balancing. The cache might not be shuffled if the cached packet is too complex for the simple parser used for this feature.
.. class:: PacketCache
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
+#include "dns_random.hh"
#include "namespaces.hh"
#include "noinitvector.hh"
ageDNSPacket(packet.data(), packet.length(), seconds, aligned_dh);
}
+void shuffleDNSPacket(char* packet, size_t length, const dnsheader_aligned& aligned_dh)
+{
+ if (length < sizeof(dnsheader)) {
+ return;
+ }
+ try {
+ const dnsheader* dhp = aligned_dh.get();
+ const uint16_t ancount = ntohs(dhp->ancount);
+ if (ancount == 1) {
+ // quick exit, nothing to shuffle
+ return;
+ }
+
+ DNSPacketMangler dpm(packet, length);
+
+ const uint16_t qdcount = ntohs(dhp->qdcount);
+
+ for(size_t iter = 0; iter < qdcount; ++iter) {
+ dpm.skipDomainName();
+ /* type and class */
+ dpm.skipBytes(4);
+ }
+
+ // for now shuffle only first rrset, only As and AAAAs
+ uint16_t rrset_type = 0;
+ DNSName rrset_dnsname{};
+ std::vector<std::pair<uint32_t, uint32_t>> rrdata_indexes;
+ rrdata_indexes.reserve(ancount);
+
+ for(size_t iter = 0; iter < ancount; ++iter) {
+ auto domain_start = dpm.getOffset();
+ dpm.skipDomainName();
+ const uint16_t dnstype = dpm.get16BitInt();
+ if (dnstype == QType::A || dnstype == QType::AAAA) {
+ if (rrdata_indexes.empty()) {
+ rrset_type = dnstype;
+ rrset_dnsname = DNSName(packet, length, domain_start, true);
+ } else {
+ if (dnstype != rrset_type) {
+ break;
+ }
+ if (DNSName(packet, length, domain_start, true) != rrset_dnsname) {
+ break;
+ }
+ }
+ /* class */
+ dpm.skipBytes(2);
+
+ /* ttl */
+ dpm.skipBytes(4);
+ rrdata_indexes.push_back(dpm.skipRDataAndReturnOffsets());
+ } else {
+ if (!rrdata_indexes.empty()) {
+ break;
+ }
+ /* class */
+ dpm.skipBytes(2);
+
+ /* ttl */
+ dpm.skipBytes(4);
+ dpm.skipRData();
+ }
+ }
+
+ if (rrdata_indexes.size() >= 2) {
+ using uid = std::uniform_int_distribution<std::vector<std::pair<uint32_t, uint32_t>>::size_type>;
+ uid dist;
+
+ pdns::dns_random_engine randomEngine;
+ for (auto swapped = rrdata_indexes.size() - 1; swapped > 0; --swapped) {
+ auto swapped_with = dist(randomEngine, uid::param_type(0, swapped));
+ if (swapped != swapped_with) {
+ dpm.swapInPlace(rrdata_indexes.at(swapped), rrdata_indexes.at(swapped_with));
+ }
+ }
+ }
+ }
+ catch(...) {
+ }
+}
+
uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA)
{
uint32_t result = std::numeric_limits<uint32_t>::max();
};
string simpleCompress(const string& label, const string& root="");
+void shuffleDNSPacket(char* packet, size_t length, const dnsheader_aligned& aligned_dh);
void ageDNSPacket(char* packet, size_t length, uint32_t seconds, const dnsheader_aligned&);
void ageDNSPacket(std::string& packet, uint32_t seconds, const dnsheader_aligned&);
void editDNSPacketTTL(char* packet, size_t length, const std::function<uint32_t(uint8_t, uint16_t, uint16_t, uint32_t)>& visitor);
moveOffset(toskip);
}
+ std::pair<uint32_t, uint32_t> skipRDataAndReturnOffsets()
+ {
+ auto toskip = get16BitInt();
+ uint32_t start = d_offset;
+ moveOffset(toskip);
+ uint32_t end = d_offset;
+ return std::pair<uint32_t,uint32_t>(start, end);
+ }
+
void decreaseAndSkip32BitInt(uint32_t decrease)
{
const char *p = d_packet + d_offset;
return d_offset;
}
+ void swapInPlace(std::pair<uint32_t, uint32_t> a, std::pair<uint32_t, uint32_t> b) {
+ // some basic range checks
+ if (b.first < a.first) {
+ std::swap(a, b);
+ }
+ if (a.second-a.first != b.second-b.first) {
+ throw std::out_of_range("swap: segments have different lengths");
+ }
+ if (a.second <= a.first) {
+ throw std::out_of_range("swap: ending of segment before start of segment");
+ }
+ if (a.second > b.first) {
+ throw std::out_of_range("swap: overlapping segments");
+ }
+ if (b.second > d_length) {
+ throw std::out_of_range("swap: ending of segment after end of array");
+ }
+ // don't allow to swap what we haven't read yet
+ if (b.second > d_offset) {
+ throw std::out_of_range("swap: ending of segment after current offset");
+ }
+ std::swap_ranges(d_packet+a.first, d_packet+a.second, d_packet+b.first);
+ }
+
private:
void moveOffset(uint16_t by)
{