Skip to content

Commit 73f93e2

Browse files
committed
dnsdist: implement simple packet shuffle in cache
The shuffle is implementing by directly swapping pieces of RData memory in a single RRSet. Signed-off-by: Karel Bilek <kb@karelbilek.com>
1 parent b88d6ba commit 73f93e2

11 files changed

+136
-2
lines changed

pdns/dnsdistdist/dnsdist-cache.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,12 @@ bool DNSDistPacketCache::get(DNSQuestion& dnsQuestion, uint16_t queryId, uint32_
329329
}
330330
}
331331

332+
if (d_settings.d_shuffle) {
333+
dnsheader_aligned dh_aligned(response.data());
334+
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
335+
shuffleDNSPacket(reinterpret_cast<char*>(response.data()), response.size(), dh_aligned);
336+
}
337+
332338
++d_hits;
333339
return true;
334340
}

pdns/dnsdistdist/dnsdist-cache.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public:
5252
bool d_deferrableInsertLock{true};
5353
bool d_parseECS{false};
5454
bool d_keepStaleData{false};
55+
bool d_shuffle{false};
5556
};
5657

5758
DNSDistPacketCache(CacheSettings settings);

pdns/dnsdistdist/dnsdist-configuration-yaml.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,7 @@ bool loadConfigurationFromFile(const std::string& fileName, [[maybe_unused]] boo
11561156
.d_deferrableInsertLock = cache.deferrable_insert_lock,
11571157
.d_parseECS = cache.parse_ecs,
11581158
.d_keepStaleData = cache.keep_stale_data,
1159+
.d_shuffle = cache.shuffle,
11591160
};
11601161
std::unordered_set<uint16_t> ranks;
11611162
for (const auto& option : cache.options_to_skip) {

pdns/dnsdistdist/dnsdist-console-completion.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ static std::vector<dnsdist::console::completion::ConsoleKeyword> s_consoleKeywor
186186
{"newLMDBKVStore", true, "fname, dbName [, noLock]", "Return a new KeyValueStore object associated to the corresponding LMDB database"},
187187
#endif
188188
{"newNMG", true, "", "Returns a NetmaskGroup"},
189-
{"newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache"},
189+
{"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"},
190190
{"newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity"},
191191
{"newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`"},
192192
{"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()`"},

pdns/dnsdistdist/dnsdist-lua-bindings-packetcache.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
4646
getOptionalValue<bool>(vars, "deferrableInsertLock", settings.d_deferrableInsertLock);
4747
getOptionalValue<bool>(vars, "dontAge", settings.d_dontAge);
4848
getOptionalValue<bool>(vars, "keepStaleData", settings.d_keepStaleData);
49+
getOptionalValue<bool>(vars, "shuffle", settings.d_shuffle);
4950
getOptionalValue<size_t>(vars, "maxNegativeTTL", settings.d_maxNegativeTTL);
5051
getOptionalValue<size_t>(vars, "maxTTL", settings.d_maxTTL);
5152
getOptionalValue<size_t>(vars, "minTTL", settings.d_minTTL);

pdns/dnsdistdist/dnsdist-settings-definitions.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,10 @@ packet_cache:
19101910
type: "bool"
19111911
default: "false"
19121912
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"
1913+
- name: "shuffle"
1914+
type: "bool"
1915+
default: "false"
1916+
description: "Whether to shuffle records in the cache."
19131917
- name: "max_negative_ttl"
19141918
type: "u32"
19151919
default: "3600"

pdns/dnsdistdist/docs/guides/cache.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Caching Responses
55
It is enabled per-pool, but the same cache can be shared between several pools.
66
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::
77

8-
pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false})
8+
pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, shuffle=false})
99
getPool(""):setCache(pc)
1010

1111
+ 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.
@@ -37,6 +37,7 @@ The equivalent ``yaml`` configuration would be:
3737
temporary_failure_ttl: 60
3838
state_ttl: 60
3939
dont_age: false
40+
shuffle: false
4041
pools:
4142
- name: ""
4243
packet_cache: "pc"

pdns/dnsdistdist/docs/reference/config.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,7 @@ See :doc:`../guides/cache` for a how to.
10321032
: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
10331033
: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
10341034
: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
1035+
: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.
10351036

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

@@ -1074,6 +1075,7 @@ See :doc:`../guides/cache` for a how to.
10741075
* ``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).
10751076
* ``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.
10761077
* ``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.
1078+
* ``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.
10771079

10781080
.. class:: PacketCache
10791081

