#include "dnsparser.hh"
#include "dnsdist-cache.hh"
#include "dnsdist-ecs.hh"
-#include "ednsoptions.hh"
#include "ednssubnet.hh"
#include "packetcache.hh"
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_cookieHashing) {
- /* skip EDNS Cookie options if any */
- result = PacketCache::hashAfterQname(pdns_string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, false);
+ if (!d_optionsToSkip.empty()) {
+ /* skip EDNS options if any */
+ result = PacketCache::hashAfterQname(pdns_string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_optionsToSkip);
}
else {
result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), packet.size() - (sizeof(dnsheader) + qnameWireLength), result);
return count;
}
+
+bool DNSDistPacketCache::isCookieHashingEnabled() const
+{
+ return d_optionsToSkip.count(EDNSOptionCode::COOKIE) == 0;
+}
+
+void DNSDistPacketCache::setCookieHashing(bool hashing)
+{
+ if (hashing) {
+ d_optionsToSkip.erase(EDNSOptionCode::COOKIE);
+ } else {
+ d_optionsToSkip.insert(EDNSOptionCode::COOKIE);
+ }
+}
+
+void DNSDistPacketCache::skipOptions(const std::unordered_set<uint16_t>& optionsToSkip)
+{
+ bool cookieHasingEnabled = isCookieHashingEnabled();
+ d_optionsToSkip = optionsToSkip;
+ setCookieHashing(cookieHasingEnabled);
+}
#include "lock.hh"
#include "noinitvector.hh"
#include "stat_t.hh"
+#include "ednsoptions.hh"
struct DNSQuestion;
uint64_t getTTLTooShorts() const { return d_ttlTooShorts; }
uint64_t getEntriesCount();
uint64_t dump(int fd);
+ bool isCookieHashingEnabled() const;
+ void setCookieHashing(bool hashing);
+ void skipOptions(const std::unordered_set<uint16_t>& optionsToSkip);
bool isECSParsingEnabled() const { return d_parseECS; }
- bool isCookieHashingEnabled() const { return d_cookieHashing; }
bool keepStaleData() const
{
d_keepStaleData = keep;
}
- void setCookieHashing(bool hashing)
- {
- d_cookieHashing = hashing;
- }
void setECSParsingEnabled(bool enabled)
{
bool d_deferrableInsertLock;
bool d_parseECS;
bool d_keepStaleData{false};
- bool d_cookieHashing{false};
+ std::unordered_set<uint16_t> d_optionsToSkip{EDNSOptionCode::COOKIE};
};
#include "dnsdist.hh"
#include "dnsdist-lua.hh"
+#include <boost/lexical_cast.hpp>
+
void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
{
/* PacketCache */
- luaCtx.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional<std::unordered_map<std::string, boost::variant<bool, size_t>>> vars) {
+ luaCtx.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional<std::unordered_map<std::string, boost::variant<bool, size_t, std::vector<std::pair<int, uint16_t>>>>> vars) {
bool keepStaleData = false;
size_t maxTTL = 86400;
bool deferrableInsertLock = true;
bool ecsParsing = false;
bool cookieHashing = false;
+ std::unordered_set<uint16_t> optionsToSkip{};
if (vars) {
if (vars->count("cookieHashing")) {
cookieHashing = boost::get<bool>((*vars)["cookieHashing"]);
}
+ if (vars->count("skipOptions")) {
+ for (auto option: boost::get<std::vector<std::pair<int, uint16_t>>>(vars->at("skipOptions"))) {
+ optionsToSkip.insert(option.second);
+ }
+ }
}
if (maxEntries < numberOfShards) {
res->setKeepStaleData(keepStaleData);
res->setCookieHashing(cookieHashing);
+ res->skipOptions(optionsToSkip);
return res;
});
``cookieHashing`` parameter added.
``numberOfShards`` now defaults to 20.
+ .. versionchanged:: 1.7.0
+ ``skipOptions`` parameter added.
+
Creates a new :class:`PacketCache` with the settings specified.
:param int maxEntries: The maximum number of entries in this cache
* ``staleTTL=60``: int - When the backend servers are not reachable, and global configuration ``setStaleCacheEntriesTTL`` is set appropriately, TTL that will be used when a stale cache entry is returned.
* ``temporaryFailureTTL=60``: int - On a SERVFAIL or REFUSED from the backend, cache for this amount of seconds..
* ``cookieHashing=false``: bool - Whether 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 (see ``cookieHashing`` above).
.. class:: PacketCache
/* hash the packet from the provided position, which should point right after tje qname. This skips:
- the query ID ;
- EDNS Cookie options, if any ;
- - EDNS Client Subnet options, if any and skipECS is true.
+ - Any given option code present in optionsToSkip
*/
- static uint32_t hashAfterQname(const pdns_string_view& packet, uint32_t currentHash, size_t pos, bool skipECS)
+ static uint32_t hashAfterQname(const pdns_string_view& packet, uint32_t currentHash, size_t pos, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
{
const size_t packetSize = packet.size();
assert(packetSize >= sizeof(dnsheader));
return currentHash;
}
- bool skip = false;
- if (optionCode == EDNSOptionCode::COOKIE) {
- skip = true;
- }
- else if (optionCode == EDNSOptionCode::ECS) {
- if (skipECS) {
- skip = true;
- }
- }
-
- if (!skip) {
+ if (optionsToSkip.count(optionCode) == 0) {
/* hash the option code, length and content */
currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4 + optionLen, currentHash);
}
else {
- /* hash the option code and length */
+ /* skip option: hash only its code and length */
currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4, currentHash);
}
/* hash the packet from the beginning, including the qname. This skips:
- the query ID ;
- EDNS Cookie options, if any ;
- - EDNS Client Subnet options, if any and skipECS is true.
+ - Any given option code present in optionsToSkip
*/
- static uint32_t canHashPacket(const std::string& packet, bool skipECS)
+ static uint32_t canHashPacket(const std::string& packet, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
{
size_t pos = 0;
uint32_t currentHash = hashHeaderAndQName(packet, pos);
return currentHash;
}
- return hashAfterQname(packet, currentHash, pos, skipECS);
+ return hashAfterQname(packet, currentHash, pos, optionsToSkip);
}
static bool queryHeaderMatches(const std::string& cachedQuery, const std::string& query)
return getResponsePacket(tag, queryPacket, qname, qtype, qclass, now, responsePacket, age, &valState, qhash, nullptr, false);
}
+static const std::unordered_set<uint16_t> s_skipOptions = {EDNSOptionCode::ECS, EDNSOptionCode::COOKIE};
+
bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now,
std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp)
{
- *qhash = canHashPacket(queryPacket, true);
+ *qhash = canHashPacket(queryPacket, s_skipOptions);
const auto& idx = d_packetCache.get<HashTag>();
auto range = idx.equal_range(tie(tag, *qhash, tcp));
bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now,
std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData *pbdata, bool tcp)
{
- *qhash = canHashPacket(queryPacket, true);
+ *qhash = canHashPacket(queryPacket, s_skipOptions);
const auto& idx = d_packetCache.get<HashTag>();
auto range = idx.equal_range(tie(tag, *qhash, tcp));
pw1.getHeader()->qr = false;
pw1.getHeader()->id = 0x42;
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, false);
+ auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.getHeader()->qr = false;
pw2.getHeader()->id = 0x84;
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, false);
+ auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
BOOST_CHECK_EQUAL(hash1, hash2);
BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, false);
+ auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, false);
+ auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same but we should _not_ match */
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
pwFQ.addOpt(512, 0, 0, ednsOptions);
pwFQ.commit();
- auto secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), false);
+ auto secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), optionsToSkip);
auto pair = colMap.insert(std::make_pair(secondKey, opt.source));
total++;
if (!pair.second) {
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, false);
+ auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, false);
+ auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same but we should _not_ match */
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, false);
+ auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, false);
+ auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same but we should _not_ match */
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
pwFQ.addOpt(512, 0, 32768, ednsOptions);
pwFQ.commit();
- auto secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), false);
+ auto secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), optionsToSkip);
colMap.insert(std::make_pair(secondKey, opt.source));
secondQuery.clear();
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
pwSQ.addOpt(512, 0, 0, ednsOptions);
pwSQ.commit();
- secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), false);
+ secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), optionsToSkip);
total++;
if (colMap.count(secondKey)) {
uint16_t qtype = QType::AAAA;
EDNSSubnetOpts opt;
DNSPacketWriter::optvect_t ednsOptions;
+ static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
{
vector<uint8_t> packet;
*(ptr + 1) = 255;
/* truncate the end of the OPT header to try to trigger an out of bounds read */
spacket1.resize(spacket1.size() - 6);
- BOOST_CHECK_NO_THROW(PacketCache::canHashPacket(spacket1, true));
+ BOOST_CHECK_NO_THROW(PacketCache::canHashPacket(spacket1, optionsToSkip));
}
}
pw1.getHeader()->qr = false;
pw1.getHeader()->id = 0x42;
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, true);
+ auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.getHeader()->qr = false;
pw2.getHeader()->id = 0x84;
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, true);
+ auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
BOOST_CHECK_EQUAL(hash1, hash2);
BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, true);
+ auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, true);
+ auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same and we don't hash the ECS so we should match */
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, true);
+ auto hash1 = PacketCache::canHashPacket(spacket1, optionsToSkip);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, true);
+ auto hash2 = PacketCache::canHashPacket(spacket2, optionsToSkip);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same but we should _not_ match, even though we skip the ECS part, because the cookies are different */