diff --git a/docs/secpoll.zone b/docs/secpoll.zone index 2bf4eb0ab8a8..68065873adf5 100644 --- a/docs/secpoll.zone +++ b/docs/secpoll.zone @@ -1,4 +1,4 @@ -@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2018110701 10800 3600 604800 10800 +@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2018110801 10800 3600 604800 10800 @ 3600 IN NS pdns-public-ns1.powerdns.com. @ 3600 IN NS pdns-public-ns2.powerdns.com. ; Auth @@ -275,3 +275,6 @@ recursor-4.0.0_beta1-1pdns.xenial.ubuntu.security-status 60 IN TXT "3 Upgrade no recursor-4.0.0_alpha2-1pdns.jessie.raspbian.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/" recursor-4.0.0_alpha3-1pdns.jessie.raspbian.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/" recursor-4.0.0_beta1-1pdns.jessie.raspbian.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/" + +; dnsdist +dnsdist-1.3.3.security-status 60 IN TXT "1 OK" diff --git a/pdns/dnsdist-cache.cc b/pdns/dnsdist-cache.cc index 6c84357a5585..b4c11df37ae4 100644 --- a/pdns/dnsdist-cache.cc +++ b/pdns/dnsdist-cache.cc @@ -78,9 +78,9 @@ bool DNSDistPacketCache::getClientSubnet(const char* packet, unsigned int consum 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& subnet) const +bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp, bool dnssecOK, const boost::optional& subnet) const { - if (cachedValue.queryFlags != queryFlags || cachedValue.tcp != tcp || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) { + if (cachedValue.queryFlags != queryFlags || cachedValue.dnssecOK != dnssecOK || cachedValue.tcp != tcp || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) { return false; } @@ -113,7 +113,7 @@ void DNSDistPacketCache::insertLocked(CacheShard& shard, uint32_t key, CacheValu CacheValue& value = it->second; bool wasExpired = value.validity <= newValue.added; - if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.tcp, newValue.subnet)) { + if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.tcp, newValue.dnssecOK, newValue.subnet)) { d_insertCollisions++; return; } @@ -126,7 +126,7 @@ void DNSDistPacketCache::insertLocked(CacheShard& shard, uint32_t key, CacheValu value = newValue; } -void DNSDistPacketCache::insert(uint32_t key, const boost::optional& 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 tempFailureTTL) +void DNSDistPacketCache::insert(uint32_t key, const boost::optional& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode, boost::optional tempFailureTTL) { if (responseLen < sizeof(dnsheader)) { return; @@ -179,6 +179,7 @@ void DNSDistPacketCache::insert(uint32_t key, const boost::optional& su newValue.validity = newValidity; newValue.added = now; newValue.tcp = tcp; + newValue.dnssecOK = dnssecOK; newValue.value = std::string(response, responseLen); newValue.subnet = subnet; @@ -200,7 +201,7 @@ void DNSDistPacketCache::insert(uint32_t key, const boost::optional& su } } -bool DNSDistPacketCache::get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, boost::optional& subnet, 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& subnet, bool dnssecOK, uint32_t allowExpired, bool skipAging) { std::string dnsQName(dq.qname->toDNSString()); uint32_t key = getKey(dnsQName, consumed, reinterpret_cast(dq.dh), dq.len, dq.tcp); @@ -247,7 +248,7 @@ bool DNSDistPacketCache::get(const DNSQuestion& dq, uint16_t consumed, uint16_t } /* check for collision */ - if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.dh)), *dq.qname, dq.qtype, dq.qclass, dq.tcp, subnet)) { + if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.dh)), *dq.qname, dq.qtype, dq.qclass, dq.tcp, dnssecOK, subnet)) { d_lookupCollisions++; return false; } diff --git a/pdns/dnsdist-cache.hh b/pdns/dnsdist-cache.hh index 1263c0a0e315..14902c400c22 100644 --- a/pdns/dnsdist-cache.hh +++ b/pdns/dnsdist-cache.hh @@ -33,8 +33,8 @@ 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, bool parseECS=false); ~DNSDistPacketCache(); - void insert(uint32_t key, const boost::optional& 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 tempFailureTTL); - bool get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, boost::optional& subnetOut, uint32_t allowExpired=0, bool skipAging=false); + void insert(uint32_t key, const boost::optional& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode, boost::optional tempFailureTTL); + bool get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, boost::optional& subnetOut, bool dnssecOK, 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); @@ -70,6 +70,7 @@ private: time_t validity{0}; uint16_t len{0}; bool tcp{false}; + bool dnssecOK{false}; }; class CacheShard @@ -95,7 +96,7 @@ private: }; static bool getClientSubnet(const char* packet, unsigned int consumed, uint16_t len, boost::optional& subnet); - bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp, const boost::optional& subnet) const; + bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool tcp, bool dnssecOK, const boost::optional& subnet) const; uint32_t getShardIndex(uint32_t key) const; void insertLocked(CacheShard& shard, uint32_t key, CacheValue& newValue); diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index a697912bcbb2..10f1e9191840 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -430,6 +430,7 @@ const std::vector g_consoleKeywords{ { "setPayloadSizeOnSelfGeneratedAnswers", true, "payloadSize", "set the UDP payload size advertised via EDNS on self-generated responses" }, { "setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy" }, { "setPoolServerPolicy", true, "name, func, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" }, + { "setPreserveTrailingData", true, "bool", "set whether trailing data should be preserved while adding ECS or XPF records to incoming queries" }, { "setQueryCount", true, "bool", "set whether queries should be counted" }, { "setQueryCountFilter", true, "func", "filter queries that would be counted, where `func` is a function with parameter `dq` which decides whether a query should and how it should be counted" }, { "setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking" }, diff --git a/pdns/dnsdist-ecs.cc b/pdns/dnsdist-ecs.cc index 71b852f25f42..d1cd71a8673a 100644 --- a/pdns/dnsdist-ecs.cc +++ b/pdns/dnsdist-ecs.cc @@ -353,7 +353,7 @@ static bool addECSToExistingOPT(char* const packet, size_t const packetSize, uin return true; } -static bool addEDNSWithECS(char* const packet, size_t const packetSize, uint16_t* const len, const string& newECSOption, bool* const ednsAdded) +static bool addEDNSWithECS(char* const packet, size_t const packetSize, uint16_t* const len, const string& newECSOption, bool* const ednsAdded, bool preserveTrailingData) { /* we need to add a EDNS0 RR with one EDNS0 ECS option, fixing the AR count */ string EDNSRR; @@ -365,17 +365,27 @@ static bool addEDNSWithECS(char* const packet, size_t const packetSize, uint16_t return false; } + uint32_t realPacketLen = getDNSPacketLength(packet, *len); + if (realPacketLen < *len && preserveTrailingData) { + size_t toMove = *len - realPacketLen; + memmove(packet + realPacketLen + EDNSRR.size(), packet + realPacketLen, toMove); + *len += EDNSRR.size(); + } + else { + *len = realPacketLen + EDNSRR.size(); + } + uint16_t arcount = ntohs(dh->arcount); arcount++; dh->arcount = htons(arcount); *ednsAdded = true; - memcpy(packet + *len, EDNSRR.c_str(), EDNSRR.size()); - *len += EDNSRR.size(); + memcpy(packet + realPacketLen, EDNSRR.c_str(), EDNSRR.size()); + return true; } -bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, bool overrideExisting, const string& newECSOption) +bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, bool overrideExisting, const string& newECSOption, bool preserveTrailingData) { assert(packet != nullptr); assert(len != nullptr); @@ -388,7 +398,7 @@ bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const u int res = getEDNSOptionsStart(packet, consumed, *len, &optRDPosition, &remaining); if (res != 0) { - return addEDNSWithECS(packet, packetSize, len, newECSOption, ednsAdded); + return addEDNSWithECS(packet, packetSize, len, newECSOption, ednsAdded, preserveTrailingData); } unsigned char* optRDLen = reinterpret_cast(packet) + optRDPosition; @@ -412,14 +422,14 @@ bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const u return true; } -bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded) +bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded, bool preserveTrailingData) { assert(dq.remote != nullptr); string newECSOption; - generateECSOption(dq.ecsSet ? dq.ecs.getNetwork() : *dq.remote, newECSOption, dq.ecsSet ? dq.ecs.getBits() : dq.ecsPrefixLength); + generateECSOption(dq.ecsSet ? dq.ecs.getNetwork() : *dq.remote, newECSOption, dq.ecsSet ? dq.ecs.getBits() : dq.ecsPrefixLength); char* packet = reinterpret_cast(dq.dh); - return handleEDNSClientSubnet(packet, dq.size, dq.consumed, &dq.len, ednsAdded, ecsAdded, dq.ecsOverride, newECSOption); + return handleEDNSClientSubnet(packet, dq.size, dq.consumed, &dq.len, ednsAdded, ecsAdded, dq.ecsOverride, newECSOption, preserveTrailingData); } static int removeEDNSOptionFromOptions(unsigned char* optionsStart, const uint16_t optionsLen, const uint16_t optionCodeToRemove, uint16_t* newOptionsLen) diff --git a/pdns/dnsdist-ecs.hh b/pdns/dnsdist-ecs.hh index 5b664407bcdf..40bf974bd055 100644 --- a/pdns/dnsdist-ecs.hh +++ b/pdns/dnsdist-ecs.hh @@ -35,8 +35,8 @@ bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const s bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uint16_t payloadSize); bool addEDNSToQueryTurnedResponse(DNSQuestion& dq); -bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded); -bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, bool overrideExisting, const string& newECSOption); +bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded, bool preserveTrailingData); +bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, bool overrideExisting, const string& newECSOption, bool preserveTrailingData); bool parseEDNSOptions(DNSQuestion& dq); diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index 6e9233634791..5cb2dc3e8e4f 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -176,7 +176,7 @@ DNSAction::Action TeeAction::operator()(DNSQuestion* dq, string* ruleresult) con string newECSOption; generateECSOption(dq->ecsSet ? dq->ecs.getNetwork() : *dq->remote, newECSOption, dq->ecsSet ? dq->ecs.getBits() : dq->ecsPrefixLength); - if (!handleEDNSClientSubnet(const_cast(query.c_str()), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, dq->ecsOverride, newECSOption)) { + if (!handleEDNSClientSubnet(const_cast(query.c_str()), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, dq->ecsOverride, newECSOption, g_preserveTrailingData)) { return DNSAction::Action::None; } diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 2bc27b1e680f..1f468ebd2173 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -913,6 +913,8 @@ void setupLuaConfig(bool client) g_lua.writeFunction("setECSOverride", [](bool override) { g_ECSOverride=override; }); + g_lua.writeFunction("setPreserveTrailingData", [](bool preserve) { g_preserveTrailingData = preserve; }); + g_lua.writeFunction("showDynBlocks", []() { setLuaNoSideEffect(); auto slow = g_dynblockNMG.getCopy(); diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index 656f8aad442e..131fd1a43a4a 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -22,6 +22,7 @@ #include "dnsdist.hh" #include "dnsdist-ecs.hh" #include "dnsdist-rings.hh" +#include "dnsdist-xpf.hh" #include "dnsparser.hh" #include "ednsoptions.hh" @@ -433,7 +434,7 @@ void* tcpClientThread(int pipefd) } if (dq.useECS && ((ds && ds->useECS) || (!ds && serverPool->getECS()))) { - if (!handleEDNSClientSubnet(dq, &(ednsAdded), &(ecsAdded))) { + if (!handleEDNSClientSubnet(dq, &(ednsAdded), &(ecsAdded), g_preserveTrailingData)) { vinfolog("Dropping query from %s because we couldn't insert the ECS value", ci.remote.toStringWithPort()); goto drop; } @@ -441,11 +442,13 @@ void* tcpClientThread(int pipefd) uint32_t cacheKey = 0; boost::optional subnet; + bool dnssecOK = false; 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, subnet, allowExpired)) { + dnssecOK = (getEDNSZ(dq) & EDNS_HEADER_FLAG_DO); + if (packetCache->get(dq, (uint16_t) consumed, dq.dh->id, cachedResponse, &cachedResponseSize, &cacheKey, subnet, dnssecOK, allowExpired)) { DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.consumed, dq.local, dq.remote, (dnsheader*) cachedResponse, sizeof cachedResponse, cachedResponseSize, true, &queryRealTime); #ifdef HAVE_PROTOBUF dr.uniqueId = dq.uniqueId; @@ -501,7 +504,7 @@ void* tcpClientThread(int pipefd) } if (dq.addXPF && ds->xpfRRCode != 0) { - addXPF(dq, ds->xpfRRCode); + addXPF(dq, ds->xpfRRCode, g_preserveTrailingData); } int dsock = -1; @@ -629,7 +632,7 @@ void* tcpClientThread(int pipefd) } if (packetCache && !dq.skipCache) { - packetCache->insert(cacheKey, subnet, origFlags, qname, qtype, qclass, response, responseLen, true, dh->rcode, dq.tempFailureTTL); + packetCache->insert(cacheKey, subnet, origFlags, dnssecOK, qname, qtype, qclass, response, responseLen, true, dh->rcode, dq.tempFailureTTL); } #ifdef HAVE_DNSCRYPT diff --git a/pdns/dnsdist-xpf.cc b/pdns/dnsdist-xpf.cc new file mode 100644 index 000000000000..c828aadfb0b1 --- /dev/null +++ b/pdns/dnsdist-xpf.cc @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#include "dnsdist-xpf.hh" + +#include "dnsparser.hh" +#include "xpf.hh" + +bool addXPF(DNSQuestion& dq, uint16_t optionCode, bool preserveTrailingData) +{ + std::string payload = generateXPFPayload(dq.tcp, *dq.remote, *dq.local); + uint8_t root = '\0'; + dnsrecordheader drh; + drh.d_type = htons(optionCode); + drh.d_class = htons(QClass::IN); + drh.d_ttl = 0; + drh.d_clen = htons(payload.size()); + size_t recordHeaderLen = sizeof(root) + sizeof(drh); + + size_t available = dq.size - dq.len; + + if ((payload.size() + recordHeaderLen) > available) { + return false; + } + + size_t xpfSize = sizeof(root) + sizeof(drh) + payload.size(); + uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast(dq.dh), dq.len); + if (realPacketLen < dq.len && preserveTrailingData) { + size_t toMove = dq.len - realPacketLen; + memmove(reinterpret_cast(dq.dh) + realPacketLen + xpfSize, reinterpret_cast(dq.dh) + realPacketLen, toMove); + dq.len += xpfSize; + } + else { + dq.len = realPacketLen + xpfSize; + } + + size_t pos = realPacketLen; + memcpy(reinterpret_cast(dq.dh) + pos, &root, sizeof(root)); + pos += sizeof(root); + memcpy(reinterpret_cast(dq.dh) + pos, &drh, sizeof(drh)); + pos += sizeof(drh); + memcpy(reinterpret_cast(dq.dh) + pos, payload.data(), payload.size()); + pos += payload.size(); + + dq.dh->arcount = htons(ntohs(dq.dh->arcount) + 1); + + return true; +} diff --git a/pdns/dnsdist-xpf.hh b/pdns/dnsdist-xpf.hh new file mode 100644 index 000000000000..5a1b411146f9 --- /dev/null +++ b/pdns/dnsdist-xpf.hh @@ -0,0 +1,27 @@ +/* + * 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 + +#include "dnsdist.hh" + +bool addXPF(DNSQuestion& dq, uint16_t optionCode, bool preserveTrailingData); + diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 488056870618..115b0f707ee5 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -48,6 +48,7 @@ #include "dnsdist-lua.hh" #include "dnsdist-rings.hh" #include "dnsdist-secpoll.hh" +#include "dnsdist-xpf.hh" #include "base64.hh" #include "delaypipe.hh" @@ -62,7 +63,6 @@ #include "sodcrypto.hh" #include "sstuff.hh" #include "threadname.hh" -#include "xpf.hh" thread_local boost::uuids::random_generator t_uuidGenerator; @@ -143,7 +143,8 @@ int g_udpTimeout{2}; bool g_servFailOnNoPolicy{false}; bool g_truncateTC{false}; -bool g_fixupCase{0}; +bool g_fixupCase{false}; +bool g_preserveTrailingData{false}; static void truncateTC(char* packet, uint16_t* len, size_t responseSize, unsigned int consumed) try @@ -517,7 +518,7 @@ try { } if (ids->packetCache && !ids->skipCache) { - ids->packetCache->insert(ids->cacheKey, ids->subnet, 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->dnssecOK, ids->qname, ids->qtype, ids->qclass, response, responseLen, false, dh->rcode, ids->tempFailureTTL); } if (ids->cs && !ids->cs->muted) { @@ -1197,38 +1198,6 @@ static ssize_t udpClientSendRequestToBackend(DownstreamState* ss, const int sd, return result; } -bool addXPF(DNSQuestion& dq, uint16_t optionCode) -{ - std::string payload = generateXPFPayload(dq.tcp, *dq.remote, *dq.local); - uint8_t root = '\0'; - dnsrecordheader drh; - drh.d_type = htons(optionCode); - drh.d_class = htons(QClass::IN); - drh.d_ttl = 0; - drh.d_clen = htons(payload.size()); - size_t recordHeaderLen = sizeof(root) + sizeof(drh); - - size_t available = dq.size - dq.len; - - if ((payload.size() + recordHeaderLen) > available) { - return false; - } - - size_t pos = dq.len; - memcpy(reinterpret_cast(dq.dh) + pos, &root, sizeof(root)); - pos += sizeof(root); - memcpy(reinterpret_cast(dq.dh) + pos, &drh, sizeof(drh)); - pos += sizeof(drh); - memcpy(reinterpret_cast(dq.dh) + pos, payload.data(), payload.size()); - pos += payload.size(); - - dq.len = pos; - - dq.dh->arcount = htons(ntohs(dq.dh->arcount) + 1); - - return true; -} - static bool isUDPQueryAcceptable(ClientState& cs, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest) { if (msgh->msg_flags & MSG_TRUNC) { @@ -1357,6 +1326,7 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct unsigned int consumed = 0; DNSName qname(query, len, sizeof(dnsheader), false, &qtype, &qclass, &consumed); DNSQuestion dq(&qname, qtype, qclass, consumed, dest.sin4.sin_family != 0 ? &dest : &cs.local, &remote, dh, queryBufferSize, len, false, &queryRealTime); + bool dnssecOK = false; if (!processQuery(holders, dq, poolname, &delayMsec, now)) { @@ -1422,7 +1392,7 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct bool ednsAdded = false; bool ecsAdded = false; if (dq.useECS && ((ss && ss->useECS) || (!ss && serverPool->getECS()))) { - if (!handleEDNSClientSubnet(dq, &(ednsAdded), &(ecsAdded))) { + if (!handleEDNSClientSubnet(dq, &(ednsAdded), &(ecsAdded), g_preserveTrailingData)) { vinfolog("Dropping query from %s because we couldn't insert the ECS value", remote.toStringWithPort()); return; } @@ -1433,7 +1403,8 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct 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, subnet, allowExpired)) { + dnssecOK = (getEDNSZ(dq) & EDNS_HEADER_FLAG_DO); + if (packetCache->get(dq, consumed, dh->id, query, &cachedResponseSize, &cacheKey, subnet, dnssecOK, allowExpired)) { DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.consumed, dq.local, dq.remote, reinterpret_cast(query), dq.size, cachedResponseSize, false, &queryRealTime); #ifdef HAVE_PROTOBUF dr.uniqueId = dq.uniqueId; @@ -1514,7 +1485,7 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct } if (dq.addXPF && ss->xpfRRCode != 0) { - addXPF(dq, ss->xpfRRCode); + addXPF(dq, ss->xpfRRCode, g_preserveTrailingData); } ss->queries++; @@ -1550,6 +1521,7 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct ids->ednsAdded = ednsAdded; ids->ecsAdded = ecsAdded; ids->qTag = dq.qTag; + ids->dnssecOK = dnssecOK; /* If we couldn't harvest the real dest addr, still write down the listening addr since it will be useful diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 566a2c245e63..096ff8d8e590 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -536,6 +536,7 @@ struct IDState bool ecsAdded{false}; bool skipCache{false}; bool destHarvested{false}; // if true, origDest holds the original dest addr, otherwise the listening addr + bool dnssecOK{false}; }; typedef std::unordered_map QueryCountRecords; @@ -948,6 +949,7 @@ extern uint32_t g_hashperturb; extern bool g_useTCPSinglePipe; extern std::atomic g_downstreamTCPCleanupInterval; extern size_t g_udpVectorSize; +extern bool g_preserveTrailingData; #ifdef HAVE_EBPF extern shared_ptr g_defaultBPFFilter; diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index f2d79be91578..92c8632ce89c 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -111,6 +111,7 @@ dnsdist_SOURCES = \ dnsdist-snmp.cc dnsdist-snmp.hh \ dnsdist-tcp.cc \ dnsdist-web.cc \ + dnsdist-xpf.cc dnsdist-xpf.hh \ dnslabeltext.cc \ dnsname.cc dnsname.hh \ dnsparser.hh dnsparser.cc \ @@ -231,6 +232,7 @@ testrunner_SOURCES = \ dnsdist.hh \ dnsdist-cache.cc dnsdist-cache.hh \ dnsdist-ecs.cc dnsdist-ecs.hh \ + dnsdist-xpf.cc dnsdist-xpf.hh \ dnscrypt.cc dnscrypt.hh \ dnslabeltext.cc \ dnsname.cc dnsname.hh \ diff --git a/pdns/dnsdistdist/dnsdist-xpf.cc b/pdns/dnsdistdist/dnsdist-xpf.cc new file mode 120000 index 000000000000..66fd88d61623 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-xpf.cc @@ -0,0 +1 @@ +../dnsdist-xpf.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsdist-xpf.hh b/pdns/dnsdistdist/dnsdist-xpf.hh new file mode 120000 index 000000000000..c2b75e2df4ef --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-xpf.hh @@ -0,0 +1 @@ +../dnsdist-xpf.hh \ No newline at end of file diff --git a/pdns/dnsdistdist/docs/advanced/ecs.rst b/pdns/dnsdistdist/docs/advanced/ecs.rst index b8ce4b2d01c8..643f467a02c2 100644 --- a/pdns/dnsdistdist/docs/advanced/ecs.rst +++ b/pdns/dnsdistdist/docs/advanced/ecs.rst @@ -14,3 +14,5 @@ In addition to the global settings, rules and Lua bindings can alter this behavi * calling :func:`ECSPrefixLengthAction(v4, v6)` or setting ``dq.ecsPrefixLength`` will override the global :func:`setECSSourcePrefixV4()` and :func:`setECSSourcePrefixV6()` values. In effect this means that for the EDNS Client Subnet option to be added to the request, ``useClientSubnet`` should be set to ``true`` for the backend used (default to ``false``) and ECS should not have been disabled by calling :func:`DisableECSAction` or setting ``dq.useECS`` to ``false`` (default to true). + +Note that any trailing data present in the incoming query is removed by default when an OPT (or XPF) record has to be inserted. This behaviour can be modified using :func:`setPreserveTrailingData()`. diff --git a/pdns/dnsdistdist/docs/changelog.rst b/pdns/dnsdistdist/docs/changelog.rst index 76499aedfef1..48ec0fa63566 100644 --- a/pdns/dnsdistdist/docs/changelog.rst +++ b/pdns/dnsdistdist/docs/changelog.rst @@ -1,6 +1,192 @@ Changelog ========= +.. changelog:: + :version: 1.3.3 + :released: 8th of November 2018 + + .. change:: + :tags: New Features + :pullreq: 6737, 6939 + :tickets: 6932 + + Add consistent hash builtin policy + + .. change:: + :tags: New Features + :pullreq: 6803 + + Add EDNSOptionRule + + .. change:: + :tags: New Features + :pullreq: 6813 + + Add DSTPortRule (phonedph1) + + .. change:: + :tags: New Features + :pullreq: 6826 + + Make getOutstanding usable from both lua and console (phonedph1) + + .. change:: + :tags: Improvements + :pullreq: 6831 + + Get rid of some allocs/copies in DNS parsing + + .. change:: + :tags: Bug Fixes + :pullreq: 6835 + + Display dynblocks' default action, None, as the global one + + .. change:: + :tags: Improvements + :pullreq: 6847 + :tickets: 6348, 4857 + + Set a correct EDNS OPT RR for self-generated answers + + .. change:: + :tags: New Features + :pullreq: 6856 + + Added :excludeRange and :includeRange methods to DynBPFFilter class (Reinier Schoof) + + .. change:: + :tags: Improvements + :pullreq: 6877 + + Fix a sign-comparison warning in isEDNSOptionInOPT() + + .. change:: + :tags: New Features + :pullreq: 3935, 6343, 6901, 7007, 7089 + :tickets: 4947, 6002 + + Add Prometheus stats support (Pavel Odintsov, Kai S) + + .. change:: + :tags: Bug Fixes + :pullreq: 6956 + + Fix compilation when SO_REUSEPORT is not defined + + .. change:: + :tags: Improvements + :pullreq: 6986 + :tickets: 6907, 6907 + + Add warning rates to DynBlockRulesGroup rules + + .. change:: + :tags: New Features + :pullreq: 6997 + :tickets: 6974 + + Name threads in the programs + + .. change:: + :tags: Improvements + :pullreq: 7015 + :tickets: 7004, 6990 + + Add support for exporting a server id in protobuf + + .. change:: + :tags: Improvements + :pullreq: 7030 + + dnsdist did not set TCP_NODELAY, causing needless latency + + .. change:: + :tags: Bug Fixes + :pullreq: 7060 + + Release memory on DNS over TLS handshake failure + + .. change:: + :tags: Improvements + :pullreq: 7062 + + Add a setting to control the number of stored sessions + + .. change:: + :tags: Improvements + :pullreq: 7064 + :tickets: 7060 + + Wrap GnuTLS and OpenSSL pointers in smart pointers + + .. change:: + :tags: New Features + :pullreq: 7075 + :tickets: 6908 + + Support the NXDomain action with dynamic blocks + + .. change:: + :tags: Improvements + :pullreq: 7078 + :tickets: 6909 + + Add a 'creationOrder' field to rules + + .. change:: + :tags: Improvements + :pullreq: 7092 + :tickets: 7091 + + Fix return-type detection with boost 1.69's tribool + + .. change:: + :tags: Improvements + :pullreq: 7104 + :tickets: 7096 + + Fix format string issue on 32bits ARM + + .. change:: + :tags: Improvements + :pullreq: 7108 + + Wrap TCP connection objects in smart pointers + + .. change:: + :tags: Improvements + :pullreq: 7109 + :tickets: 7084 + + Add the setConsoleOutputMaxMsgSize function + + .. change:: + :tags: New Features + :pullreq: 7115 + + Add security polling + + .. change:: + :tags: Improvements + :pullreq: 7117 + :tickets: 7112 + + Add the ability to update webserver credentials + + .. change:: + :tags: New Features + :pullreq: 7140 + + Add a PoolAvailableRule to easily add backup pools (Robin Geuze) + + .. change:: + :tags: Bug Fixes + :pullreq: 7165 + :tickets: 6896 + + Handle trailing data correctly when adding OPT or ECS info + .. changelog:: :version: 1.3.2 :released: 10th of July 2018 diff --git a/pdns/dnsdistdist/docs/security-advisories/powerdns-advisory-for-dnsdist-2018-08.rst b/pdns/dnsdistdist/docs/security-advisories/powerdns-advisory-for-dnsdist-2018-08.rst new file mode 100644 index 000000000000..ccee76e9cac8 --- /dev/null +++ b/pdns/dnsdistdist/docs/security-advisories/powerdns-advisory-for-dnsdist-2018-08.rst @@ -0,0 +1,22 @@ +PowerDNS Security Advisory for dnsdist 2018-08: Record smuggling when adding ECS or XPF +======================================================================================= + +- CVE: CVE-2018-14663 +- Date: November 8th 2018 +- Affects: PowerDNS DNSDist up to and including 1.3.2 +- Not affected: 1.3.3 +- Severity: Low +- Impact: Insufficient validation +- Exploit: This problem can be triggered via crafted queries +- Risk of system compromise: No +- Solution: Upgrade to a non-affected version + +An issue has been found in PowerDNS DNSDist allowing a remote attacker to craft a DNS query with trailing data such that the addition of a record by dnsdist, for example an OPT record when adding EDNS Client Subnet, might result in the trailing data being smuggled to the backend as a valid record while not seen by dnsdist. +This is an issue when dnsdist is deployed as a DNS Firewall and used to filter some records that should not be received by the backend. +This issue occurs only when either the 'useClientSubnet' or the experimental 'addXPF' parameters are used when declaring a new backend. + +This issue has been assigned CVE-2018-14663 by Red Hat. + +PowerDNS DNSDist up to and including 1.3.2 is affected. + +We would like to thank Richard Gibson for finding and subsequently reporting this issue. diff --git a/pdns/dnsparser.cc b/pdns/dnsparser.cc index e8888111365e..4103df84f565 100644 --- a/pdns/dnsparser.cc +++ b/pdns/dnsparser.cc @@ -811,7 +811,7 @@ uint32_t getDNSPacketLength(const char* packet, size_t length) } try { - const dnsheader* dh = (const dnsheader*) packet; + const dnsheader* dh = reinterpret_cast(packet); DNSPacketMangler dpm(const_cast(packet), length); const uint16_t qdcount = ntohs(dh->qdcount); diff --git a/pdns/test-dnsdist_cc.cc b/pdns/test-dnsdist_cc.cc index 9996b6edf8c8..c579f40c11bf 100644 --- a/pdns/test-dnsdist_cc.cc +++ b/pdns/test-dnsdist_cc.cc @@ -23,9 +23,12 @@ #define BOOST_TEST_NO_MAIN #include +#include #include "dnsdist.hh" #include "dnsdist-ecs.hh" +#include "dnsdist-xpf.hh" + #include "dolog.hh" #include "dnsname.hh" #include "dnsparser.hh" @@ -33,17 +36,13 @@ #include "ednsoptions.hh" #include "ednscookies.hh" #include "ednssubnet.hh" -#include BOOST_AUTO_TEST_SUITE(test_dnsdist_cc) -bool g_syslog{true}; -bool g_verbose{true}; - static const uint16_t ECSSourcePrefixV4 = 24; static const uint16_t ECSSourcePrefixV6 = 56; -static void validateQuery(const char * packet, size_t packetSize, bool hasEdns=true) +static void validateQuery(const char * packet, size_t packetSize, bool hasEdns=true, bool hasXPF=false) { MOADNSParser mdp(true, packet, packetSize); @@ -52,7 +51,8 @@ static void validateQuery(const char * packet, size_t packetSize, bool hasEdns=t BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1); BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0); BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0); - BOOST_CHECK_EQUAL(mdp.d_header.arcount, (hasEdns ? 1 : 0)); + uint16_t expectedARCount = 0 + (hasEdns ? 1 : 0) + (hasXPF ? 1 : 0); + BOOST_CHECK_EQUAL(mdp.d_header.arcount, expectedARCount); } static void validateECS(const char* packet, size_t packetSize, const ComboAddress& expected) @@ -89,6 +89,124 @@ static void validateResponse(const char * packet, size_t packetSize, bool hasEdn BOOST_CHECK_EQUAL(mdp.d_header.arcount, (hasEdns ? 1 : 0) + additionalCount); } +BOOST_AUTO_TEST_CASE(test_addXPF) +{ + static const uint16_t xpfOptionCode = 65422; + + struct timespec queryTime; + gettime(&queryTime); // does not have to be accurate ("realTime") in tests + ComboAddress remote; + DNSName name("www.powerdns.com."); + + vector query; + DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + const uint16_t len = query.size(); + vector queryWithXPF; + + { + char packet[1500]; + memcpy(packet, query.data(), query.size()); + + /* large enough packet */ + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + auto dh = reinterpret_cast(packet); + DNSQuestion dq(&qname, qtype, QClass::IN, qname.wirelength(), &remote, &remote, dh, sizeof(packet), query.size(), false, &queryTime); + + BOOST_CHECK(addXPF(dq, xpfOptionCode, false)); + BOOST_CHECK(static_cast(dq.len) > query.size()); + validateQuery(packet, dq.len, false, true); + queryWithXPF.resize(dq.len); + memcpy(queryWithXPF.data(), packet, dq.len); + } + + { + char packet[1500]; + memcpy(packet, query.data(), query.size()); + + /* not large enough packet */ + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + auto dh = reinterpret_cast(packet); + DNSQuestion dq(&qname, qtype, QClass::IN, qname.wirelength(), &remote, &remote, dh, sizeof(packet), query.size(), false, &queryTime); + dq.size = dq.len; + + BOOST_CHECK(!addXPF(dq, xpfOptionCode, false)); + BOOST_CHECK_EQUAL(static_cast(dq.len), query.size()); + validateQuery(packet, dq.len, false, false); + } + + { + char packet[1500]; + memcpy(packet, query.data(), query.size()); + + /* packet with trailing data (overriding it) */ + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + auto dh = reinterpret_cast(packet); + DNSQuestion dq(&qname, qtype, QClass::IN, qname.wirelength(), &remote, &remote, dh, sizeof(packet), query.size(), false, &queryTime); + + /* add trailing data */ + const size_t trailingDataSize = 10; + /* Making sure we have enough room to allow for fake trailing data */ + BOOST_REQUIRE(sizeof(packet) > dq.len && (sizeof(packet) - dq.len) > trailingDataSize); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + packet[dq.len + idx] = 'A'; + } + dq.len += trailingDataSize; + + BOOST_CHECK(addXPF(dq, xpfOptionCode, false)); + BOOST_CHECK_EQUAL(static_cast(dq.len), queryWithXPF.size()); + BOOST_CHECK_EQUAL(memcmp(queryWithXPF.data(), packet, queryWithXPF.size()), 0); + validateQuery(packet, dq.len, false, true); + } + + { + char packet[1500]; + memcpy(packet, query.data(), query.size()); + + /* packet with trailing data (preserving trailing data) */ + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + auto dh = reinterpret_cast(packet); + DNSQuestion dq(&qname, qtype, QClass::IN, qname.wirelength(), &remote, &remote, dh, sizeof(packet), query.size(), false, &queryTime); + + /* add trailing data */ + const size_t trailingDataSize = 10; + /* Making sure we have enough room to allow for fake trailing data */ + BOOST_REQUIRE(sizeof(packet) > dq.len && (sizeof(packet) - dq.len) > trailingDataSize); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + packet[dq.len + idx] = 'A'; + } + dq.len += trailingDataSize; + + BOOST_CHECK(addXPF(dq, xpfOptionCode, true)); + BOOST_CHECK(static_cast(dq.len) > queryWithXPF.size()); + BOOST_CHECK_EQUAL(memcmp(queryWithXPF.data(), packet, queryWithXPF.size()), 0); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + BOOST_CHECK_EQUAL(packet[queryWithXPF.size() + idx], 'A'); + } + validateQuery(packet, dq.len, false, true); + } +} + BOOST_AUTO_TEST_CASE(addECSWithoutEDNS) { bool ednsAdded = false; @@ -109,31 +227,84 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNS) unsigned int consumed = 0; uint16_t qtype; - DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed); + DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed); BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption)); - BOOST_CHECK((size_t) len > query.size()); + BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false)); + BOOST_CHECK(static_cast(len) > query.size()); BOOST_CHECK_EQUAL(ednsAdded, true); BOOST_CHECK_EQUAL(ecsAdded, false); validateQuery(packet, len); validateECS(packet, len, remote); + vector queryWithEDNS; + queryWithEDNS.resize(len); + memcpy(queryWithEDNS.data(), packet, len); /* not large enough packet */ ednsAdded = false; ecsAdded = false; consumed = 0; len = query.size(); - qname = DNSName(reinterpret_cast(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed); + qname = DNSName(reinterpret_cast(query.data()), len, sizeof(dnsheader), false, &qtype, nullptr, &consumed); BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption)); - BOOST_CHECK_EQUAL((size_t) len, query.size()); + BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false)); + BOOST_CHECK_EQUAL(static_cast(len), query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); validateQuery(reinterpret_cast(query.data()), len, false); + + /* packet with trailing data (overriding it) */ + memcpy(packet, query.data(), query.size()); + ednsAdded = false; + ecsAdded = false; + consumed = 0; + len = query.size(); + qname = DNSName(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + /* add trailing data */ + const size_t trailingDataSize = 10; + /* Making sure we have enough room to allow for fake trailing data */ + BOOST_REQUIRE(sizeof(packet) > len && (sizeof(packet) - len) > trailingDataSize); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + packet[len + idx] = 'A'; + } + len += trailingDataSize; + BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false)); + BOOST_REQUIRE_EQUAL(static_cast(len), queryWithEDNS.size()); + BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet, queryWithEDNS.size()), 0); + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, len); + + /* packet with trailing data (preserving trailing data) */ + memcpy(packet, query.data(), query.size()); + ednsAdded = false; + ecsAdded = false; + consumed = 0; + len = query.size(); + qname = DNSName(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + /* add trailing data */ + /* Making sure we have enough room to allow for fake trailing data */ + BOOST_REQUIRE(sizeof(packet) > len && (sizeof(packet) - len) > trailingDataSize); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + packet[len + idx] = 'A'; + } + len += trailingDataSize; + BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, true)); + BOOST_REQUIRE_EQUAL(static_cast(len), queryWithEDNS.size() + trailingDataSize); + BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet, queryWithEDNS.size()), 0); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + BOOST_CHECK_EQUAL(packet[queryWithEDNS.size() + idx], 'A'); + } + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, len); } BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed) @@ -164,7 +335,7 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed) BOOST_CHECK(!parseEDNSOptions(dq)); /* And now we add our own ECS */ - BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded)); + BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded, false)); BOOST_CHECK_GT(static_cast(dq.len), query.size()); BOOST_CHECK_EQUAL(ednsAdded, true); BOOST_CHECK_EQUAL(ecsAdded, false); @@ -181,7 +352,7 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed) BOOST_CHECK(qclass == QClass::IN); DNSQuestion dq2(&qname, qtype, qclass, consumed, nullptr, &remote, reinterpret_cast(query.data()), query.size(), query.size(), false, nullptr); - BOOST_CHECK(!handleEDNSClientSubnet(dq2, &ednsAdded, &ecsAdded)); + BOOST_CHECK(!handleEDNSClientSubnet(dq2, &ednsAdded, &ecsAdded, false)); BOOST_CHECK_EQUAL(static_cast(dq2.len), query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); @@ -213,7 +384,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) { BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption)); + BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false)); BOOST_CHECK((size_t) len > query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, true); @@ -229,7 +400,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) { BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption)); + BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false)); BOOST_CHECK_EQUAL((size_t) len, query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); @@ -265,7 +436,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) { BOOST_CHECK(parseEDNSOptions(dq)); /* And now we add our own ECS */ - BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded)); + BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded, false)); BOOST_CHECK_GT(static_cast(dq.len), query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, true); @@ -282,7 +453,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) { BOOST_CHECK(qclass == QClass::IN); DNSQuestion dq2(&qname, qtype, qclass, consumed, nullptr, &remote, reinterpret_cast(query.data()), query.size(), query.size(), false, nullptr); - BOOST_CHECK(!handleEDNSClientSubnet(dq2, &ednsAdded, &ecsAdded)); + BOOST_CHECK(!handleEDNSClientSubnet(dq2, &ednsAdded, &ecsAdded, false)); BOOST_CHECK_EQUAL(static_cast(dq2.len), query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); @@ -320,7 +491,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) { BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption)); + BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption, false)); BOOST_CHECK_EQUAL((size_t) len, query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); @@ -365,7 +536,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed) { BOOST_CHECK(parseEDNSOptions(dq)); /* And now we add our own ECS */ - BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded)); + BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded, false)); BOOST_CHECK_EQUAL(static_cast(dq.len), query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); @@ -404,7 +575,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSmaller) { BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption)); + BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption, false)); BOOST_CHECK((size_t) len < query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); @@ -443,7 +614,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithLarger) { BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption)); + BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption, false)); BOOST_CHECK((size_t) len > query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); @@ -459,7 +630,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithLarger) { BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption)); + BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption, false)); BOOST_CHECK_EQUAL((size_t) len, query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); BOOST_CHECK_EQUAL(ecsAdded, false); diff --git a/pdns/test-dnsdistpacketcache_cc.cc b/pdns/test-dnsdistpacketcache_cc.cc index 281949f78ea8..5b1fbc0f346f 100644 --- a/pdns/test-dnsdistpacketcache_cc.cc +++ b/pdns/test-dnsdistpacketcache_cc.cc @@ -24,6 +24,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) { size_t counter=0; size_t skipped=0; ComboAddress remote; + bool dnssecOK = false; try { for(counter = 0; counter < 100000; ++counter) { DNSName a=DNSName(std::to_string(counter))+DNSName(" hello"); @@ -50,13 +51,13 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) { boost::optional subnet; auto dh = reinterpret_cast(query.data()); DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime); - bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet); + bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet, dnssecOK); BOOST_CHECK_EQUAL(found, false); BOOST_CHECK(!subnet); - PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none); + PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), dnssecOK, 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, subnet, 0, true); + found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, dnssecOK, 0, true); if (found == true) { BOOST_CHECK_EQUAL(responseBufSize, responseLen); int match = memcmp(responseBuf, response.data(), responseLen); @@ -83,7 +84,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) { uint32_t key = 0; boost::optional subnet; DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, (struct dnsheader*) query.data(), query.size(), query.size(), false, &queryTime); - bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet); + bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet, dnssecOK); if (found == true) { PC.expungeByName(a); deleted++; @@ -106,7 +107,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) { char response[4096]; uint16_t responseSize = sizeof(response); DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, (struct dnsheader*) query.data(), len, query.size(), false, &queryTime); - if(PC.get(dq, a.wirelength(), pwQ.getHeader()->id, response, &responseSize, &key, subnet)) { + if(PC.get(dq, a.wirelength(), pwQ.getHeader()->id, response, &responseSize, &key, subnet, dnssecOK)) { matches++; } } @@ -128,6 +129,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) { gettime(&queryTime); // does not have to be accurate ("realTime") in tests ComboAddress remote; + bool dnssecOK = false; try { DNSName a = DNSName("servfail"); BOOST_CHECK_EQUAL(DNSName(a.toString()), a); @@ -152,19 +154,19 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) { boost::optional subnet; auto dh = reinterpret_cast(query.data()); DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime); - bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet); + bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet, dnssecOK); BOOST_CHECK_EQUAL(found, false); BOOST_CHECK(!subnet); // Insert with failure-TTL of 0 (-> should not enter cache). - PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional(0)); - found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true); + PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), dnssecOK, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional(0)); + found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, dnssecOK, 0, true); BOOST_CHECK_EQUAL(found, false); BOOST_CHECK(!subnet); // Insert with failure-TTL non-zero (-> should enter cache). - PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional(300)); - found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true); + PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), dnssecOK, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional(300)); + found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, dnssecOK, 0, true); BOOST_CHECK_EQUAL(found, true); BOOST_CHECK(!subnet); } @@ -182,6 +184,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL) { gettime(&queryTime); // does not have to be accurate ("realTime") in tests ComboAddress remote; + bool dnssecOK = false; try { DNSName name("nodata"); vector query; @@ -209,18 +212,18 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL) { boost::optional subnet; auto dh = reinterpret_cast(query.data()); DNSQuestion dq(&name, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime); - bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet); + bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet, dnssecOK); BOOST_CHECK_EQUAL(found, false); BOOST_CHECK(!subnet); - PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), name, QType::A, QClass::IN, reinterpret_cast(response.data()), responseLen, false, RCode::NoError, boost::none); - found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true); + PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), dnssecOK, name, QType::A, QClass::IN, reinterpret_cast(response.data()), responseLen, false, RCode::NoError, boost::none); + found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, dnssecOK, 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, subnet, 0, true); + found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, dnssecOK, 0, true); BOOST_CHECK_EQUAL(found, false); BOOST_CHECK(!subnet); } @@ -238,6 +241,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) { gettime(&queryTime); // does not have to be accurate ("realTime") in tests ComboAddress remote; + bool dnssecOK = false; try { DNSName name("nxdomain"); vector query; @@ -265,18 +269,18 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) { boost::optional subnet; auto dh = reinterpret_cast(query.data()); DNSQuestion dq(&name, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime); - bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet); + bool found = PC.get(dq, name.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet, dnssecOK); BOOST_CHECK_EQUAL(found, false); BOOST_CHECK(!subnet); - PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), name, QType::A, QClass::IN, reinterpret_cast(response.data()), responseLen, false, RCode::NXDomain, boost::none); - found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, 0, true); + PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), dnssecOK, name, QType::A, QClass::IN, reinterpret_cast(response.data()), responseLen, false, RCode::NXDomain, boost::none); + found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, dnssecOK, 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, subnet, 0, true); + found = PC.get(dq, name.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, subnet, dnssecOK, 0, true); BOOST_CHECK_EQUAL(found, false); BOOST_CHECK(!subnet); } @@ -294,6 +298,7 @@ static void *threadMangler(void* off) gettime(&queryTime); // does not have to be accurate ("realTime") in tests try { ComboAddress remote; + bool dnssecOK = false; unsigned int offset=(unsigned int)(unsigned long)off; for(unsigned int counter=0; counter < 100000; ++counter) { DNSName a=DNSName("hello ")+DNSName(std::to_string(counter+offset)); @@ -318,9 +323,9 @@ static void *threadMangler(void* off) boost::optional subnet; auto dh = reinterpret_cast(query.data()); DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, dh, query.size(), query.size(), false, &queryTime); - PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet); + PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet, dnssecOK); - PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none); + PC.insert(key, subnet, *(getFlagsFromDNSHeader(dh)), dnssecOK, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none); } } catch(PDNSException& e) { @@ -334,6 +339,7 @@ AtomicCounter g_missing; static void *threadReader(void* off) { + bool dnssecOK = false; struct timespec queryTime; gettime(&queryTime); // does not have to be accurate ("realTime") in tests try @@ -352,7 +358,7 @@ static void *threadReader(void* off) uint32_t key = 0; boost::optional subnet; DNSQuestion dq(&a, QType::A, QClass::IN, 0, &remote, &remote, (struct dnsheader*) query.data(), query.size(), query.size(), false, &queryTime); - bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet); + bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key, subnet, dnssecOK); if (!found) { g_missing++; } @@ -402,6 +408,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) { uint32_t key; uint32_t secondKey; boost::optional subnetOut; + bool dnssecOK = false; /* lookup for a query with an ECS value of 10.0.118.46/32, insert a corresponding response */ @@ -423,7 +430,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) { struct timespec queryTime; gettime(&queryTime); DNSQuestion dq(&qname, QType::AAAA, QClass::IN, 0, &remote, &remote, pwQ.getHeader(), query.size(), query.size(), false, &queryTime); - bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut); + bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut, dnssecOK); BOOST_CHECK_EQUAL(found, false); BOOST_REQUIRE(subnetOut); BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString()); @@ -439,10 +446,10 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) { pwR.addOpt(512, 0, 0, ednsOptions); pwR.commit(); - PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), qname, qtype, QClass::IN, reinterpret_cast(response.data()), response.size(), false, RCode::NoError, boost::none); + PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), dnssecOK, qname, qtype, QClass::IN, reinterpret_cast(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); + found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut, dnssecOK); BOOST_CHECK_EQUAL(found, true); BOOST_REQUIRE(subnetOut); BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString()); @@ -468,7 +475,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) { struct timespec queryTime; gettime(&queryTime); DNSQuestion dq(&qname, QType::AAAA, QClass::IN, 0, &remote, &remote, pwQ.getHeader(), query.size(), query.size(), false, &queryTime); - bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &secondKey, subnetOut); + bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &secondKey, subnetOut, dnssecOK); BOOST_CHECK_EQUAL(found, false); BOOST_CHECK_EQUAL(secondKey, key); BOOST_REQUIRE(subnetOut); @@ -477,4 +484,58 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) { } } +BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) { + 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; + boost::optional subnetOut; + + /* lookup for a query with DNSSEC OK, + insert a corresponding response with DO set, + check that it doesn't match without DO, but does with it */ + { + vector query; + DNSPacketWriter pwQ(query, qname, qtype, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + pwQ.getHeader()->id = qid; + pwQ.addOpt(512, 0, EDNS_HEADER_FLAG_DO); + 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, 0, &remote, &remote, pwQ.getHeader(), query.size(), query.size(), false, &queryTime); + bool found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut, true); + BOOST_CHECK_EQUAL(found, false); + + vector 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, EDNS_HEADER_FLAG_DO); + pwR.commit(); + + PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), /* DNSSEC OK is set */ true, qname, qtype, QClass::IN, reinterpret_cast(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, false); + BOOST_CHECK_EQUAL(found, false); + + found = PC.get(dq, qname.wirelength(), 0, responseBuf, &responseBufSize, &key, subnetOut, true); + BOOST_CHECK_EQUAL(found, true); + } + +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/pdns/test-dnsparser_cc.cc b/pdns/test-dnsparser_cc.cc index e9f248cb6b31..4a2f6ddfd403 100644 --- a/pdns/test-dnsparser_cc.cc +++ b/pdns/test-dnsparser_cc.cc @@ -417,6 +417,32 @@ BOOST_AUTO_TEST_CASE(test_getDNSPacketLength) { BOOST_CHECK_EQUAL(result, realSize); } + { + /* truncated packet, should return the full size */ + vector packet; + DNSPacketWriter pwR(packet, name, QType::A, QClass::IN, 0); + pwR.getHeader()->qr = 1; + pwR.commit(); + + pwR.startRecord(name, QType::A, 255, QClass::IN, DNSResourceRecord::ANSWER); + pwR.xfrIP(v4.sin4.sin_addr.s_addr); + pwR.commit(); + + pwR.startRecord(name, QType::SOA, 257, QClass::IN, DNSResourceRecord::AUTHORITY); + pwR.commit(); + + pwR.startRecord(name, QType::A, 256, QClass::IN, DNSResourceRecord::ADDITIONAL); + pwR.xfrIP(v4.sin4.sin_addr.s_addr); + pwR.commit(); + + pwR.addOpt(4096, 0, 0); + pwR.commit(); + + size_t fakeSize = packet.size()-1; + auto result = getDNSPacketLength(reinterpret_cast(packet.data()), fakeSize); + BOOST_CHECK_EQUAL(result, fakeSize); + } + } BOOST_AUTO_TEST_CASE(test_getRecordsOfTypeCount) { diff --git a/regression-tests.dnsdist/test_Caching.py b/regression-tests.dnsdist/test_Caching.py index b242b1766e1f..bddf2cdd9302 100644 --- a/regression-tests.dnsdist/test_Caching.py +++ b/regression-tests.dnsdist/test_Caching.py @@ -77,6 +77,64 @@ def testCached(self): self.assertEquals(total, 1) + def testDOCached(self): + """ + Cache: Served from cache, query has DO bit set + + 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-do.cache.tests.powerdns.com.' + query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, want_dnssec=True) + 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 testSkipCache(self): """ Cache: SkipCacheAction