-
Notifications
You must be signed in to change notification settings - Fork 968
dnsdist: implement simple packet shuffle in cache #16108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
|
||||||
|
@@ -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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
.. class:: PacketCache | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -24,6 +24,7 @@ | |||||
#include <boost/algorithm/string.hpp> | ||||||
#include <boost/format.hpp> | ||||||
|
||||||
#include "dns_random.hh" | ||||||
#include "namespaces.hh" | ||||||
#include "noinitvector.hh" | ||||||
|
||||||
|
@@ -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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The creation of the |
||||||
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(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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]); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
catch(...) { | ||||||
} | ||||||
} | ||||||
|
||||||
uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA) | ||||||
{ | ||||||
uint32_t result = std::numeric_limits<uint32_t>::max(); | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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); | ||||||
|
@@ -617,6 +618,15 @@ public: | |||||
moveOffset(toskip); | ||||||
} | ||||||
|
||||||
std::pair<uint32_t, uint32_t> skipAndReturnIndexesRData() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
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; | ||||||
|
@@ -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) | ||||||
{ | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.