#include "dolog.hh"
#include "dnsparser.hh"
#include "dnsdist-cache.hh"
+#include "dnsdist-ecs.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.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): 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)
+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)
{
d_shards.resize(d_shardCount);
}
}
-bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp)
+bool DNSDistPacketCache::getClientSubnet(const char* packet, unsigned int consumed, uint16_t len, boost::optional<Netmask>& subnet)
+{
+ char * optRDLen = NULL;
+ size_t remaining = 0;
+
+ int res = getEDNSOptionsStart(const_cast<char*>(packet), consumed, len, &optRDLen, &remaining);
+
+ if (res == 0) {
+ char * ecsOptionStart = NULL;
+ size_t ecsOptionSize = 0;
+
+ res = getEDNSOption(optRDLen, remaining, EDNSOptionCode::ECS, &ecsOptionStart, &ecsOptionSize);
+
+ if (res == 0 && ecsOptionSize > (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
+
+ EDNSSubnetOpts eso;
+ if (getEDNSSubnetOptsFromString(ecsOptionStart + (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), ecsOptionSize - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), &eso) == true) {
+ subnet = eso.source;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp, const boost::optional<Netmask>& subnet) const
{
if (cachedValue.queryFlags != queryFlags || cachedValue.tcp != tcp || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) {
return false;
}
+ if (d_parseECS && cachedValue.subnet != subnet) {
+ return false;
+ }
+
return true;
}
-void DNSDistPacketCache::insertLocked(CacheShard& shard, uint32_t key, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp, CacheValue& newValue, time_t now, time_t newValidity)
+void DNSDistPacketCache::insertLocked(CacheShard& shard, uint32_t key, CacheValue& newValue)
{
auto& map = shard.d_map;
/* check again now that we hold the lock to prevent a race */
/* in case of collision, don't override the existing entry
except if it has expired */
CacheValue& value = it->second;
- bool wasExpired = value.validity <= now;
+ bool wasExpired = value.validity <= newValue.added;
- if (!wasExpired && !cachedValueMatches(value, queryFlags, qname, qtype, qclass, tcp)) {
+ if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.tcp, newValue.subnet)) {
d_insertCollisions++;
return;
}
/* if the existing entry had a longer TTD, keep it */
- if (newValidity <= value.validity) {
+ if (newValue.validity <= value.validity) {
return;
}
value = newValue;
}
-void DNSDistPacketCache::insert(uint32_t key, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL)
+void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL)
{
if (responseLen < sizeof(dnsheader)) {
return;
newValue.added = now;
newValue.tcp = tcp;
newValue.value = std::string(response, responseLen);
+ newValue.subnet = subnet;
auto& shard = d_shards.at(shardIndex);
d_deferredInserts++;
return;
}
- insertLocked(shard, key, queryFlags, qname, qtype, qclass, tcp, newValue, now, newValidity) ;
+ insertLocked(shard, key, newValue);
}
else {
WriteLock w(&shard.d_lock);
- insertLocked(shard, key, queryFlags, qname, qtype, qclass, tcp, newValue, now, newValidity) ;
+ insertLocked(shard, key, newValue);
}
}
-bool DNSDistPacketCache::get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, uint32_t allowExpired, bool skipAging)
+bool DNSDistPacketCache::get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, boost::optional<Netmask>& subnet, uint32_t allowExpired, bool skipAging)
{
std::string dnsQName(dq.qname->toDNSString());
- uint32_t key = getKey(dnsQName, consumed, (const unsigned char*)dq.dh, dq.len, dq.tcp);
+ uint32_t key = getKey(dnsQName, consumed, reinterpret_cast<const unsigned char*>(dq.dh), dq.len, dq.tcp);
if (keyOut)
*keyOut = key;
+ if (d_parseECS) {
+ getClientSubnet(reinterpret_cast<const char*>(dq.dh), consumed, dq.len, subnet);
+ }
+
uint32_t shardIndex = getShardIndex(key);
time_t now = time(NULL);
time_t age;
}
/* check for collision */
- if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.dh)), *dq.qname, dq.qtype, dq.qclass, dq.tcp)) {
+ if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.dh)), *dq.qname, dq.qtype, dq.qclass, dq.tcp, subnet)) {
d_lookupCollisions++;
return false;
}
class DNSDistPacketCache : boost::noncopyable
{
public:
- DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t maxNegativeTTL=3600, uint32_t staleTTL=60, bool dontAge=false, uint32_t shards=1, bool deferrableInsertLock=true);
+ DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t maxNegativeTTL=3600, uint32_t staleTTL=60, bool dontAge=false, uint32_t shards=1, bool deferrableInsertLock=true, bool parseECS=false);
~DNSDistPacketCache();
- void insert(uint32_t key, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL);
- bool get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, uint32_t allowExpired=0, bool skipAging=false);
+ void insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL);
+ bool get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, boost::optional<Netmask>& subnetOut, uint32_t allowExpired=0, bool skipAging=false);
void purgeExpired(size_t upTo=0);
void expunge(size_t upTo=0);
void expungeByName(const DNSName& name, uint16_t qtype=QType::ANY, bool suffixMatch=false);
uint64_t dump(int fd);
static uint32_t getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA);
+ static uint32_t getKey(const std::string& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp);
private:
time_t getTTD() const { return validity; }
std::string value;
DNSName qname;
+ boost::optional<Netmask> subnet;
uint16_t qtype{0};
uint16_t qclass{0};
uint16_t queryFlags{0};
std::atomic<uint64_t> d_entriesCount;
};
- static uint32_t getKey(const std::string& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp);
- static bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp);
+ static bool getClientSubnet(const char* packet, unsigned int consumed, uint16_t len, boost::optional<Netmask>& subnet);
+ bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp, const boost::optional<Netmask>& subnet) const;
uint32_t getShardIndex(uint32_t key) const;
- void insertLocked(CacheShard& shard, uint32_t key, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp, CacheValue& newValue, time_t now, time_t newValidity);
+ void insertLocked(CacheShard& shard, uint32_t key, CacheValue& newValue);
std::vector<CacheShard> d_shards;
uint32_t d_staleTTL;
bool d_dontAge;
bool d_deferrableInsertLock;
+ bool d_parseECS;
};
}
/* extract the start of the OPT RR in a QUERY packet if any */
-static int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optRDLen, size_t * remaining)
+int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optRDLen, size_t * remaining)
{
assert(packet != NULL);
assert(optRDLen != NULL);
assert(remaining != NULL);
- const struct dnsheader* dh = (const struct dnsheader*) packet;
+ const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
- if (offset >= len)
+ if (offset >= len) {
return ENOENT;
+ }
- if (ntohs(dh->qdcount) != 1 || dh->ancount != 0 || ntohs(dh->arcount) != 1 || dh->nscount != 0)
+ if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->arcount) != 1 || ntohs(dh->nscount) != 0)
return ENOENT;
size_t pos = sizeof(dnsheader) + offset;
return ENOENT;
uint16_t qtype, qclass;
- unsigned int consumed;
- DNSName aname(packet, len, pos, true, &qtype, &qclass, &consumed);
- if ((len - pos) < (consumed + DNS_TYPE_SIZE + DNS_CLASS_SIZE))
+ if ((pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE) >= len) {
+ return ENOENT;
+ }
+
+ if (packet[pos] != 0) {
+ /* not the root so not an OPT record */
return ENOENT;
+ }
+ pos += 1;
+
+ qtype = (const unsigned char)packet[pos]*256 + (const unsigned char)packet[pos+1];
+ pos += DNS_TYPE_SIZE;
+ pos += DNS_CLASS_SIZE;
- pos += consumed + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
if(qtype != QType::OPT || (len - pos) < (DNS_TTL_SIZE + DNS_RDLENGTH_SIZE))
return ENOENT;
size_t remaining = 0;
int res = getEDNSOptionsStart(packet, consumed, *len, (char**) &optRDLen, &remaining);
-
+
if (res == 0) {
char * ecsOptionStart = NULL;
size_t ecsOptionSize = 0;
void generateOptRR(const std::string& optRData, string& res);
int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove);
int rewriteResponseWithoutEDNSOption(const char * packet, const size_t len, const uint16_t optionCodeToSkip, vector<uint8_t>& newContent);
+int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optRDLen, size_t * remaining);
#endif /* HAVE_EBPF */
/* PacketCache */
- g_lua.writeFunction("newPacketCache", [](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL, boost::optional<bool> dontAge, boost::optional<size_t> numberOfShards, boost::optional<bool> deferrableInsertLock, boost::optional<uint32_t> maxNegativeTTL) {
- return std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, maxNegativeTTL ? *maxNegativeTTL : 3600, staleTTL ? *staleTTL : 60, dontAge ? *dontAge : false, numberOfShards ? *numberOfShards : 1, deferrableInsertLock ? *deferrableInsertLock : true);
+ g_lua.writeFunction("newPacketCache", [](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL, boost::optional<bool> dontAge, boost::optional<size_t> numberOfShards, boost::optional<bool> deferrableInsertLock, boost::optional<uint32_t> maxNegativeTTL, boost::optional<bool> ecsParsing) {
+ return std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, maxNegativeTTL ? *maxNegativeTTL : 3600, staleTTL ? *staleTTL : 60, dontAge ? *dontAge : false, numberOfShards ? *numberOfShards : 1, deferrableInsertLock ? *deferrableInsertLock : true, ecsParsing ? *ecsParsing : false);
});
g_lua.registerFunction("toString", &DNSDistPacketCache::toString);
g_lua.registerFunction("isFull", &DNSDistPacketCache::isFull);
}
uint32_t cacheKey = 0;
+ boost::optional<Netmask> subnet;
if (packetCache && !dq.skipCache) {
char cachedResponse[4096];
uint16_t cachedResponseSize = sizeof cachedResponse;
uint32_t allowExpired = ds ? 0 : g_staleCacheEntriesTTL;
- if (packetCache->get(dq, (uint16_t) consumed, dq.dh->id, cachedResponse, &cachedResponseSize, &cacheKey, allowExpired)) {
+ if (packetCache->get(dq, (uint16_t) consumed, dq.dh->id, cachedResponse, &cachedResponseSize, &cacheKey, subnet, allowExpired)) {
DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, (dnsheader*) cachedResponse, sizeof cachedResponse, cachedResponseSize, true, &queryRealTime);
#ifdef HAVE_PROTOBUF
dr.uniqueId = dq.uniqueId;
}
if (packetCache && !dq.skipCache) {
- packetCache->insert(cacheKey, origFlags, qname, qtype, qclass, response, responseLen, true, dh->rcode, dq.tempFailureTTL);
+ packetCache->insert(cacheKey, subnet, origFlags, qname, qtype, qclass, response, responseLen, true, dh->rcode, dq.tempFailureTTL);
}
#ifdef HAVE_DNSCRYPT
}
if (ids->packetCache && !ids->skipCache) {
- ids->packetCache->insert(ids->cacheKey, ids->origFlags, ids->qname, ids->qtype, ids->qclass, response, responseLen, false, dh->rcode, ids->tempFailureTTL);
+ ids->packetCache->insert(ids->cacheKey, ids->subnet, ids->origFlags, ids->qname, ids->qtype, ids->qclass, response, responseLen, false, dh->rcode, ids->tempFailureTTL);
}
if (ids->cs && !ids->cs->muted) {
}
uint32_t cacheKey = 0;
+ boost::optional<Netmask> subnet;
if (packetCache && !dq.skipCache) {
uint16_t cachedResponseSize = dq.size;
uint32_t allowExpired = ss ? 0 : g_staleCacheEntriesTTL;
- if (packetCache->get(dq, consumed, dh->id, query, &cachedResponseSize, &cacheKey, allowExpired)) {
+ if (packetCache->get(dq, consumed, dh->id, query, &cachedResponseSize, &cacheKey, subnet, allowExpired)) {
DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, reinterpret_cast<dnsheader*>(query), dq.size, cachedResponseSize, false, &queryRealTime);
#ifdef HAVE_PROTOBUF
dr.uniqueId = dq.uniqueId;
ids->tempFailureTTL = dq.tempFailureTTL;
ids->origFlags = origFlags;
ids->cacheKey = cacheKey;
+ ids->subnet = subnet;
ids->skipCache = dq.skipCache;
ids->packetCache = packetCache;
ids->ednsAdded = ednsAdded;
#ifdef HAVE_PROTOBUF
boost::optional<boost::uuids::uuid> uniqueId;
#endif
+ boost::optional<Netmask> subnet{boost::none};
std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
std::shared_ptr<QTag> qTag{nullptr};
const ClientState* cs{nullptr};
A Pool can have a packet cache to answer queries directly in stead of going to the backend.
See :doc:`../guides/cache` for a how to.
-.. function:: newPacketCache(maxEntries[, maxTTL=86400[, minTTL=0[, temporaryFailureTTL=60[, staleTTL=60[, dontAge=false[, numberOfShards=1[, deferrableInsertLock=true[, maxNegativeTTL=3600]]]]]]]) -> PacketCache
+.. function:: newPacketCache(maxEntries[, maxTTL=86400[, minTTL=0[, temporaryFailureTTL=60[, staleTTL=60[, dontAge=false[, numberOfShards=1[, deferrableInsertLock=true[, maxNegativeTTL=3600[, parseECS=false]]]]]]]) -> PacketCache
.. versionchanged:: 1.3.0
``numberOfShards`` and ``deferrableInsertLock`` parameters added.
.. versionchanged:: 1.3.1
- ``maxNegativeTTL`` parameter added.
+ ``maxNegativeTTL`` and ``parseECS` parameters added.
Creates a new :class:`PacketCache` with the settings specified.
:param int numberOfShards: Number of shards to divide the cache into, to reduce lock contention
:param bool deferrableInsertLock: Whether the cache should give up insertion if the lock is held by another thread, or simply wait to get the lock
:param bool maxNegativeTTL: Cache a NXDomain or NoData answer from the backend for at most this amount of seconds, even if the TTL of the SOA record is higher
+ :param bool parseECS: Whether any EDNS Client Subnet option present in the query should be extracted and stored to be able to detect hash collisions involving queries with the same qname, qtype and qclass but a different incoming ECS value. Enabling this option adds a parsing cost and only makes sense if at least one backend might send different responses based on the ECS value, so it's disabled by default
.. class:: PacketCache
#include <boost/test/unit_test.hpp>
-#include "ednssubnet.hh"
+#include "ednscookies.hh"
#include "ednsoptions.hh"
+#include "ednssubnet.hh"
#include "dnsdist.hh"
#include "iputils.hh"
#include "dnswriter.hh"
char responseBuf[4096];
uint16_t responseBufSize = sizeof(responseBuf);
uint32_t key = 0;
+ boost::optional<Netmask> subnet;
auto dh = reinterpret_cast<dnsheader*>(query.data());
DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
- bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key);
+ bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
- PC.insert(key, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none);
+ PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none);
- found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+ found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
if (found == true) {
BOOST_CHECK_EQUAL(responseBufSize, responseLen);
int match = memcmp(responseBuf, response.data(), responseLen);
BOOST_CHECK_EQUAL(match, 0);
+ BOOST_CHECK(!subnet);
}
else {
skipped++;
char responseBuf[4096];
uint16_t responseBufSize = sizeof(responseBuf);
uint32_t key = 0;
+ boost::optional<Netmask> subnet;
DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, (struct dnsheader*) query.data(), query.size(), query.size(), false, &queryTime);
- bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key);
+ bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
if (found == true) {
PC.expungeByName(a);
deleted++;
pwQ.getHeader()->rd = 1;
uint16_t len = query.size();
uint32_t key = 0;
+ boost::optional<Netmask> subnet;
char response[4096];
uint16_t responseSize = sizeof(response);
DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, (struct dnsheader*) query.data(), len, query.size(), false, &queryTime);
- if(PC.get(dq, a.wirelength(), pwQ.getHeader()->id, response, &responseSize, &key)) {
+ if(PC.get(dq, a.wirelength(), pwQ.getHeader()->id, response, &responseSize, &key, subnet)) {
matches++;
}
}
char responseBuf[4096];
uint16_t responseBufSize = sizeof(responseBuf);
uint32_t key = 0;
+ boost::optional<Netmask> subnet;
auto dh = reinterpret_cast<dnsheader*>(query.data());
DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
- bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key);
+ bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
// Insert with failure-TTL of 0 (-> should not enter cache).
- PC.insert(key, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional<uint32_t>(0));
- found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+ PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional<uint32_t>(0));
+ found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
// Insert with failure-TTL non-zero (-> should enter cache).
- PC.insert(key, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional<uint32_t>(300));
- found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+ PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional<uint32_t>(300));
+ found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
}
catch(PDNSException& e) {
cerr<<"Had error: "<<e.reason<<endl;
char responseBuf[4096];
uint16_t responseBufSize = sizeof(responseBuf);
uint32_t key = 0;
+ boost::optional<Netmask> subnet;
auto dh = reinterpret_cast<dnsheader*>(query.data());
DNSQuestion dq(&name, QType::A, QClass::IN, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
- bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key);
+ bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
- PC.insert(key, *(getFlagsFromDNSHeader(dh)), name, QType::A, QClass::IN, reinterpret_cast<const char*>(response.data()), responseLen, false, RCode::NoError, boost::none);
- found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+ PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), name, QType::A, QClass::IN, reinterpret_cast<const char*>(response.data()), responseLen, false, RCode::NoError, boost::none);
+ found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+
sleep(2);
/* it should have expired by now */
- found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+ found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
BOOST_CHECK_EQUAL(found, false);
-
+ BOOST_CHECK(!subnet);
}
catch(const PDNSException& e) {
cerr<<"Had error: "<<e.reason<<endl;
char responseBuf[4096];
uint16_t responseBufSize = sizeof(responseBuf);
uint32_t key = 0;
+ boost::optional<Netmask> subnet;
auto dh = reinterpret_cast<dnsheader*>(query.data());
DNSQuestion dq(&name, QType::A, QClass::IN, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
- bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key);
+ bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK(!subnet);
- PC.insert(key, *(getFlagsFromDNSHeader(dh)), name, QType::A, QClass::IN, reinterpret_cast<const char*>(response.data()), responseLen, false, RCode::NXDomain, boost::none);
- found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+ PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), name, QType::A, QClass::IN, reinterpret_cast<const char*>(response.data()), responseLen, false, RCode::NXDomain, boost::none);
+ found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
BOOST_CHECK_EQUAL(found, true);
+ BOOST_CHECK(!subnet);
+
sleep(2);
/* it should have expired by now */
- found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+ found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true);
BOOST_CHECK_EQUAL(found, false);
-
+ BOOST_CHECK(!subnet);
}
catch(const PDNSException& e) {
cerr<<"Had error: "<<e.reason<<endl;
char responseBuf[4096];
uint16_t responseBufSize = sizeof(responseBuf);
uint32_t key = 0;
+ boost::optional<Netmask> subnet;
auto dh = reinterpret_cast<dnsheader*>(query.data());
DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, dh, query.size(), query.size(), false, &queryTime);
- PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key);
+ PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
- PC.insert(key, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none);
+ PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none);
}
}
catch(PDNSException& e) {
char responseBuf[4096];
uint16_t responseBufSize = sizeof(responseBuf);
uint32_t key = 0;
+ boost::optional<Netmask> subnet;
DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, (struct dnsheader*) query.data(), query.size(), query.size(), false, &queryTime);
- bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key);
+ bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet);
if (!found) {
g_missing++;
}
}
+BOOST_AUTO_TEST_CASE(test_PCCollision) {
+ const size_t maxEntries = 150000;
+ DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
+ BOOST_CHECK_EQUAL(PC.getSize(), 0);
+
+ DNSName qname("www.powerdns.com.");
+ uint16_t qtype = QType::AAAA;
+ uint16_t qid = 0x42;
+ uint32_t key;
+ uint32_t secondKey;
+ boost::optional<Netmask> subnetOut;
+
+ /* lookup for a query with an ECS value of 10.0.118.46/32,
+ insert a corresponding response */
+ {
+ vector<uint8_t> query;
+ DNSPacketWriter pwQ(query, qname, qtype, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ pwQ.getHeader()->id = qid;
+ DNSPacketWriter::optvect_t ednsOptions;
+ EDNSSubnetOpts opt;
+ opt.source = Netmask("10.0.118.46/32");
+ ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
+ pwQ.addOpt(512, 0, 0, ednsOptions);
+ pwQ.commit();
+
+ char responseBuf[4096];
+ uint16_t responseBufSize = sizeof(responseBuf);
+ ComboAddress remote("192.0.2.1");
+ struct timespec queryTime;
+ gettime(&queryTime);
+ DNSQuestion dq(&qname, QType::AAAA, QClass::IN, &remote, &remote, pwQ.getHeader(), query.size(), query.size(), false, &queryTime);
+ bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_REQUIRE(subnetOut);
+ BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+
+ vector<uint8_t> response;
+ DNSPacketWriter pwR(response, qname, qtype, QClass::IN, 0);
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->id = qid;
+ pwR.startRecord(qname, qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
+ ComboAddress v6("::1");
+ pwR.xfrCAWithoutPort(6, v6);
+ pwR.commit();
+ pwR.addOpt(512, 0, 0, ednsOptions);
+ pwR.commit();
+
+ PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), qname, qtype, QClass::IN, reinterpret_cast<const char*>(response.data()), response.size(), false, RCode::NoError, boost::none);
+ BOOST_CHECK_EQUAL(PC.getSize(), 1);
+
+ found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut);
+ BOOST_CHECK_EQUAL(found, true);
+ BOOST_REQUIRE(subnetOut);
+ BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+ }
+
+ /* now lookup for the same query with an ECS value of 10.0.123.193/32
+ we should get the same key (collision) but no match */
+ {
+ vector<uint8_t> query;
+ DNSPacketWriter pwQ(query, qname, qtype, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ pwQ.getHeader()->id = qid;
+ DNSPacketWriter::optvect_t ednsOptions;
+ EDNSSubnetOpts opt;
+ opt.source = Netmask("10.0.123.193/32");
+ ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
+ pwQ.addOpt(512, 0, 0, ednsOptions);
+ pwQ.commit();
+
+ char responseBuf[4096];
+ uint16_t responseBufSize = sizeof(responseBuf);
+ ComboAddress remote("192.0.2.1");
+ struct timespec queryTime;
+ gettime(&queryTime);
+ DNSQuestion dq(&qname, QType::AAAA, QClass::IN, &remote, &remote, pwQ.getHeader(), query.size(), query.size(), false, &queryTime);
+ bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &secondKey, subnetOut);
+ BOOST_CHECK_EQUAL(found, false);
+ BOOST_CHECK_EQUAL(secondKey, key);
+ BOOST_REQUIRE(subnetOut);
+ BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+ BOOST_CHECK_EQUAL(PC.getLookupCollisions(), 1);
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
import base64
import time
import dns
+import clientsubnetoption
from dnsdisttests import DNSDistTest
class TestCaching(DNSDistTest):
# same over TCP
(_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
self.assertEquals(receivedResponse, response)
+
+class TestCachingCollisionNoECSParsing(DNSDistTest):
+
+ _config_template = """
+ pc = newPacketCache(100, 86400, 1)
+ getPool(""):setCache(pc)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testCacheCollisionNoECSParsing(self):
+ """
+ Cache: Collision with no ECS parsing
+ """
+ name = 'collision-no-ecs-parsing.cache.tests.powerdns.com.'
+ ecso = clientsubnetoption.ClientSubnetOption('10.0.188.3', 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)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ # first query should 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)
+
+ # 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)
+ 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)
+ receivedResponse.id = response.id
+ self.assertEquals(receivedResponse, response)
+
+class TestCachingCollisionWithECSParsing(DNSDistTest):
+
+ _config_template = """
+ pc = newPacketCache(100, 86400, 1, 60, 60, false, 1, true, 3600, true)
+ getPool(""):setCache(pc)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testCacheCollisionWithECSParsing(self):
+ """
+ Cache: Collision with ECS parsing
+ """
+ name = 'collision-with-ecs-parsing.cache.tests.powerdns.com.'
+ ecso = clientsubnetoption.ClientSubnetOption('10.0.115.61', 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)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ # first query should 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)
+
+ # 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)
+ 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)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ response2.answer.append(rrset)
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query2, response2)
+ self.assertEquals(receivedResponse, response2)