unix_utility.cc \
utility.hh \
version.cc version.hh \
+ views.hh \
webserver.cc webserver.hh \
ws-api.cc ws-api.hh \
ws-auth.cc ws-auth.hh \
cleanupIfNeeded();
- uint32_t hash = canHashPacket(p.getString());
+ uint32_t hash = canHashPacket(p.getString(), /* don't skip ECS */ false);
p.setHash(hash);
string value;
bool AuthPacketCache::entryMatches(cmap_t::index<HashTag>::type::iterator& iter, const std::string& query, const DNSName& qname, uint16_t qtype, bool tcp)
{
- return iter->tcp == tcp && iter->qtype == qtype && iter->qname == qname && queryMatches(iter->query, query, qname);
+ static const std::unordered_set<uint16_t> skippedEDNSTypes{ EDNSOptionCode::COOKIE };
+ return iter->tcp == tcp && iter->qtype == qtype && iter->qname == qname && queryMatches(iter->query, query, qname, skippedEDNSTypes);
}
void AuthPacketCache::insert(DNSPacket& q, DNSPacket& r, unsigned int maxTTL)
#include "dnsdist-ecs.hh"
#include "ednsoptions.hh"
#include "ednssubnet.hh"
+#include "packetcache.hh"
DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t maxNegativeTTL, uint32_t staleTTL, bool dontAge, uint32_t shards, bool deferrableInsertLock, bool parseECS): d_maxEntries(maxEntries), d_shardCount(shards), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_maxNegativeTTL(maxNegativeTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge), d_deferrableInsertLock(deferrableInsertLock), d_parseECS(parseECS)
{
const auto& dnsQName = dq.qname->getStorage();
uint32_t key = getKey(dnsQName, consumed, reinterpret_cast<const unsigned char*>(dq.dh), dq.len, dq.tcp);
- if (keyOut)
+ if (keyOut) {
*keyOut = key;
+ }
if (d_parseECS) {
getClientSubnet(reinterpret_cast<const char*>(dq.dh), consumed, dq.len, subnet);
{
uint32_t result = 0;
/* skip the query ID */
- if (packetLen < sizeof(dnsheader))
- throw std::range_error("Computing packet cache key for an invalid packet size");
+ if (packetLen < sizeof(dnsheader)) {
+ throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packetLen) +")");
+ }
+
result = burtle(packet + 2, sizeof(dnsheader) - 2, result);
result = burtleCI((const unsigned char*) qname.c_str(), qname.length(), result);
if (packetLen < sizeof(dnsheader) + consumed) {
- throw std::range_error("Computing packet cache key for an invalid packet");
+ throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packetLen) + " < " + std::to_string(sizeof(dnsheader) + consumed) + ")");
}
if (packetLen > ((sizeof(dnsheader) + consumed))) {
- result = burtle(packet + sizeof(dnsheader) + consumed, packetLen - (sizeof(dnsheader) + consumed), result);
+ if (!d_cookieHashing) {
+ /* skip EDNS Cookie options if any */
+ result = PacketCache::hashAfterQname(string_view(reinterpret_cast<const char*>(packet), packetLen), result, sizeof(dnsheader) + consumed, false);
+ }
+ else {
+ result = burtle(packet + sizeof(dnsheader) + consumed, packetLen - (sizeof(dnsheader) + consumed), result);
+ }
}
result = burtle((const unsigned char*) &tcp, sizeof(tcp), result);
return result;
uint64_t dump(int fd);
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)
+ {
+ d_parseECS = enabled;
+ }
+
+ uint32_t getKey(const DNSName::string_t& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp);
+
static uint32_t getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA);
- static uint32_t getKey(const DNSName::string_t& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp);
static bool getClientSubnet(const char* packet, unsigned int consumed, uint16_t len, boost::optional<Netmask>& subnet);
private:
bool d_deferrableInsertLock;
bool d_parseECS;
bool d_keepStaleData{false};
+ bool d_cookieHashing{false};
};
misc.cc misc.hh \
mplexer.hh \
namespaces.hh \
+ packetcache.hh \
pdnsexception.hh \
protobuf.cc protobuf.hh \
proxy-protocol.cc proxy-protocol.hh \
bool dontAge = false;
bool deferrableInsertLock = true;
bool ecsParsing = false;
+ bool cookieHashing = false;
if (vars) {
if (vars->count("temporaryFailureTTL")) {
tempFailTTL = boost::get<size_t>((*vars)["temporaryFailureTTL"]);
}
+
+ if (vars->count("cookieHashing")) {
+ cookieHashing = boost::get<bool>((*vars)["cookieHashing"]);
+ }
}
auto res = std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL, minTTL, tempFailTTL, maxNegativeTTL, staleTTL, dontAge, numberOfShards, deferrableInsertLock, ecsParsing);
res->setKeepStaleData(keepStaleData);
+ res->setCookieHashing(cookieHashing);
return res;
});
.. versionadded:: 1.4.0
+ .. versionchanged:: 1.6.0
+ ``cookieHashing`` parameter added.
+
Creates a new :class:`PacketCache` with the settings specified.
:param int maxEntries: The maximum number of entries in this cache
* ``parseECS=false``: bool - 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. Enabling this option is required for the 'zero scope' option to work
* ``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.
.. class:: PacketCache
Upgrade Guide
=============
-1.4.0 to 1.5.x
+1.5.x to 1.6.0
+--------------
+
+The packet cache no longer hashes EDNS Cookies by default, which means that two queries that are identical except for the content of their cookie will now be served the same answer. This only works if the backend is not returning any answer containing EDNS Cookies, otherwise the wrong cookie might be returned to a client. To prevent this, the ``cookieHashing=true`` parameter might be passed to :func:`newPacketCache` so that cookies are hashed, resulting in separate entries in the packet cache.
+
+1.4.x to 1.5.0
--------------
DOH endpoints specified in the fourth parameter of :func:`addDOHLocal` are now specified as exact paths instead of path prefixes. The default endpoint also switched from ``/`` to ``/dns-query``.
--- /dev/null
+../packetcache.hh
\ No newline at end of file
#include "ednsoptions.hh"
#include "iputils.hh"
+bool getNextEDNSOption(const char* data, size_t dataLen, uint16_t& optionCode, uint16_t& optionLen)
+{
+ if (data == nullptr || dataLen < (sizeof(uint16_t) + sizeof(uint16_t))) {
+ return false;
+ }
+
+ size_t pos = 0;
+
+ optionCode = (static_cast<unsigned char>(data[pos]) * 256) + static_cast<unsigned char>(data[pos + 1]);
+ pos += EDNS_OPTION_CODE_SIZE;
+
+ optionLen = (static_cast<unsigned char>(data[pos]) * 256) + static_cast<unsigned char>(data[pos + 1]);
+ pos += EDNS_OPTION_LENGTH_SIZE;
+
+ return true;
+}
+
/* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
int getEDNSOption(char* optRR, const size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize)
{
while(len >= (pos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE) &&
rdLen >= (rdPos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
- const uint16_t optionCode = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+ uint16_t optionCode;
+ uint16_t optionLen;
+ if (!getNextEDNSOption(optRR + pos, len-pos, optionCode, optionLen)) {
+ break;
+ }
+
pos += EDNS_OPTION_CODE_SIZE;
rdPos += EDNS_OPTION_CODE_SIZE;
- const uint16_t optionLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
pos += EDNS_OPTION_LENGTH_SIZE;
rdPos += EDNS_OPTION_LENGTH_SIZE;
- if (optionLen > (rdLen - rdPos) || optionLen > (len - pos))
+
+ if (optionLen > (rdLen - rdPos) || optionLen > (len - pos)) {
return EINVAL;
+ }
if (optionCode == wantedOption) {
*optionValue = optRR + pos - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE);
int getEDNSOptions(const char* optRR, size_t len, EDNSOptionViewMap& options);
/* extract all EDNS0 options from the content (so after rdLen) of the OPT RR */
bool getEDNSOptionsFromContent(const std::string& content, std::vector<std::pair<uint16_t, std::string>>& options);
+/* parse the next EDNS option and the return the code and length. data should point to the beginning of the option code, dataLen should be maximum length of the data (minimum of remaining size in packet and remaining size in rdata) */
+bool getNextEDNSOption(const char* data, size_t dataLen, uint16_t& optionCode, uint16_t& optionLen);
void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res);
}
/* dnsdist's version */
+ DNSDistPacketCache pcSkipCookies(10000);
+ pcSkipCookies.setECSParsingEnabled(true);
+ pcSkipCookies.setCookieHashing(false);
+
+ DNSDistPacketCache pcHashCookies(10000);
+ pcHashCookies.setECSParsingEnabled(true);
+ pcHashCookies.setCookieHashing(true);
+
try {
uint16_t qtype;
uint16_t qclass;
unsigned int consumed;
const DNSName qname(reinterpret_cast<const char*>(data), size, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
- DNSDistPacketCache::getKey(qname.getStorage(), consumed, data, size, false);
+ pcSkipCookies.getKey(qname.getStorage(), consumed, data, size, false);
+ pcHashCookies.getKey(qname.getStorage(), consumed, data, size, false);
boost::optional<Netmask> subnet;
DNSDistPacketCache::getClientSubnet(reinterpret_cast<const char*>(data), consumed, size, subnet);
}
/* auth's version */
try {
- PacketCache::canHashPacket(input);
+ static const std::unordered_set<uint16_t> optionsToIgnore{ EDNSOptionCode::COOKIE };
+
+ PacketCache::canHashPacket(input, false);
+ DNSName qname(input.data(), input.size(), sizeof(dnsheader), false);
+ PacketCache::queryMatches(input, input, qname, optionsToIgnore);
}
catch(const std::exception& e) {
}
/* recursor's version */
try {
- uint16_t ecsBegin = 0;
- uint16_t ecsEnd = 0;
- PacketCache::canHashPacket(input, &ecsBegin, &ecsEnd);
+ static const std::unordered_set<uint16_t> optionsToIgnore{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
+
+ PacketCache::canHashPacket(input, true);
+ DNSName qname(input.data(), input.size(), sizeof(dnsheader), false);
+ PacketCache::queryMatches(input, input, qname, optionsToIgnore);
}
catch(const std::exception& e) {
}
#include "ednsoptions.hh"
#include "misc.hh"
#include "iputils.hh"
+#include "views.hh"
class PacketCache : public boost::noncopyable
{
public:
- static uint32_t canHashPacket(const std::string& packet, uint16_t* ecsBegin, uint16_t* ecsEnd)
+
+ /* 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.
+ */
+ static uint32_t hashAfterQname(const string_view& packet, uint32_t currentHash, size_t pos, bool skipECS)
{
- uint32_t ret = 0;
- ret = burtle(reinterpret_cast<const unsigned char*>(packet.c_str()) + 2, sizeof(dnsheader) - 2, ret); // rest of dnsheader, skip id
- size_t packetSize = packet.size();
- size_t pos = sizeof(dnsheader);
- const char* end = packet.c_str() + packetSize;
- const char* p = packet.c_str() + pos;
-
- for(; p < end && *p; ++p, ++pos) { // XXX if you embed a 0 in your qname we'll stop lowercasing there
- const unsigned char l = dns_tolower(*p); // label lengths can safely be lower cased
- ret=burtle(&l, 1, ret);
- } // XXX the embedded 0 in the qname will break the subnet stripping
-
- const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.c_str());
- const char* skipBegin = p;
- const char* skipEnd = p;
- if (ecsBegin != nullptr && ecsEnd != nullptr) {
- *ecsBegin = 0;
- *ecsEnd = 0;
- }
- /* we need at least 1 (final empty label) + 2 (QTYPE) + 2 (QCLASS)
+ const size_t packetSize = packet.size();
+ assert(packetSize >= sizeof(dnsheader));
+
+ /* we need at least 2 (QTYPE) + 2 (QCLASS)
+
+ OPT root label (1), type (2), class (2) and ttl (4)
+ the OPT RR rdlen (2)
- = 16
+ = 15
*/
- if(ntohs(dh->arcount)==1 && (pos+16) < packetSize) {
- char* optionBegin = nullptr;
- size_t optionLen = 0;
- /* skip the final empty label (1), the qtype (2), qclass (2) */
- /* root label (1), type (2), class (2) and ttl (4) */
- int res = getEDNSOption(const_cast<char*>(reinterpret_cast<const char*>(p)) + 14, end - (p + 14), EDNSOptionCode::ECS, &optionBegin, &optionLen);
- if (res == 0) {
- skipBegin = optionBegin;
- skipEnd = optionBegin + optionLen;
- if (ecsBegin != nullptr && ecsEnd != nullptr) {
- *ecsBegin = optionBegin - packet.c_str();
- *ecsEnd = *ecsBegin + optionLen;
- }
+ const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
+ 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;
}
- if (skipBegin > p) {
- ret = burtle(reinterpret_cast<const unsigned char*>(p), skipBegin-p, ret);
+
+ currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 15, currentHash);
+ /* skip the qtype (2), qclass (2) */
+ /* root label (1), type (2), class (2) and ttl (4) */
+ /* already hashed above */
+ pos += 13;
+
+ const uint16_t rdLen = ((static_cast<unsigned char>(packet.at(pos)) * 256) + static_cast<unsigned char>(packet.at(pos + 1)));
+ /* skip the rd length */
+ /* already hashed above */
+ pos += 2;
+
+ if (rdLen > (packetSize - pos)) {
+ if (pos < packetSize) {
+ currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
+ }
+ return currentHash;
}
- if (skipEnd < end) {
- ret = burtle(reinterpret_cast<const unsigned char*>(skipEnd), end-skipEnd, ret);
+
+ uint16_t rdataRead = 0;
+ uint16_t optionCode;
+ uint16_t optionLen;
+
+ while (pos < packetSize && rdataRead < rdLen && getNextEDNSOption(&packet.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
+ if (optionLen > (rdLen - rdataRead)) {
+ if (packetSize > pos) {
+ currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
+ }
+ return currentHash;
+ }
+
+ bool skip = false;
+ if (optionCode == EDNSOptionCode::COOKIE) {
+ skip = true;
+ }
+ else if (optionCode == EDNSOptionCode::ECS) {
+ if (skipECS) {
+ skip = true;
+ }
+ }
+
+ if (!skip) {
+ /* 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 */
+ currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4, currentHash);
+ }
+
+ pos += 4 + optionLen;
+ rdataRead += 4 + optionLen;
+ }
+
+ if (pos < packetSize) {
+ currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
}
- return ret;
+ return currentHash;
}
- static uint32_t canHashPacket(const std::string& packet)
+ static uint32_t hashHeaderAndQName(const std::string& packet, size_t& pos)
{
- uint32_t ret = 0;
- ret = burtle(reinterpret_cast<const unsigned char*>(packet.c_str()) + 2, sizeof(dnsheader) - 2, ret); // rest of dnsheader, skip id
+ uint32_t currentHash = 0;
size_t packetSize = packet.size();
- size_t pos = sizeof(dnsheader);
- const char* end = packet.c_str() + packetSize;
- const char* p = packet.c_str() + pos;
+ assert(packetSize >= sizeof(dnsheader));
+ currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(2)), sizeof(dnsheader) - 2, currentHash); // rest of dnsheader, skip id
+ pos = sizeof(dnsheader);
+
+ for (; pos < packetSize; ) {
+ const unsigned char labelLen = static_cast<unsigned char>(packet.at(pos));
+ currentHash = burtle(&labelLen, 1, currentHash);
+ ++pos;
+ if (labelLen == 0) {
+ break;
+ }
+
+ for (size_t idx = 0; idx < labelLen && pos < packetSize; ++idx, ++pos) {
+ const unsigned char l = dns_tolower(packet.at(pos));
+ currentHash = burtle(&l, 1, currentHash);
+ }
+ }
- for(; p < end && *p; ++p) { // XXX if you embed a 0 in your qname we'll stop lowercasing there
- const unsigned char l = dns_tolower(*p); // label lengths can safely be lower cased
- ret=burtle(&l, 1, ret);
- } // XXX the embedded 0 in the qname will break the subnet stripping
+ return currentHash;
+ }
- if (p < end) {
- ret = burtle(reinterpret_cast<const unsigned char*>(p), end-p, ret);
+ /* 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.
+ */
+ static uint32_t canHashPacket(const std::string& packet, bool skipECS)
+ {
+ size_t pos = 0;
+ uint32_t currentHash = hashHeaderAndQName(packet, pos);
+ size_t packetSize = packet.size();
+
+ if (pos >= packetSize) {
+ return currentHash;
}
- return ret;
+ return hashAfterQname(packet, currentHash, pos, skipECS);
}
static bool queryHeaderMatches(const std::string& cachedQuery, const std::string& query)
return (cachedQuery.compare(/* skip the ID */ 2, sizeof(dnsheader) - 2, query, 2, sizeof(dnsheader) - 2) == 0);
}
- static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname)
+ static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname, const std::unordered_set<uint16_t>& optionsToIgnore)
{
+ const size_t querySize = query.size();
+ const size_t cachedQuerySize = cachedQuery.size();
+ if (querySize != cachedQuerySize) {
+ return false;
+ }
+
if (!queryHeaderMatches(cachedQuery, query)) {
return false;
}
size_t pos = sizeof(dnsheader) + qname.wirelength();
- return (cachedQuery.compare(pos, cachedQuery.size() - pos, query, pos, query.size() - pos) == 0);
- }
+ /* we need at least 2 (QTYPE) + 2 (QCLASS)
+ + OPT root label (1), type (2), class (2) and ttl (4)
+ + the OPT RR rdlen (2)
+ = 15
+ */
+ const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(query.data());
+ if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= querySize || optionsToIgnore.empty()) {
+ return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
+ }
- static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname, uint16_t ecsBegin, uint16_t ecsEnd)
- {
- if (!queryHeaderMatches(cachedQuery, query)) {
+ /* compare up to the first option, if any */
+ if (cachedQuery.compare(pos, 15, query, pos, 15) != 0) {
return false;
}
- size_t pos = sizeof(dnsheader) + qname.wirelength();
+ /* skip the qtype (2), qclass (2) */
+ /* root label (1), type (2), class (2) and ttl (4) */
+ /* already compared above */
+ pos += 13;
- if (ecsBegin != 0 && ecsBegin >= pos && ecsEnd > ecsBegin) {
- if (cachedQuery.compare(pos, ecsBegin - pos, query, pos, ecsBegin - pos) != 0) {
- return false;
+ const uint16_t rdLen = ((static_cast<unsigned char>(query.at(pos)) * 256) + static_cast<unsigned char>(query.at(pos + 1)));
+ /* skip the rd length */
+ /* already compared above */
+ pos += sizeof(uint16_t);
+
+ if (rdLen > (querySize - pos)) {
+ /* something is wrong, let's just compare everything */
+ return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
+ }
+
+ uint16_t rdataRead = 0;
+ uint16_t optionCode;
+ uint16_t optionLen;
+
+ while (pos < querySize && rdataRead < rdLen && getNextEDNSOption(&query.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
+ if (optionLen > (rdLen - rdataRead)) {
+ return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
}
- if (cachedQuery.compare(ecsEnd, cachedQuery.size() - ecsEnd, query, ecsEnd, query.size() - ecsEnd) != 0) {
+ /* compare the option code and length */
+ if (cachedQuery.compare(pos, 4, query, pos, 4) != 0) {
return false;
}
- }
- else {
- if (cachedQuery.compare(pos, cachedQuery.size() - pos, query, pos, query.size() - pos) != 0) {
- return false;
+ pos += 4;
+ rdataRead += 4;
+
+ if (optionLen > 0 && optionsToIgnore.count(optionCode) == 0) {
+ if (cachedQuery.compare(pos, optionLen, query, pos, optionLen) != 0) {
+ return false;
+ }
}
+ pos += optionLen;
+ rdataRead += optionLen;
+ }
+
+ if (pos >= querySize) {
+ return true;
}
- return true;
+ return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
}
};
unsigned int d_tag{0};
uint32_t d_qhash{0};
uint32_t d_ttlCap{std::numeric_limits<uint32_t>::max()};
- uint16_t d_ecsBegin{0};
- uint16_t d_ecsEnd{0};
bool d_variable{false};
bool d_ecsFound{false};
bool d_ecsParsed{false};
pw.getHeader()->rcode == RCode::ServFail ? SyncRes::s_packetcacheservfailttl :
min(minTTL,SyncRes::s_packetcachettl),
dq.validationState,
- dc->d_ecsBegin,
- dc->d_ecsEnd,
std::move(pbMessage));
}
// else cerr<<"Not putting in packet cache: "<<sr.wasVariable()<<endl;
EDNSSubnetOpts ednssubnet;
bool ecsFound = false;
bool ecsParsed = false;
- uint16_t ecsBegin = 0;
- uint16_t ecsEnd = 0;
std::vector<DNSRecord> records;
boost::optional<int> rcode = boost::none;
uint32_t ttlCap = std::numeric_limits<uint32_t>::max();
as cacheable we would cache it with a wrong tag, so better safe than sorry. */
vState valState;
if (qnameParsed) {
- cacheHit = (!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, qname, qtype, qclass, g_now.tv_sec, &response, &age, &valState, &qhash, &ecsBegin, &ecsEnd, pbMessage ? &(*pbMessage) : nullptr));
+ cacheHit = (!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, qname, qtype, qclass, g_now.tv_sec, &response, &age, &valState, &qhash, pbMessage ? &(*pbMessage) : nullptr));
}
else {
- cacheHit = (!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, qname, &qtype, &qclass, g_now.tv_sec, &response, &age, &valState, &qhash, &ecsBegin, &ecsEnd, pbMessage ? &(*pbMessage) : nullptr));
+ cacheHit = (!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, qname, &qtype, &qclass, g_now.tv_sec, &response, &age, &valState, &qhash, pbMessage ? &(*pbMessage) : nullptr));
}
if (cacheHit) {
dc->d_tcp=false;
dc->d_ecsFound = ecsFound;
dc->d_ecsParsed = ecsParsed;
- dc->d_ecsBegin = ecsBegin;
- dc->d_ecsEnd = ecsEnd;
dc->d_ednssubnet = ednssubnet;
dc->d_ttlCap = ttlCap;
dc->d_variable = variable;
t_bogusqueryring = std::unique_ptr<boost::circular_buffer<pair<DNSName, uint16_t> > >(new boost::circular_buffer<pair<DNSName, uint16_t> >());
t_bogusqueryring->set_capacity(ringsize);
}
-
MT=std::unique_ptr<MTasker<PacketID,string> >(new MTasker<PacketID,string>(::arg().asNum("stack-size")));
threadInfo.mt = MT.get();
return count;
}
-bool RecursorPacketCache::qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, uint16_t ecsBegin, uint16_t ecsEnd)
+bool RecursorPacketCache::qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass)
{
// this ignores checking on the EDNS subnet flags!
if (qname != iter->d_name || iter->d_type != qtype || iter->d_class != qclass) {
return false;
}
- if (iter->d_ecsBegin != ecsBegin || iter->d_ecsEnd != ecsEnd) {
- return false;
- }
-
- return queryMatches(iter->d_query, queryPacket, qname, ecsBegin, ecsEnd);
+ static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
+ return queryMatches(iter->d_query, queryPacket, qname, optionsToSkip);
}
-bool RecursorPacketCache::checkResponseMatches(std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, RecProtoBufMessage* protobufMessage, uint16_t ecsBegin, uint16_t ecsEnd)
+bool RecursorPacketCache::checkResponseMatches(std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, RecProtoBufMessage* protobufMessage)
{
for(auto iter = range.first ; iter != range.second ; ++iter) {
// the possibility is VERY real that we get hits that are not right - birthday paradox
- if (!qrMatch(iter, queryPacket, qname, qtype, qclass, ecsBegin, ecsEnd)) {
+ if (!qrMatch(iter, queryPacket, qname, qtype, qclass)) {
continue;
}
}
}
#endif
-
return true;
}
else {
{
DNSName qname;
uint16_t qtype, qclass;
- uint16_t ecsBegin;
- uint16_t ecsEnd;
vState valState;
- return getResponsePacket(tag, queryPacket, qname, &qtype, &qclass, now, responsePacket, age, &valState, qhash, &ecsBegin, &ecsEnd, nullptr);
+ return getResponsePacket(tag, queryPacket, qname, &qtype, &qclass, now, responsePacket, age, &valState, qhash, nullptr);
}
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, uint32_t* qhash)
{
vState valState;
- uint16_t ecsBegin;
- uint16_t ecsEnd;
- return getResponsePacket(tag, queryPacket, qname, qtype, qclass, now, responsePacket, age, &valState, qhash, &ecsBegin, &ecsEnd, nullptr);
+ return getResponsePacket(tag, queryPacket, qname, qtype, qclass, now, responsePacket, age, &valState, qhash, nullptr);
}
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, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage)
+ std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, RecProtoBufMessage* protobufMessage)
{
- *qhash = canHashPacket(queryPacket, ecsBegin, ecsEnd);
+ *qhash = canHashPacket(queryPacket, true);
const auto& idx = d_packetCache.get<HashTag>();
auto range = idx.equal_range(tie(tag,*qhash));
return false;
}
- return checkResponseMatches(range, queryPacket, qname, qtype, qclass, now, responsePacket, age, valState, protobufMessage, *ecsBegin, *ecsEnd);
+ return checkResponseMatches(range, queryPacket, qname, qtype, qclass, now, responsePacket, age, valState, protobufMessage);
}
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, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage)
+ std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, RecProtoBufMessage* protobufMessage)
{
- *qhash = canHashPacket(queryPacket, ecsBegin, ecsEnd);
+ *qhash = canHashPacket(queryPacket, true);
const auto& idx = d_packetCache.get<HashTag>();
auto range = idx.equal_range(tie(tag,*qhash));
qname = DNSName(queryPacket.c_str(), queryPacket.length(), sizeof(dnsheader), false, qtype, qclass, 0);
- return checkResponseMatches(range, queryPacket, qname, *qtype, *qclass, now, responsePacket, age, valState, protobufMessage, *ecsBegin, *ecsEnd);
+ return checkResponseMatches(range, queryPacket, qname, *qtype, *qclass, now, responsePacket, age, valState, protobufMessage);
}
-void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, uint16_t ecsBegin, uint16_t ecsEnd, boost::optional<RecProtoBufMessage>&& protobufMessage)
+void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, boost::optional<RecProtoBufMessage>&& protobufMessage)
{
auto& idx = d_packetCache.get<HashTag>();
auto range = idx.equal_range(tie(tag,qhash));
moveCacheItemToBack<SequencedTag>(d_packetCache, iter);
iter->d_packet = std::move(responsePacket);
iter->d_query = std::move(query);
- iter->d_ecsBegin = ecsBegin;
- iter->d_ecsEnd = ecsEnd;
iter->d_ttd = now + ttl;
iter->d_creation = now;
iter->d_vstate = valState;
if(iter == range.second) { // nothing to refresh
struct Entry e(qname, std::move(responsePacket), std::move(query));
e.d_qhash = qhash;
- e.d_ecsBegin = ecsBegin;
- e.d_ecsEnd = ecsEnd;
e.d_type = qtype;
e.d_class = qclass;
e.d_ttd = now+ttl;
RecursorPacketCache();
bool getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now, std::string* responsePacket, uint32_t* age, uint32_t* qhash);
bool 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, uint32_t* qhash);
- bool 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, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage);
- bool 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, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage);
- void insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, uint16_t ecsBegin, uint16_t ecsEnd, boost::optional<RecProtoBufMessage>&& protobufMessage);
+ bool 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, RecProtoBufMessage* protobufMessage);
+ bool 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, RecProtoBufMessage* protobufMessage);
+ void insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, boost::optional<RecProtoBufMessage>&& protobufMessage);
void doPruneTo(size_t maxSize=250000);
uint64_t doDump(int fd);
int doWipePacketCache(const DNSName& name, uint16_t qtype=0xffff, bool subtree=false);
uint32_t d_tag;
uint16_t d_type;
uint16_t d_class;
- mutable uint16_t d_ecsBegin;
- mutable uint16_t d_ecsEnd;
mutable vState d_vstate;
inline bool operator<(const struct Entry& rhs) const;
packetCache_t d_packetCache;
- static bool qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, uint16_t ecsBegin, uint16_t ecsEnd);
- bool checkResponseMatches(std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, RecProtoBufMessage* protobufMessage, uint16_t ecsBegin, uint16_t ecsEnd);
+ static bool qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass);
+ bool checkResponseMatches(std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, RecProtoBufMessage* protobufMessage);
public:
void preRemoval(const Entry& entry)
uuid-utils.hh uuid-utils.cc \
validate.cc validate.hh validate-recursor.cc validate-recursor.hh \
version.cc version.hh \
+ views.hh \
webserver.cc webserver.hh \
ws-api.cc ws-api.hh \
ws-recursor.cc ws-recursor.hh \
--- /dev/null
+../views.hh
\ No newline at end of file
#include "dnswriter.hh"
#include "dnsdist-cache.hh"
#include "gettime.hh"
+#include "packetcache.hh"
BOOST_AUTO_TEST_SUITE(test_dnsdistpacketcache_cc)
boost::optional<Netmask> subnetOut;
bool dnssecOK = false;
- /* lookup for a query with an ECS value of 10.0.118.46/32,
+ /* lookup for a query with a first ECS value,
insert a corresponding response */
{
vector<uint8_t> query;
pwQ.getHeader()->id = qid;
DNSPacketWriter::optvect_t ednsOptions;
EDNSSubnetOpts opt;
- opt.source = Netmask("10.0.118.46/32");
+ opt.source = Netmask("10.0.59.220/32");
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
pwQ.addOpt(512, 0, 0, ednsOptions);
pwQ.commit();
BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
}
- /* now lookup for the same query with an ECS value of 10.0.123.193/32
+ /* now lookup for the same query with a different ECS value,
we should get the same key (collision) but no match */
{
vector<uint8_t> query;
pwQ.getHeader()->id = qid;
DNSPacketWriter::optvect_t ednsOptions;
EDNSSubnetOpts opt;
- opt.source = Netmask("10.0.123.193/32");
+ opt.source = Netmask("10.0.167.48/32");
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
pwQ.addOpt(512, 0, 0, ednsOptions);
pwQ.commit();
BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
BOOST_CHECK_EQUAL(PC.getLookupCollisions(), 1U);
}
+
+#if 0
+ /* to be able to compute a new collision if the packet cache hashing code is updated */
+ {
+ DNSDistPacketCache pc(10000);
+ DNSPacketWriter::optvect_t ednsOptions;
+ EDNSSubnetOpts opt;
+ std::map<uint32_t, Netmask> colMap;
+ size_t collisions = 0;
+ size_t total = 0;
+ //qname = DNSName("collision-with-ecs-parsing.cache.tests.powerdns.com.");
+
+ for (size_t idxA = 0; idxA < 256; idxA++) {
+ for (size_t idxB = 0; idxB < 256; idxB++) {
+ for (size_t idxC = 0; idxC < 256; idxC++) {
+ vector<uint8_t> secondQuery;
+ DNSPacketWriter pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+ pwFQ.getHeader()->rd = 1;
+ pwFQ.getHeader()->qr = false;
+ pwFQ.getHeader()->id = 0x42;
+ opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+ ednsOptions.clear();
+ ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
+ pwFQ.addOpt(512, 0, 0, ednsOptions);
+ pwFQ.commit();
+ secondKey = pc.getKey(qname.toDNSString(), qname.wirelength(), secondQuery.data(), secondQuery.size(), false);
+ auto pair = colMap.insert(std::make_pair(secondKey, opt.source));
+ total++;
+ if (!pair.second) {
+ collisions++;
+ cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
+ goto done;
+ }
+ }
+ }
+ }
+ done:
+ cerr<<"collisions: "<<collisions<<endl;
+ cerr<<"total: "<<total<<endl;
+ }
+#endif
}
BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) {
we directly compute the hash instead of querying the
cache because 1/ it's faster 2/ no deferred-lookup issues
*/
- q.setHash(g_PC->canHashPacket(q.getString()));
+ q.setHash(g_PC->canHashPacket(q.getString(), false));
const unsigned int maxTTL = 3600;
g_PC->insert(q, r, maxTTL);
uint16_t qtype = QType::AAAA;
EDNSSubnetOpts opt;
DNSPacketWriter::optvect_t ednsOptions;
+ static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE };
+ static const std::unordered_set<uint16_t> noOptionsToSkip{ };
{
/* same query, different IDs */
pw1.getHeader()->qr = false;
pw1.getHeader()->id = 0x42;
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1);
+ auto hash1 = PacketCache::canHashPacket(spacket1, false);
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);
+ auto hash2 = PacketCache::canHashPacket(spacket2, false);
BOOST_CHECK_EQUAL(hash1, hash2);
- BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname));
+ BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
}
{
pw1.getHeader()->rd = true;
pw1.getHeader()->qr = false;
pw1.getHeader()->id = 0x42;
- opt.source = Netmask("10.0.18.199/32");
+ opt.source = Netmask("10.0.152.74/32");
ednsOptions.clear();
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
pw1.addOpt(512, 0, 0, ednsOptions);
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1);
+ auto hash1 = PacketCache::canHashPacket(spacket1, false);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.getHeader()->rd = true;
pw2.getHeader()->qr = false;
pw2.getHeader()->id = 0x84;
- opt.source = Netmask("10.0.131.66/32");
+ opt.source = Netmask("10.2.70.250/32");
ednsOptions.clear();
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
pw2.addOpt(512, 0, 0, ednsOptions);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2);
+ auto hash2 = PacketCache::canHashPacket(spacket2, false);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same but we should _not_ match */
- BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname));
+ BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
+
+#if 0
+ /* to be able to compute a new collision if the hashing function is updated */
+ {
+ std::map<uint32_t, Netmask> colMap;
+ size_t collisions = 0;
+ size_t total = 0;
+
+ for (size_t idxA = 0; idxA < 256; idxA++) {
+ for (size_t idxB = 0; idxB < 256; idxB++) {
+ for (size_t idxC = 0; idxC < 256; idxC++) {
+ vector<uint8_t> secondQuery;
+ DNSPacketWriter pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+ pwFQ.getHeader()->rd = 1;
+ pwFQ.getHeader()->qr = false;
+ pwFQ.getHeader()->id = 0x42;
+ opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+ ednsOptions.clear();
+ 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 pair = colMap.insert(std::make_pair(secondKey, opt.source));
+ total++;
+ if (!pair.second) {
+ collisions++;
+ cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
+ goto done1;
+ }
+ }
+ }
+ }
+ done1:
+ cerr<<"collisions: "<<collisions<<endl;
+ cerr<<"total: "<<total<<endl;
+ }
+#endif
}
{
pw1.getHeader()->rd = true;
pw1.getHeader()->qr = false;
pw1.getHeader()->id = 0x42;
- opt.source = Netmask("47.8.0.0/32");
+ opt.source = Netmask("10.0.34.159/32");
ednsOptions.clear();
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1);
+ auto hash1 = PacketCache::canHashPacket(spacket1, false);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.getHeader()->rd = true;
pw2.getHeader()->qr = false;
pw2.getHeader()->id = 0x84;
- opt.source = Netmask("18.43.1.0/32");
+ opt.source = Netmask("10.0.179.58/32");
ednsOptions.clear();
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
/* no EDNSOpts::DNSSECOK !! */
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2);
+ auto hash2 = PacketCache::canHashPacket(spacket2, false);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same but we should _not_ match */
- BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname));
+ BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
}
{
EDNSCookiesOpt cookiesOpt;
cookiesOpt.client = string("deadbeef");
cookiesOpt.server = string("deadbeef");
- cookiesOpt.server[4] = -42;
- cookiesOpt.server[5] = -6;
- cookiesOpt.server[6] = 1;
- cookiesOpt.server[7] = 0;
ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, makeEDNSCookiesOptString(cookiesOpt)));
pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1);
+ auto hash1 = PacketCache::canHashPacket(spacket1, false);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
ednsOptions.clear();
ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
cookiesOpt.client = string("deadbeef");
- cookiesOpt.server = string("deadbeef");
- cookiesOpt.server[4] = 29;
- cookiesOpt.server[5] = -79;
- cookiesOpt.server[6] = 1;
- cookiesOpt.server[7] = 0;
+ cookiesOpt.server = string("badc0fee");
ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, makeEDNSCookiesOptString(cookiesOpt)));
pw2.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2);
+ auto hash2 = PacketCache::canHashPacket(spacket2, false);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same but we should _not_ match */
- BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname));
+ BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, noOptionsToSkip));
+ /* but it does match if we skip cookies, though */
+ BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
+
+#if 0
+ {
+ /* to be able to compute a new collision if the packet cache hashing code is updated */
+ std::map<uint32_t, Netmask> colMap;
+ size_t collisions = 0;
+ size_t total = 0;
+
+ for (size_t idxA = 0; idxA < 256; idxA++) {
+ for (size_t idxB = 0; idxB < 256; idxB++) {
+ for (size_t idxC = 0; idxC < 256; idxC++) {
+ vector<uint8_t> secondQuery;
+ DNSPacketWriter pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+ pwFQ.getHeader()->rd = 1;
+ pwFQ.getHeader()->qr = false;
+ pwFQ.getHeader()->id = 0x42;
+ opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+ ednsOptions.clear();
+ 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);
+ colMap.insert(std::make_pair(secondKey, opt.source));
+
+ secondQuery.clear();
+ DNSPacketWriter pwSQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+ pwSQ.getHeader()->rd = 1;
+ pwSQ.getHeader()->qr = false;
+ pwSQ.getHeader()->id = 0x42;
+ opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+ ednsOptions.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);
+
+ total++;
+ if (colMap.count(secondKey)) {
+ collisions++;
+ cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
+ goto done2;
+ }
+ }
+ }
+ }
+ done2:
+ cerr<<"collisions: "<<collisions<<endl;
+ cerr<<"total: "<<total<<endl;
+ }
+#endif
}
}
uint16_t qtype = QType::AAAA;
EDNSSubnetOpts opt;
DNSPacketWriter::optvect_t ednsOptions;
- uint16_t ecsBegin;
- uint16_t ecsEnd;
{
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);
- PacketCache::canHashPacket(spacket1, &ecsBegin, &ecsEnd);
- /* no ECS */
- BOOST_CHECK_EQUAL(ecsBegin, 0);
- BOOST_CHECK_EQUAL(ecsEnd, 0);
+ BOOST_CHECK_NO_THROW(PacketCache::canHashPacket(spacket1, true));
}
}
uint16_t qtype = QType::AAAA;
EDNSSubnetOpts opt;
DNSPacketWriter::optvect_t ednsOptions;
- uint16_t ecsBegin;
- uint16_t ecsEnd;
+ static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
{
/* same query, different IDs */
pw1.getHeader()->qr = false;
pw1.getHeader()->id = 0x42;
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, &ecsBegin, &ecsEnd);
- /* no ECS */
- BOOST_CHECK_EQUAL(ecsBegin, 0);
- BOOST_CHECK_EQUAL(ecsEnd, 0);
+ auto hash1 = PacketCache::canHashPacket(spacket1, true);
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, &ecsBegin, &ecsEnd);
- /* no ECS */
- BOOST_CHECK_EQUAL(ecsBegin, 0);
- BOOST_CHECK_EQUAL(ecsEnd, 0);
+ auto hash2 = PacketCache::canHashPacket(spacket2, true);
BOOST_CHECK_EQUAL(hash1, hash2);
- BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, ecsBegin, ecsEnd));
+ BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
}
{
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, &ecsBegin, &ecsEnd);
- /* ECS value */
- BOOST_CHECK_EQUAL(ecsBegin, sizeof(dnsheader) + qname.wirelength() + ( 2 * sizeof(uint16_t)) /* qtype */ + (2 * sizeof(uint16_t)) /* qclass */ + /* OPT root label */ 1 + sizeof(uint32_t) /* TTL */ + DNS_RDLENGTH_SIZE);
- BOOST_CHECK_EQUAL(ecsEnd, ecsBegin + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + 2 /* family */ + 1 /* scope length */ + 1 /* source length */ + 4 /* IPv4 */);
+ auto hash1 = PacketCache::canHashPacket(spacket1, true);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, &ecsBegin, &ecsEnd);
- /* ECS value */
- BOOST_CHECK_EQUAL(ecsBegin, sizeof(dnsheader) + qname.wirelength() + ( 2 * sizeof(uint16_t)) /* qtype */ + (2 * sizeof(uint16_t)) /* qclass */ + /* OPT root label */ 1 + sizeof(uint32_t) /* TTL */ + DNS_RDLENGTH_SIZE);
- BOOST_CHECK_EQUAL(ecsEnd, ecsBegin + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + 2 /* family */ + 1 /* scope length */ + 1 /* source length */ + 4 /* IPv4 */);
+ auto hash2 = PacketCache::canHashPacket(spacket2, true);
BOOST_CHECK_EQUAL(hash1, hash2);
/* the hash is the same and we don't hash the ECS so we should match */
- BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, ecsBegin, ecsEnd));
+ BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
}
{
pw1.commit();
string spacket1((const char*)&packet[0], packet.size());
- auto hash1 = PacketCache::canHashPacket(spacket1, &ecsBegin, &ecsEnd);
- /* ECS value */
- BOOST_CHECK_EQUAL(ecsBegin, sizeof(dnsheader) + qname.wirelength() + ( 2 * sizeof(uint16_t)) /* qtype */ + (2 * sizeof(uint16_t)) /* qclass */ + /* OPT root label */ 1 + sizeof(uint32_t) /* TTL */ + DNS_RDLENGTH_SIZE);
- BOOST_CHECK_EQUAL(ecsEnd, ecsBegin + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + 2 /* family */ + 1 /* scope length */ + 1 /* source length */ + 4 /* IPv4 */);
+ auto hash1 = PacketCache::canHashPacket(spacket1, true);
packet.clear();
DNSPacketWriter pw2(packet, qname, qtype);
pw2.commit();
string spacket2((const char*)&packet[0], packet.size());
- auto hash2 = PacketCache::canHashPacket(spacket2, &ecsBegin, &ecsEnd);
- /* ECS value */
- BOOST_CHECK_EQUAL(ecsBegin, sizeof(dnsheader) + qname.wirelength() + ( 2 * sizeof(uint16_t)) /* qtype */ + (2 * sizeof(uint16_t)) /* qclass */ + /* OPT root label */ 1 + sizeof(uint32_t) /* TTL */ + DNS_RDLENGTH_SIZE);
- BOOST_CHECK_EQUAL(ecsEnd, ecsBegin + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + 2 /* family */ + 1 /* scope length */ + 1 /* source length */ + 4 /* IPv4 */);
+ auto hash2 = PacketCache::canHashPacket(spacket2, true);
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 */
- BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, ecsBegin, ecsEnd));
+ static const std::unordered_set<uint16_t> skipECSOnly{ EDNSOptionCode::ECS };
+ BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, skipECSOnly));
+
+ /* we do match if we skip the cookie as well */
+ BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
}
}
pw.commit();
string rpacket((const char*)&packet[0], packet.size());
- rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
rpc.doPruneTo(0);
BOOST_CHECK_EQUAL(rpc.size(), 0U);
- rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
rpc.doWipePacketCache(qname);
BOOST_CHECK_EQUAL(rpc.size(), 0U);
- rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
uint32_t qhash2 = 0;
bool found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash2);
BOOST_CHECK(r1packet != r2packet);
/* inserting a response for tag1 */
- rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
/* inserting a different response for tag2, should not override the first one */
- rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 2U);
/* remove all responses from the cache */
BOOST_CHECK_EQUAL(rpc.size(), 0U);
/* reinsert both */
- rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
- rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 2U);
/* remove the responses by qname, should remove both */
BOOST_CHECK_EQUAL(rpc.size(), 0U);
/* insert the response for tag1 */
- rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 1U);
/* we can retrieve it */
BOOST_CHECK_EQUAL(temphash, qhash);
/* adding a response for the second tag */
- rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, 0, 0, boost::none);
+ rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none);
BOOST_CHECK_EQUAL(rpc.size(), 2U);
/* We still get the correct response for the first tag */
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+#ifdef __cpp_lib_string_view
+using std::string_view;
+#else
+#include <boost/version.hpp>
+#if BOOST_VERSION >= 106100
+#include <boost/utility/string_view.hpp>
+using boost::string_view;
+#else
+#include <boost/utility/string_ref.hpp>
+using string_view = boost::string_ref;
+#endif
+#endif
import time
import dns
import clientsubnetoption
+import cookiesoption
from dnsdisttests import DNSDistTest
class TestCaching(DNSDistTest):
self.assertEquals(total, 1)
+ def testCacheDifferentCookies(self):
+ """
+ Cache: The content of cookies should be ignored by the cache
+ """
+ ttl = 600
+ name = 'cache-different-cookies.cache.tests.powerdns.com.'
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ eco = cookiesoption.CookiesOption(b'badc0fee', b'badc0fee')
+ query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco])
+ # second query should be served from the cache
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ receivedResponse.id = response.id
+ self.assertEquals(receivedResponse, response)
+
+ def testCacheCookies(self):
+ """
+ Cache: A query with a cookie should not match one without any cookie
+ """
+ ttl = 600
+ name = 'cache-cookie.cache.tests.powerdns.com.'
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ # second query should NOT be served from the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ def testCacheSameCookieDifferentECS(self):
+ """
+ Cache: The content of cookies should be ignored by the cache but not the ECS one
+ """
+ ttl = 600
+ name = 'cache-different-cookies-different-ecs.cache.tests.powerdns.com.'
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+ query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco,ecso])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ ecso = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
+ query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco,ecso])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+class TestCachingHashingCookies(DNSDistTest):
+
+ _config_template = """
+ pc = newPacketCache(100, {maxTTL=86400, minTTL=1, cookieHashing=true})
+ getPool(""):setCache(pc)
+ newServer{address="127.0.0.1:%d"}
+ """
+
+ def testCached(self):
+ """
+ Cache: Served from cache
+
+ dnsdist is configured to cache entries, we are sending several
+ identical requests and checking that the backend only receive
+ the first one.
+ """
+ numberOfQueries = 10
+ name = 'cached.cache.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ for _ in range(numberOfQueries):
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, response)
+
+ total = 0
+ for key in self._responsesCounter:
+ total += self._responsesCounter[key]
+ TestCaching._responsesCounter[key] = 0
+
+ self.assertEquals(total, 1)
+
+ # TCP should not be cached
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ for _ in range(numberOfQueries):
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, response)
+
+ total = 0
+ for key in self._responsesCounter:
+ total += self._responsesCounter[key]
+ TestCaching._responsesCounter[key] = 0
+
+ self.assertEquals(total, 1)
+
+
+ def testCacheDifferentCookies(self):
+ """
+ Cache: The content of cookies should NOT be ignored by the cache (cookieHashing is set)
+ """
+ ttl = 600
+ name = 'cache-different-cookies.cache-cookie-hashing.tests.powerdns.com.'
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ eco = cookiesoption.CookiesOption(b'badc0fee', b'badc0fee')
+ query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco])
+ differentResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ differentResponse.answer.append(rrset)
+ # second query should NOT be served from the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, differentResponse)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, differentResponse)
+ self.assertNotEquals(receivedResponse, response)
+
+ def testCacheCookies(self):
+ """
+ Cache: A query with a cookie should not match one without any cookie (cookieHashing=true)
+ """
+ ttl = 600
+ name = 'cache-cookie.cache-cookie-hashing.tests.powerdns.com.'
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ # second query should NOT be served from the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ def testCacheSameCookieDifferentECS(self):
+ """
+ Cache: The content of cookies should NOT be ignored by the cache (cookieHashing=true), even with ECS there
+ """
+ ttl = 600
+ name = 'cache-different-cookies-different-ecs.cache-cookie-hashing.tests.powerdns.com.'
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+ query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco,ecso])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ ecso = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
+ query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco,ecso])
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
class TestTempFailureCacheTTLAction(DNSDistTest):
_config_template = """
Cache: Collision with no ECS parsing
"""
name = 'collision-no-ecs-parsing.cache.tests.powerdns.com.'
- ecso = clientsubnetoption.ClientSubnetOption('10.0.188.3', 32)
+ ecso = clientsubnetoption.ClientSubnetOption('10.0.226.63', 32)
query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso], payload=512)
query.flags = dns.flags.RD
response = dns.message.make_response(query)
# second query will hash to the same key, triggering a collision which
# will not be detected because the qname, qtype, qclass and flags will
# match and EDNS Client Subnet parsing has not been enabled
- ecso2 = clientsubnetoption.ClientSubnetOption('10.0.192.138', 32)
+ ecso2 = clientsubnetoption.ClientSubnetOption('10.1.60.19', 32)
query2 = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso2], payload=512)
query2.flags = dns.flags.RD
(_, receivedResponse) = self.sendUDPQuery(query2, response=None, useQueue=False)
Cache: Collision with ECS parsing
"""
name = 'collision-with-ecs-parsing.cache.tests.powerdns.com.'
- ecso = clientsubnetoption.ClientSubnetOption('10.0.115.61', 32)
+ ecso = clientsubnetoption.ClientSubnetOption('10.0.150.206', 32)
query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso], payload=512)
query.flags = dns.flags.RD
response = dns.message.make_response(query)
# second query will hash to the same key, triggering a collision which
# _will_ be detected this time because the qname, qtype, qclass and flags will
# match but EDNS Client Subnet parsing is now enabled and will detect the issue
- ecso2 = clientsubnetoption.ClientSubnetOption('10.0.143.21', 32)
+ ecso2 = clientsubnetoption.ClientSubnetOption('10.0.212.51', 32)
query2 = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso2], payload=512)
query2.flags = dns.flags.RD
response2 = dns.message.make_response(query2)
--- /dev/null
+import clientsubnetoption
+import cookiesoption
+import dns
+import os
+import requests
+
+from recursortests import RecursorTest
+
+class PacketCacheRecursorTest(RecursorTest):
+
+ _confdir = 'PacketCache'
+ _wsPort = 8042
+ _wsTimeout = 2
+ _wsPassword = 'secretpassword'
+ _apiKey = 'secretapikey'
+ _config_template = """
+ packetcache-ttl=60
+ auth-zones=example=configs/%s/example.zone
+ webserver=yes
+ webserver-port=%d
+ webserver-address=127.0.0.1
+ webserver-password=%s
+ api-key=%s
+ """ % (_confdir, _wsPort, _wsPassword, _apiKey)
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ authzonepath = os.path.join(confdir, 'example.zone')
+ with open(authzonepath, 'w') as authzone:
+ authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+a 3600 IN A 192.0.2.42
+b 3600 IN A 192.0.2.42
+c 3600 IN A 192.0.2.42
+d 3600 IN A 192.0.2.42
+e 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+ super(PacketCacheRecursorTest, cls).generateRecursorConfig(confdir)
+
+ @classmethod
+ def setUpClass(cls):
+
+ # we don't need all the auth stuff
+ cls.setUpSockets()
+ cls.startResponders()
+
+ confdir = os.path.join('configs', cls._confdir)
+ cls.createConfigDir(confdir)
+
+ cls.generateRecursorConfig(confdir)
+ cls.startRecursor(confdir, cls._recursorPort)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRecursor()
+
+ def checkPacketCacheMetrics(self, expectedHits, expectedMisses):
+ headers = {'x-api-key': self._apiKey}
+ url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/statistics'
+ r = requests.get(url, headers=headers, timeout=self._wsTimeout)
+ self.assertTrue(r)
+ self.assertEquals(r.status_code, 200)
+ self.assertTrue(r.json())
+ content = r.json()
+ foundHits = False
+ foundMisses = True
+ for entry in content:
+ if entry['name'] == 'packetcache-hits':
+ foundHits = True
+ self.assertEquals(int(entry['value']), expectedHits)
+ elif entry['name'] == 'packetcache-misses':
+ foundMisses = True
+ self.assertEquals(int(entry['value']), expectedMisses)
+
+ self.assertTrue(foundHits)
+ self.assertTrue(foundMisses)
+
+ def testPacketCache(self):
+ # first query, no cookie
+ qname = 'a.example.'
+ query = dns.message.make_query(qname, 'A', want_dnssec=True)
+ expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ res = sender(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
+ self.checkPacketCacheMetrics(0, 1)
+
+ # we should get a hit over UDP this time
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+ self.checkPacketCacheMetrics(1, 1)
+
+ eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+ eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de')
+ ecso1 = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+ ecso2 = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
+
+ # we add a cookie, should not match anymore
+ query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+ self.checkPacketCacheMetrics(1, 2)
+
+ # same cookie, should match
+ query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+ self.checkPacketCacheMetrics(2, 2)
+
+ # different cookie, should still match
+ query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+ self.checkPacketCacheMetrics(3, 2)
+
+ # first cookie but with an ECS option, should not match
+ query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso1])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+ self.checkPacketCacheMetrics(3, 3)
+
+ # different cookie but same ECS option, should match
+ query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2, ecso1])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+ self.checkPacketCacheMetrics(4, 3)
+
+ # first cookie but different ECS option, should still match (we ignore EDNS Client Subnet
+ # in the recursor's packet cache, but ECS-specific responses are not cached
+ query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso2])
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+ self.checkPacketCacheMetrics(5, 3)