Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pdns/dnsdistdist/dnsdist-cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@ bool DNSDistPacketCache::get(DNSQuestion& dnsQuestion, uint16_t queryId, uint32_
}
}

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;
}
Expand Down
1 change: 1 addition & 0 deletions pdns/dnsdistdist/dnsdist-cache.hh
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public:
bool d_deferrableInsertLock{true};
bool d_parseECS{false};
bool d_keepStaleData{false};
bool d_shuffle{false};
};

DNSDistPacketCache(CacheSettings settings);
Expand Down
1 change: 1 addition & 0 deletions pdns/dnsdistdist/dnsdist-configuration-yaml.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,7 @@ bool loadConfigurationFromFile(const std::string& fileName, [[maybe_unused]] boo
.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;
for (const auto& option : cache.options_to_skip) {
Expand Down
2 changes: 1 addition & 1 deletion pdns/dnsdistdist/dnsdist-console-completion.cc
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ static std::vector<dnsdist::console::completion::ConsoleKeyword> s_consoleKeywor
{"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()`"},
Expand Down
1 change: 1 addition & 0 deletions pdns/dnsdistdist/dnsdist-lua-bindings-packetcache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
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);
Expand Down
4 changes: 4 additions & 0 deletions pdns/dnsdistdist/dnsdist-settings-definitions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,10 @@ packet_cache:
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 to shuffle records in the cache."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description: "Whether to shuffle records in the cache."
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"
Expand Down
3 changes: 2 additions & 1 deletion pdns/dnsdistdist/docs/guides/cache.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Caching Responses
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.
Expand Down Expand Up @@ -37,6 +37,7 @@ The equivalent ``yaml`` configuration would be:
temporary_failure_ttl: 60
state_ttl: 60
dont_age: false
shuffle: false
pools:
- name: ""
packet_cache: "pc"
Expand Down
2 changes: 2 additions & 0 deletions pdns/dnsdistdist/docs/reference/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,7 @@ See :doc:`../guides/cache` for a how to.
:param bool deferrableInsertLock: Whether the cache should give up insertion if the lock is held by another thread, or simply wait to get the lock
:param int maxNegativeTTL: Cache a NXDomain or NoData answer from the backend for at most this amount of seconds, even if the TTL of the SOA record is higher
:param bool parseECS: Whether any EDNS Client Subnet option present in the query should be extracted and stored to be able to detect hash collisions involving queries with the same qname, qtype and qclass but a different incoming ECS value. Enabling this option adds a parsing cost and only makes sense if at least one backend might send different responses based on the ECS value, so it's disabled by default
:param bool shuffle: Whether A and AAAA records should be shuffled when serving from cache, for load-balancing. The cache might not be always shuffled when the results are too complex.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This syntax has been deprecated since 1.4.0, no need to update it.


.. function:: newPacketCache(maxEntries, [options]) -> PacketCache

Expand Down Expand Up @@ -1074,6 +1075,7 @@ See :doc:`../guides/cache` for a how to.
* ``skipOptions={}``: Extra list of EDNS option codes to skip when hashing the packet (if ``cookieHashing`` above is false, EDNS cookie option number will be added to 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 always shuffled when the results are too complex.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* ``shuffle=false``: bool - Whether A and AAAA records should be shuffled when serving from cache, for load-balancing. The cache might not be always shuffled when the results are too complex.
* ``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

Expand Down
3 changes: 3 additions & 0 deletions pdns/dnsdistdist/test-dnsdistpacketcache_cc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSharded)
.d_staleTTL = 60,
.d_shardCount = 10,
.d_dontAge = false,
.d_shuffle = false,
};
DNSDistPacketCache localCache(settings);
BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
Expand Down Expand Up @@ -872,6 +873,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision)
.d_dontAge = false,
.d_deferrableInsertLock = true,
.d_parseECS = true,
.d_shuffle = false,
};
DNSDistPacketCache localCache(settings);
BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
Expand Down Expand Up @@ -1012,6 +1014,7 @@ BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision)
.d_dontAge = false,
.d_deferrableInsertLock = true,
.d_parseECS = true,
.d_shuffle = false,
};
DNSDistPacketCache localCache(settings);
BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
Expand Down
81 changes: 81 additions & 0 deletions pdns/dnsparser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>

#include "dns_random.hh"
#include "namespaces.hh"
#include "noinitvector.hh"

Expand Down Expand Up @@ -985,6 +986,86 @@ void ageDNSPacket(std::string& packet, uint32_t seconds, const dnsheader_aligned
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 {
DNSPacketMangler dpm(packet, length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The creation of the DNSPacketMangler object can be moved after the ancount == 1 check.

const dnsheader* dhp = aligned_dh.get();
const uint16_t ancount = ntohs(dhp->ancount);
if (ancount == 1) {
// quick exit, nothing to shuffle
return;
}

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 = DNSName();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DNSName rrset_dnsname = DNSName();
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.skipAndReturnIndexesRData());
} 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[swapped], rrdata_indexes[swapped_with]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dpm.swapInPlace(rrdata_indexes[swapped], rrdata_indexes[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();
Expand Down
34 changes: 34 additions & 0 deletions pdns/dnsparser.hh
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ private:
};

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);
Expand Down Expand Up @@ -617,6 +618,15 @@ public:
moveOffset(toskip);
}

std::pair<uint32_t, uint32_t> skipAndReturnIndexesRData()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::pair<uint32_t, uint32_t> skipAndReturnIndexesRData()
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;
Expand Down Expand Up @@ -647,6 +657,30 @@ public:
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)
{
Expand Down