throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packet.size()) + ")");
}
- if (d_settings.d_skipHashingAR) {
- /* skip Additional Resource Records */
- result = burtle(&packet.at(2), sizeof(dnsheader) - 4, result);
- }
- else {
- result = burtle(&packet.at(2), sizeof(dnsheader) - 2, result);
- }
+ result = burtle(&packet.at(2), sizeof(dnsheader) - 2, result);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
result = burtleCI(reinterpret_cast<const unsigned char*>(qname.c_str()), qname.length(), result);
if (packet.size() < sizeof(dnsheader) + qnameWireLength) {
throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packet.size()) + " < " + std::to_string(sizeof(dnsheader) + qnameWireLength) + ")");
}
if (packet.size() > ((sizeof(dnsheader) + qnameWireLength))) {
- if (d_settings.d_skipHashingAR) {
- /* only need to include the 2+2 bytes for qtype and qclass */
- result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), 4, result);
- }
- else if (!d_settings.d_optionsToSkip.empty()) {
+ if (!d_settings.d_optionsToSkip.empty()) {
/* skip EDNS options if any */
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
- result = PacketCache::hashAfterQname(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_settings.d_optionsToSkip);
+ result = PacketCache::hashAfterQname(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_settings.d_optionsToSkip, d_settings.d_payloadRanks);
}
else {
result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), packet.size() - (sizeof(dnsheader) + qnameWireLength), result);
struct CacheSettings
{
std::unordered_set<uint16_t> d_optionsToSkip{EDNSOptionCode::COOKIE};
+ std::vector<uint16_t> d_payloadRanks{};
size_t d_maxEntries{0};
size_t d_maximumEntrySize{4096};
uint32_t d_maxTTL{86400};
bool d_deferrableInsertLock{true};
bool d_parseECS{false};
bool d_keepStaleData{false};
- bool d_skipHashingAR{false};
};
DNSDistPacketCache(CacheSettings settings);
.d_parseECS = cache.parse_ecs,
.d_keepStaleData = cache.keep_stale_data,
};
+ std::unordered_set<uint16_t> ranks;
for (const auto& option : cache.options_to_skip) {
settings.d_optionsToSkip.insert(pdns::checked_stoi<uint16_t>(std::string(option)));
}
if (cache.maximum_entry_size >= sizeof(dnsheader)) {
settings.d_maximumEntrySize = cache.maximum_entry_size;
}
- if (!cache.parse_ecs) {
- settings.d_skipHashingAR = cache.skip_hashing_ar;
+ for (const auto& rankstr : cache.payload_ranks) {
+ auto rank = pdns::checked_stoi<uint16_t>(std::string(rankstr));
+ if (rank < 512 || rank > settings.d_maximumEntrySize) {
+ continue;
+ }
+ ranks.insert(rank);
+ }
+ if (!ranks.empty()) {
+ settings.d_payloadRanks.assign(ranks.begin(), ranks.end());
+ std::sort(settings.d_payloadRanks.begin(), settings.d_payloadRanks.end());
}
auto packetCacheObj = std::make_shared<DNSDistPacketCache>(settings);
.d_shardCount = 20,
};
bool cookieHashing = false;
- bool skipHashingAR = false;
LuaArray<uint16_t> skipOptions;
+ LuaArray<uint16_t> payloadRanks;
+ std::unordered_set<uint16_t> ranks;
size_t maximumEntrySize{4096};
getOptionalValue<bool>(vars, "deferrableInsertLock", settings.d_deferrableInsertLock);
getOptionalValue<size_t>(vars, "truncatedTTL", settings.d_truncatedTTL);
getOptionalValue<bool>(vars, "cookieHashing", cookieHashing);
getOptionalValue<size_t>(vars, "maximumEntrySize", maximumEntrySize);
- getOptionalValue<bool>(vars, "skipHashingAR", skipHashingAR);
if (maximumEntrySize >= sizeof(dnsheader)) {
settings.d_maximumEntrySize = maximumEntrySize;
}
}
+ if (getOptionalValue<decltype(payloadRanks)>(vars, "payloadRanks", payloadRanks) > 0) {
+ for (const auto& rank : payloadRanks) {
+ if (rank.second < 512 || rank.second > settings.d_maximumEntrySize) {
+ continue;
+ }
+ ranks.insert(rank.second);
+ }
+ if (!ranks.empty()) {
+ settings.d_payloadRanks.assign(ranks.begin(), ranks.end());
+ std::sort(settings.d_payloadRanks.begin(), settings.d_payloadRanks.end());
+ }
+ }
+
if (cookieHashing) {
settings.d_optionsToSkip.erase(EDNSOptionCode::COOKIE);
}
settings.d_maxEntries = 1;
settings.d_shardCount = 1;
}
- if (!settings.d_parseECS) {
- settings.d_skipHashingAR = skipHashingAR;
- }
return std::make_shared<DNSDistPacketCache>(settings);
});
type: "Vec<String>"
default: ""
description: "Extra list of EDNS option codes to skip when hashing the packet (if ``cookie_hashing`` above is false, EDNS cookie option number will be added to this list internally)"
- - name: "skip_hashing_ar"
- type: "bool"
- default: "false"
- description: "If true, the whole Additional Resource Record section (including all EDNS options) will be skipped when hashing the packet. This will allow cache entry sharing between multiple clients who will use different EDNS0 payload size in its request for the same query name/type/class. However, if ``parse_ecs`` abvoe is true, this parameter is ignored since the answer to the same query name/type/class might be different if ECS option is used"
+ - name: "payload_ranks"
+ type: "Vec<String>"
+ default: ""
+ description: "List of payload size used when hashing the packet. The list will be sorted in ascend 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 ``maximum_entry_size`` above will be discarded. This option is to enable cache entry sharing between clients using different payload sizes when needed"
proxy_protocol:
description: "Proxy Protocol-related settings"
``truncatedTTL`` parameter added.
.. versionchanged:: 2.0.0
- ``skipHashingAR`` parameter added.
+ ``payloadRanks`` parameter added.
Creates a new :class:`PacketCache` with the settings specified.
* ``cookieHashing=false``: bool - If true, EDNS Cookie values will be hashed, resulting in separate entries for different cookies in the packet cache. This is required if the backend is sending answers with EDNS Cookies, otherwise a client might receive an answer with the wrong cookie.
* ``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.
- * ``skipHashingAR=false``: bool - If true, the whole Additional Resource Record section (including all EDNS options) will be skipped when hashing the packet. This will allow cache entry sharing between multiple clients who will use different EDNS0 payload size in its request for the same query name/type/class. However, if ``parseECS`` abvoe is true, this parameter is ignored since the answer to the same query name/type/class might be different if ECS option is used.
+ * ``payloadRanks={}``: List of payload size used when hashing the packet. The list will be sorted in ascend 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.
.. class:: PacketCache
- EDNS Cookie options, if any ;
- Any given option code present in optionsToSkip
*/
- static uint32_t hashAfterQname(const std::string_view& packet, uint32_t currentHash, size_t pos, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
+ static uint32_t hashAfterQname(const std::string_view& packet, uint32_t currentHash, size_t pos, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE}, const std::vector<uint16_t>& payloadRanks = {})
{
const size_t packetSize = packet.size();
assert(packetSize >= sizeof(dnsheader));
*/
const dnsheader_aligned dnsheaderdata(packet.data());
const struct dnsheader *dh = dnsheaderdata.get();
- if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= packetSize) {
+ if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) > packetSize) {
if (packetSize > pos) {
currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
}
return currentHash;
}
- currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 15, currentHash);
+ if (payloadRanks.empty()) {
+ currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 15, currentHash);
+ }
+ else {
+ std::vector<unsigned char> optrr(packet.begin() + pos, packet.begin() + pos + 15);
+ uint16_t bufSize = optrr.at(7) * 256 + optrr.at(8);
+ auto it = std::upper_bound(payloadRanks.begin(), payloadRanks.end(), bufSize);
+ if (it != payloadRanks.begin()) {
+ it--;
+ optrr[7] = (*it) >> 8;
+ optrr[8] = (*it) & 0xff;
+ }
+ currentHash = burtle(reinterpret_cast<const unsigned char*>(&optrr.at(0)), 15, currentHash);
+ }
+ if ( (pos + 15) == packetSize ) {
+ return currentHash;
+ }
+
/* skip the qtype (2), qclass (2) */
/* root label (1), type (2), class (2) and ttl (4) */
/* already hashed above */
import clientsubnetoption
import cookiesoption
import requests
-import random
-import string
from dnsdisttests import DNSDistTest, pickAvailablePort
class TestCaching(DNSDistTest):
"""
numberOfQueries = 10
name = 'large-answer.cache.tests.powerdns.com.'
- query = dns.message.make_query(name, 'TXT', 'IN')
+ query = dns.message.make_query(name, 'TXT', 'IN', payload=4096)
response = dns.message.make_response(query)
# we prepare a large answer
content = ""
content = content + ', '
content = content + (str(i)*50)
# pad up to 4096
- content = content + 'A'*42
+ content = content + 'A'*31
rrset = dns.rrset.from_text(name,
3600,
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertEqual(receivedResponse, response)
-class TestCachingSkipAR(DNSDistTest):
+class TestCachingPayloadRanks(DNSDistTest):
_verboseMode = True
_testServerPort = pickAvailablePort()
_config_template = """
webserver("127.0.0.1:%s")
setWebserverConfig({apiKey="%s"})
- pc = newPacketCache(100, {maxTTL=86400, minTTL=1, skipHashingAR=true})
+ pc = newPacketCache(100, {maxTTL=86400, minTTL=1, payloadRanks={768, 512, 4096, 1280, 1024, 2048}})
getPool(""):setCache(pc)
newServer{address="127.0.0.1:%d"}
"""
pool = pools[poolID]
return int(pool[metricName])
- def testCacheSkipAR(self):
+ def testCachePayloadRanks(self):
"""
- Cache: Testing ``skipHashingAR`` parameter for caching
+ Cache: Testing ``payloadRanks`` parameter for caching
- dnsdist is configured to cache entries with ``skipHashingAR`` turned on,
- cache entry will be used even with/without AR section or different payload
- size is used.
"""
# testing with and without EDNS0 payload size
name1 = 'cached.cache.tests.powerdns.com.'
- query1 = dns.message.make_query(name1, 'AAAA', 'IN')
+ query1 = dns.message.make_query(name1, 'AAAA', 'IN', payload=512)
query1_1 = dns.message.make_query(name1, 'AAAA', 'IN', payload=600)
response1 = dns.message.make_response(query1)
rrset1 = dns.rrset.from_text(name1,
self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 1)
self.assertEqual(receivedResponse, response1)
- # query1_1 shall also hit cache even it inlcudes Opt RR for payload size
+ # query1_1 shall also hit cache since 600 round down to 512
(_, receivedResponse) = self.sendUDPQuery(query1_1, response=None, useQueue=False)
self.assertTrue(receivedResponse)
self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 2)
# testing for large sized cache entry
name2 = 'bigcached.cache.tests.powerdns.com.'
- cookieBytes = ''.join(random.choices(string.hexdigits, k=8)).lower().encode()
- myCookie = dns.edns.CookieOption(client=cookieBytes, server=b'')
- query2 = dns.message.make_query(name2, 'AAAA', 'IN', payload=4096)
- query2_1 = dns.message.make_query(name2, 'AAAA', 'IN', payload=2000)
- query2_2 = dns.message.make_query(name2, 'AAAA', 'IN', payload=1500, options=[myCookie])
- query2_3 = dns.message.make_query(name2, 'AAAA', 'IN', payload=800)
+ query2 = dns.message.make_query(name2, 'AAAA', 'IN', payload=1279)
+ query2_1 = dns.message.make_query(name2, 'AAAA', 'IN', payload=1200)
+ query2_2 = dns.message.make_query(name2, 'AAAA', 'IN', payload=1024)
response2 = dns.message.make_response(query2)
v6addr_list = []
self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 3)
self.assertEqual(receivedResponse, response2)
- # query2_1 shall also hit cache even it has a different payload size
+ # query2_1 shall hit cache
(_, receivedResponse) = self.sendUDPQuery(query2_1, response=None, useQueue=False)
self.assertTrue(receivedResponse)
self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 4)
self.assertEqual(len(receivedResponse.answer), 1)
self.assertEqual(receivedResponse.answer[0], rrset2)
- # query2_2 shall also hit cache even it has a different payload size and EDNS COOKIE
+ # query2_2 shall hit cache but truncated since payload size is not enough
(_, receivedResponse) = self.sendUDPQuery(query2_2, response=None, useQueue=False)
self.assertTrue(receivedResponse)
self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 5)
- self.assertEqual(len(receivedResponse.answer), 1)
- self.assertEqual(receivedResponse.answer[0], rrset2)
-
- # query2_3 shall also hit cache but truncated since payload size is not enough
- (_, receivedResponse) = self.sendUDPQuery(query2_3, response=None, useQueue=False)
- self.assertTrue(receivedResponse)
- self.assertEqual(self.getPoolMetric(0, 'cacheHits'), 6)
self.assertEqual(len(receivedResponse.answer), 0)
self.assertEqual(receivedResponse.flags & dns.flags.TC, dns.flags.TC)