pdns/dnsdistdist/test-dnsdistpacketcache_cc.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSharded)
144144
.d_staleTTL = 60,
145145
.d_shardCount = 10,
146146
.d_dontAge = false,
147+
.d_shuffle = false,
147148
};
148149
DNSDistPacketCache localCache(settings);
149150
BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
@@ -872,6 +873,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision)
872873
.d_dontAge = false,
873874
.d_deferrableInsertLock = true,
874875
.d_parseECS = true,
876+
.d_shuffle = false,
875877
};
876878
DNSDistPacketCache localCache(settings);
877879
BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
@@ -1012,6 +1014,7 @@ BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision)
10121014
.d_dontAge = false,
10131015
.d_deferrableInsertLock = true,
10141016
.d_parseECS = true,
1017+
.d_shuffle = false,
10151018
};
10161019
DNSDistPacketCache localCache(settings);
10171020
BOOST_CHECK_EQUAL(localCache.getSize(), 0U);

pdns/dnsparser.cc

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <boost/algorithm/string.hpp>
2525
#include <boost/format.hpp>
2626

27+
#include "dns_random.hh"
2728
#include "namespaces.hh"
2829
#include "noinitvector.hh"
2930

@@ -985,6 +986,86 @@ void ageDNSPacket(std::string& packet, uint32_t seconds, const dnsheader_aligned
985986
ageDNSPacket(packet.data(), packet.length(), seconds, aligned_dh);
986987
}
987988

989+
void shuffleDNSPacket(char* packet, size_t length, const dnsheader_aligned& aligned_dh)
990+
{
991+
if (length < sizeof(dnsheader)) {
992+
return;
993+
}
994+
try {
995+
DNSPacketMangler dpm(packet, length);
996+
const dnsheader* dhp = aligned_dh.get();
997+
const uint16_t ancount = ntohs(dhp->ancount);
998+
if (ancount == 1) {
999+
// quick exit, nothing to shuffle
1000+
return;
1001+
}
1002+
1003+
const uint16_t qdcount = ntohs(dhp->qdcount);
1004+
1005+
for(size_t iter = 0; iter < qdcount; ++iter) {
1006+
dpm.skipDomainName();
1007+
/* type and class */
1008+
dpm.skipBytes(4);
1009+
}
1010+
1011+
// for now shuffle only first rrset, only As and AAAAs
1012+
uint16_t rrset_type = 0;
1013+
DNSName rrset_dnsname = DNSName();
1014+
std::vector<std::pair<uint32_t, uint32_t>> rrdata_indexes;
1015+
rrdata_indexes.reserve(ancount);
1016+
1017+
for(size_t iter = 0; iter < ancount; ++iter) {
1018+
auto domain_start = dpm.getOffset();
1019+
dpm.skipDomainName();
1020+
const uint16_t dnstype = dpm.get16BitInt();
1021+
if (dnstype == QType::A || dnstype == QType::AAAA) {
1022+
if (rrdata_indexes.size() == 0) {
1023+
rrset_type = dnstype;
1024+
rrset_dnsname = DNSName(packet, length, domain_start, true);
1025+
} else {
1026+
if (dnstype != rrset_type) {
1027+
break;
1028+
}
1029+
if (DNSName(packet, length, domain_start, true) != rrset_dnsname) {
1030+
break;
1031+
}
1032+
}
1033+
/* class */
1034+
dpm.skipBytes(2);
1035+
1036+
/* ttl */
1037+
dpm.skipBytes(4);
1038+
rrdata_indexes.push_back(dpm.skipAndReturnIndexesRData());
1039+
} else {
1040+
if (rrdata_indexes.size() != 0) {
1041+
break;
1042+
}
1043+
/* class */
1044+
dpm.skipBytes(2);
1045+
1046+
/* ttl */
1047+
dpm.skipBytes(4);
1048+
dpm.skipRData();
1049+
}
1050+
}
1051+
1052+
if (rrdata_indexes.size() >= 2) {
1053+
using uid = std::uniform_int_distribution<std::vector<std::pair<uint32_t, uint32_t>>::size_type>;
1054+
uid dist;
1055+
1056+
pdns::dns_random_engine randomEngine;
1057+
for (auto swapped = rrdata_indexes.size() - 1; swapped > 0; --swapped) {
1058+
auto swapped_with = dist(randomEngine, uid::param_type(0, swapped));
1059+
if (swapped != swapped_with) {
1060+
dpm.swapInPlace(rrdata_indexes[swapped], rrdata_indexes[swapped_with]);
1061+
}
1062+
}
1063+
}
1064+
}
1065+
catch(...) {
1066+
}
1067+
}
1068+
9881069
uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA)
9891070
{
9901071
uint32_t result = std::numeric_limits<uint32_t>::max();

0 commit comments

Comments
 (0)