From: Remi Gacogne Date: Thu, 10 Mar 2016 08:31:33 +0000 (+0100) Subject: dnsdist: Remove ECS option from response's OPT RR when necessary X-Git-Tag: dnsdist-1.0.0-beta1~61^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F3627%2Fhead;p=thirdparty%2Fpdns.git dnsdist: Remove ECS option from response's OPT RR when necessary If we added an ECS option to a query already having EDNS, we need to remove the ECS option sent back by the server if any, otherwise this might confuse the original client. --- diff --git a/pdns/dnsdist-ecs.cc b/pdns/dnsdist-ecs.cc index cf9fdaaf9c..0bc87ae85a 100644 --- a/pdns/dnsdist-ecs.cc +++ b/pdns/dnsdist-ecs.cc @@ -87,7 +87,6 @@ int rewriteResponseWithoutEDNS(const char * packet, const size_t len, vector end) { + return EINVAL; + } + if (optionCode == optionCodeToRemove) { + if (p + optionLen < end) { + /* move remaining options over the removed one, + if any */ + memmove(optionBegin, p + optionLen, end - (p + optionLen)); + } + *newOptionsLen = optionsLen - (sizeof(optionCode) + sizeof(optionLen) + optionLen); + return 0; + } + p += optionLen; + } + return ENOENT; +} + +int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove) +{ + /* we need at least: + root label (1), type (2), class (2), ttl (4) + rdlen (2)*/ + if (*optLen < 11) { + return EINVAL; + } + const unsigned char* end = (const unsigned char*) optStart + *optLen; + unsigned char* p = (unsigned char*) optStart + 9; + unsigned char* rdLenPtr = p; + uint16_t rdLen = (0x100*p[0] + p[1]); + p += sizeof(rdLen); + if (p + rdLen != end) { + return EINVAL; + } + uint16_t newRdLen = 0; + int res = removeEDNSOptionFromOptions(p, rdLen, optionCodeToRemove, &newRdLen); + if (res != 0) { + return res; + } + *optLen -= (rdLen - newRdLen); + rdLenPtr[0] = newRdLen / 0x100; + rdLenPtr[1] = newRdLen % 0x100; + return 0; +} + +int rewriteResponseWithoutEDNSOption(const char * packet, const size_t len, const uint16_t optionCodeToSkip, vector& newContent) +{ + assert(packet != NULL); + assert(len >= sizeof(dnsheader)); + const struct dnsheader* dh = (const struct dnsheader*) packet; + + if (ntohs(dh->arcount) == 0) + return ENOENT; + + if (ntohs(dh->qdcount) == 0) + return ENOENT; + + vector content(len - sizeof(dnsheader)); + copy(packet + sizeof(dnsheader), packet + len, content.begin()); + PacketReader pr(content); + + size_t idx = 0; + DNSName rrname; + uint16_t qdcount = ntohs(dh->qdcount); + uint16_t ancount = ntohs(dh->ancount); + uint16_t nscount = ntohs(dh->nscount); + uint16_t arcount = ntohs(dh->arcount); + uint16_t rrtype; + uint16_t rrclass; + string blob; + struct dnsrecordheader ah; + + rrname = pr.getName(); + rrtype = pr.get16BitInt(); + rrclass = pr.get16BitInt(); + + DNSPacketWriter pw(newContent, rrname, rrtype, rrclass, dh->opcode); + pw.getHeader()->id=dh->id; + pw.getHeader()->qr=dh->qr; + pw.getHeader()->aa=dh->aa; + pw.getHeader()->tc=dh->tc; + pw.getHeader()->rd=dh->rd; + pw.getHeader()->ra=dh->ra; + pw.getHeader()->ad=dh->ad; + pw.getHeader()->cd=dh->cd; + pw.getHeader()->rcode=dh->rcode; + + /* consume remaining qd if any */ + if (qdcount > 1) { + for(idx = 1; idx < qdcount; idx++) { + rrname = pr.getName(); + rrtype = pr.get16BitInt(); + rrclass = pr.get16BitInt(); + (void) rrtype; + (void) rrclass; + } + } + + /* copy AN and NS */ + for (idx = 0; idx < ancount; idx++) { + rrname = pr.getName(); + pr.getDnsrecordheader(ah); + + pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true); + pr.xfrBlob(blob); + pw.xfrBlob(blob); + } + + for (idx = 0; idx < nscount; idx++) { + rrname = pr.getName(); + pr.getDnsrecordheader(ah); + + pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true); + pr.xfrBlob(blob); + pw.xfrBlob(blob); + } + + /* consume AR, looking for OPT */ + for (idx = 0; idx < arcount; idx++) { + rrname = pr.getName(); + pr.getDnsrecordheader(ah); + + if (ah.d_type != QType::OPT) { + pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true); + pr.xfrBlob(blob); + pw.xfrBlob(blob); + } else { + pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, false); + pr.xfrBlob(blob); + uint16_t rdLen = blob.length(); + removeEDNSOptionFromOptions((unsigned char*)blob.c_str(), rdLen, optionCodeToSkip, &rdLen); + /* xfrBlob(string, size) completely ignores size.. */ + if (rdLen > 0) { + blob.resize((size_t)rdLen); + pw.xfrBlob(blob); + } else { + pw.commit(); + } + } + } + pw.commit(); + + return 0; +} diff --git a/pdns/dnsdist-ecs.hh b/pdns/dnsdist-ecs.hh index c28985c4f3..94930b10f8 100644 --- a/pdns/dnsdist-ecs.hh +++ b/pdns/dnsdist-ecs.hh @@ -1,6 +1,8 @@ #pragma once int rewriteResponseWithoutEDNS(const char * packet, size_t len, vector& newContent); -int locateEDNSOptRR(const char * packet, size_t len, const char ** optStart, size_t * optLen, bool * last); -void handleEDNSClientSubnet(char * packet, size_t packetSize, unsigned int consumed, uint16_t * len, string& largerPacket, bool * ednsAdded, const ComboAddress& remote); +int locateEDNSOptRR(char * packet, size_t len, char ** optStart, size_t * optLen, bool * last); +void handleEDNSClientSubnet(char * packet, size_t packetSize, unsigned int consumed, uint16_t * len, string& largerPacket, bool* ednsAdded, bool* ecsAdded, const ComboAddress& remote); 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& newContent); diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index a6cca5dfbe..81954a30fc 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -22,6 +22,7 @@ #include "dnsdist.hh" #include "dnsdist-ecs.hh" +#include "ednsoptions.hh" #include "dolog.hh" #include "lock.hh" #include @@ -187,7 +188,6 @@ void* tcpClientThread(int pipefd) string poolname; string largerQuery; vector rewrittenResponse; - bool ednsAdded = false; shared_ptr ds; if (!setNonBlocking(ci.fd)) goto drop; @@ -208,6 +208,8 @@ void* tcpClientThread(int pipefd) break; } + bool ednsAdded = false; + bool ecsAdded = false; /* if the query is small, allocate a bit more memory to be able to spoof the content, or to add ECS without allocating a new buffer */ @@ -294,7 +296,7 @@ void* tcpClientThread(int pipefd) if (ds && ds->useECS) { uint16_t newLen = dq.len; - handleEDNSClientSubnet(queryBuffer, dq.size, consumed, &newLen, largerQuery, &ednsAdded, ci.remote); + handleEDNSClientSubnet(queryBuffer, dq.size, consumed, &newLen, largerQuery, &ednsAdded, &ecsAdded, ci.remote); if (largerQuery.empty() == false) { query = largerQuery.c_str(); dq.len = largerQuery.size(); @@ -408,7 +410,7 @@ void* tcpClientThread(int pipefd) break; } - if (!fixUpResponse(&response, &responseLen, &responseSize, qname, origFlags, ednsAdded, rewrittenResponse, addRoom)) { + if (!fixUpResponse(&response, &responseLen, &responseSize, qname, origFlags, ednsAdded, ecsAdded, rewrittenResponse, addRoom)) { break; } diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 091a3557eb..d658cb630d 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -203,7 +203,7 @@ void restoreFlags(struct dnsheader* dh, uint16_t origFlags) *flags |= origFlags; } -bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, const DNSName& qname, uint16_t origFlags, bool ednsAdded, std::vector& rewrittenResponse, uint16_t addRoom) +bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, std::vector& rewrittenResponse, uint16_t addRoom) { struct dnsheader* dh = (struct dnsheader*) *response; @@ -220,33 +220,62 @@ bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, restoreFlags(dh, origFlags); - if (ednsAdded) { - const char * optStart = NULL; + if (ednsAdded || ecsAdded) { + char * optStart = NULL; size_t optLen = 0; bool last = false; int res = locateEDNSOptRR(*response, *responseLen, &optStart, &optLen, &last); if (res == 0) { - if (last) { - /* simply remove the last AR */ - *responseLen -= optLen; - uint16_t arcount = ntohs(dh->arcount); - arcount--; - dh->arcount = htons(arcount); + if (ednsAdded) { + /* we added the entire OPT RR, + therefore we need to remove it entirely */ + if (last) { + /* simply remove the last AR */ + *responseLen -= optLen; + uint16_t arcount = ntohs(dh->arcount); + arcount--; + dh->arcount = htons(arcount); + } + else { + /* Removing an intermediary RR could lead to compression error */ + if (rewriteResponseWithoutEDNS(*response, *responseLen, rewrittenResponse) == 0) { + *responseLen = rewrittenResponse.size(); + if (addRoom && (UINT16_MAX - *responseLen) > addRoom) { + rewrittenResponse.reserve(*responseLen + addRoom); + } + *responseSize = rewrittenResponse.capacity(); + *response = reinterpret_cast(rewrittenResponse.data()); + } + else { + warnlog("Error rewriting content"); + } + } } else { - /* Removing an intermediary RR could lead to compression error */ - if (rewriteResponseWithoutEDNS(*response, *responseLen, rewrittenResponse) == 0) { - *responseLen = rewrittenResponse.size(); - if (addRoom && (UINT16_MAX - *responseLen) > addRoom) { - rewrittenResponse.reserve(*responseLen + addRoom); - } - *responseSize = rewrittenResponse.capacity(); - *response = reinterpret_cast(rewrittenResponse.data()); + /* the OPT RR was already present, but without ECS, + we need to remove the ECS option if any */ + if (last) { + /* nothing after the OPT RR, we can simply remove the + ECS option */ + size_t existingOptLen = optLen; + removeEDNSOptionFromOPT(optStart, &optLen, EDNSOptionCode::ECS); + *responseLen -= (existingOptLen - optLen); } else { - warnlog("Error rewriting content"); + /* Removing an intermediary RR could lead to compression error */ + if (rewriteResponseWithoutEDNSOption(*response, *responseLen, EDNSOptionCode::ECS, rewrittenResponse) == 0) { + *responseLen = rewrittenResponse.size(); + if (addRoom && (UINT16_MAX - *responseLen) > addRoom) { + rewrittenResponse.reserve(*responseLen + addRoom); + } + *responseSize = rewrittenResponse.capacity(); + *response = reinterpret_cast(rewrittenResponse.data()); + } + else { + warnlog("Error rewriting content"); + } } } } @@ -354,7 +383,7 @@ void* responderThread(std::shared_ptr state) addRoom = DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE; } #endif - if (!fixUpResponse(&response, &responseLen, &responseSize, ids->qname, ids->origFlags, ids->ednsAdded, rewrittenResponse, addRoom)) { + if (!fixUpResponse(&response, &responseLen, &responseSize, ids->qname, ids->origFlags, ids->ednsAdded, ids->ecsAdded, rewrittenResponse, addRoom)) { continue; } @@ -912,8 +941,9 @@ try } bool ednsAdded = false; + bool ecsAdded = false; if (ss && ss->useECS) { - handleEDNSClientSubnet(query, dq.size, consumed, &dq.len, largerQuery, &(ednsAdded), remote); + handleEDNSClientSubnet(query, dq.size, consumed, &dq.len, largerQuery, &(ednsAdded), &(ecsAdded), remote); } uint32_t cacheKey = 0; @@ -968,11 +998,11 @@ try ids->origDest.sin4.sin_family=0; ids->delayMsec = delayMsec; ids->origFlags = origFlags; - ids->ednsAdded = false; ids->cacheKey = cacheKey; ids->skipCache = dq.skipCache; ids->packetCache = packetCache; ids->ednsAdded = ednsAdded; + ids->ecsAdded = ecsAdded; #ifdef HAVE_DNSCRYPT ids->dnsCryptQuery = dnsCryptQuery; #endif diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 6e92f51516..224f750f8b 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -224,6 +224,7 @@ struct IDState uint16_t origFlags; // 2 int delayMsec; bool ednsAdded{false}; + bool ecsAdded{false}; bool skipCache{false}; }; @@ -530,7 +531,7 @@ void resetLuaSideEffect(); // reset to indeterminate state bool responseContentMatches(const char* response, const uint16_t responseLen, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& remote); bool processQuery(LocalStateHolder >& localDynBlock, LocalStateHolder, std::shared_ptr > > >& localRulactions, blockfilter_t blockFilter, DNSQuestion& dq, string& poolname, int* delayMsec, const struct timespec& now); bool processResponse(LocalStateHolder, std::shared_ptr > > >& localRespRulactions, DNSQuestion& dq); -bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, const DNSName& qname, uint16_t origFlags, bool ednsAdded, std::vector& rewrittenResponse, uint16_t addRoom); +bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, std::vector& rewrittenResponse, uint16_t addRoom); void restoreFlags(struct dnsheader* dh, uint16_t origFlags); #ifdef HAVE_DNSCRYPT diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 91035f791b..54d74f3090 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -68,6 +68,7 @@ dnsdist_SOURCES = \ dnswriter.cc dnswriter.hh \ dolog.hh \ ednsoptions.cc ednsoptions.hh \ + ednscookies.cc ednscookies.hh \ ednssubnet.cc ednssubnet.hh \ iputils.cc iputils.hh \ lock.hh \ @@ -128,6 +129,7 @@ testrunner_SOURCES = \ dnswriter.cc dnswriter.hh \ dolog.hh \ ednsoptions.cc ednsoptions.hh \ + ednscookies.cc ednscookies.hh \ ednssubnet.cc ednssubnet.hh \ iputils.cc iputils.hh \ misc.cc misc.hh \ diff --git a/pdns/dnsdistdist/ednscookies.cc b/pdns/dnsdistdist/ednscookies.cc new file mode 120000 index 0000000000..e8c4721bbc --- /dev/null +++ b/pdns/dnsdistdist/ednscookies.cc @@ -0,0 +1 @@ +../ednscookies.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/ednscookies.hh b/pdns/dnsdistdist/ednscookies.hh new file mode 120000 index 0000000000..f29d488b64 --- /dev/null +++ b/pdns/dnsdistdist/ednscookies.hh @@ -0,0 +1 @@ +../ednscookies.hh \ No newline at end of file diff --git a/pdns/ednscookies.cc b/pdns/ednscookies.cc new file mode 100644 index 0000000000..e48f6df70d --- /dev/null +++ b/pdns/ednscookies.cc @@ -0,0 +1,51 @@ +/* + PowerDNS Versatile Database Driven Nameserver + Copyright (C) 2011 - 2016 Netherlabs Computer Consulting BV + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation + + Additionally, the license of this program contains a special + exception which allows to distribute the program in binary form when + it is linked against OpenSSL. + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ednscookies.hh" + +bool getEDNSCookiesOptFromString(const string& option, EDNSCookiesOpt* eco) +{ + return getEDNSCookiesOptFromString(option.c_str(), option.length(), eco); +} + +bool getEDNSCookiesOptFromString(const char* option, unsigned int len, EDNSCookiesOpt* eco) +{ + if(len != 8 && len < 16) + return false; + eco->client = string(option, 8); + if (len > 8) { + eco->server = string(option + 8, len - 8); + } + return true; +} + +string makeEDNSCookiesOptString(const EDNSCookiesOpt& eco) +{ + string ret; + if (eco.client.length() != 8) + return ret; + if (eco.server.length() != 0 && (eco.server.length() < 8 || eco.server.length() > 32)) + return ret; + ret.assign(eco.client); + if (eco.server.length() != 0) + ret.append(eco.server); + return ret; +} diff --git a/pdns/ednscookies.hh b/pdns/ednscookies.hh new file mode 100644 index 0000000000..50812fd993 --- /dev/null +++ b/pdns/ednscookies.hh @@ -0,0 +1,36 @@ +/* + PowerDNS Versatile Database Driven Nameserver + Copyright (C) 2011 - 2016 Netherlabs Computer Consulting BV + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation + + Additionally, the license of this program contains a special + exception which allows to distribute the program in binary form when + it is linked against OpenSSL. + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef PDNS_EDNSCOOKIES_HH +#define PDNS_EDNSCOOKIES_HH + +#include "namespaces.hh" + +struct EDNSCookiesOpt +{ + string client; + string server; +}; + +bool getEDNSCookiesOptFromString(const char* option, unsigned int len, EDNSCookiesOpt* eco); +bool getEDNSCookiesOptFromString(const string& option, EDNSCookiesOpt* eco); +string makeEDNSCookiesOptString(const EDNSCookiesOpt& eco); +#endif diff --git a/pdns/test-dnsdist_cc.cc b/pdns/test-dnsdist_cc.cc index 5e470f9aa4..fe8a8ff360 100644 --- a/pdns/test-dnsdist_cc.cc +++ b/pdns/test-dnsdist_cc.cc @@ -33,6 +33,7 @@ #include "dnsparser.hh" #include "dnswriter.hh" #include "ednsoptions.hh" +#include "ednscookies.hh" #include "ednssubnet.hh" #include @@ -54,7 +55,7 @@ static void validateQuery(const char * packet, size_t packetSize) BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1); } -static void validateResponse(const char * packet, size_t packetSize, bool hasEdns) +static void validateResponse(const char * packet, size_t packetSize, bool hasEdns, uint8_t additionalCount=0) { MOADNSParser mdp(packet, packetSize); @@ -64,13 +65,14 @@ static void validateResponse(const char * packet, size_t packetSize, bool hasEdn BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1); BOOST_CHECK_EQUAL(mdp.d_header.ancount, 1); BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0); - BOOST_CHECK_EQUAL(mdp.d_header.arcount, hasEdns ? 1 : 0); + BOOST_CHECK_EQUAL(mdp.d_header.arcount, (hasEdns ? 1 : 0) + additionalCount); } BOOST_AUTO_TEST_CASE(addECSWithoutEDNS) { string largerPacket; bool ednsAdded = false; + bool ecsAdded = false; ComboAddress remote; DNSName name("www.powerdns.com."); @@ -89,10 +91,11 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNS) BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, remote); + handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, &ecsAdded, remote); BOOST_CHECK((size_t) len > query.size()); BOOST_CHECK_EQUAL(largerPacket.size(), 0); BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, false); validateQuery(packet, len); /* not large enought packet */ @@ -102,16 +105,18 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNS) BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - handleEDNSClientSubnet((char*) query.data(), query.size(), consumed, &len, largerPacket, &ednsAdded, remote); + handleEDNSClientSubnet((char*) query.data(), query.size(), consumed, &len, largerPacket, &ednsAdded, &ecsAdded, remote); BOOST_CHECK_EQUAL((size_t) len, query.size()); BOOST_CHECK(largerPacket.size() > query.size()); BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, false); validateQuery(largerPacket.c_str(), largerPacket.size()); } BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) { string largerPacket; bool ednsAdded = false; + bool ecsAdded = false; ComboAddress remote; DNSName name("www.powerdns.com."); @@ -132,10 +137,11 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) { BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, remote); + handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, &ecsAdded, remote); BOOST_CHECK((size_t) len > query.size()); BOOST_CHECK_EQUAL(largerPacket.size(), 0); BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, true); validateQuery(packet, len); /* not large enought packet */ @@ -145,16 +151,18 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) { BOOST_CHECK_EQUAL(qname, name); BOOST_CHECK(qtype == QType::A); - handleEDNSClientSubnet((char*) query.data(), query.size(), consumed, &len, largerPacket, &ednsAdded, remote); + handleEDNSClientSubnet((char*) query.data(), query.size(), consumed, &len, largerPacket, &ednsAdded, &ecsAdded, remote); BOOST_CHECK_EQUAL((size_t) len, query.size()); BOOST_CHECK(largerPacket.size() > query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, true); validateQuery(largerPacket.c_str(), largerPacket.size()); } BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) { string largerPacket; bool ednsAdded = false; + bool ecsAdded = false; ComboAddress remote("192.168.1.25"); DNSName name("www.powerdns.com."); ComboAddress origRemote("127.0.0.1"); @@ -182,16 +190,18 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) { BOOST_CHECK(qtype == QType::A); g_ECSOverride = true; - handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, remote); + handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, &ecsAdded, remote); BOOST_CHECK_EQUAL((size_t) len, query.size()); BOOST_CHECK_EQUAL(largerPacket.size(), 0); BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); validateQuery(packet, len); } BOOST_AUTO_TEST_CASE(replaceECSWithSmaller) { string largerPacket; bool ednsAdded = false; + bool ecsAdded = false; ComboAddress remote("192.168.1.25"); DNSName name("www.powerdns.com."); ComboAddress origRemote("127.0.0.1"); @@ -219,16 +229,18 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSmaller) { BOOST_CHECK(qtype == QType::A); g_ECSOverride = true; - handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, remote); + handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, &ecsAdded, remote); BOOST_CHECK((size_t) len < query.size()); BOOST_CHECK_EQUAL(largerPacket.size(), 0); BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); validateQuery(packet, len); } BOOST_AUTO_TEST_CASE(replaceECSWithLarger) { string largerPacket; bool ednsAdded = false; + bool ecsAdded = false; ComboAddress remote("192.168.1.25"); DNSName name("www.powerdns.com."); ComboAddress origRemote("127.0.0.1"); @@ -256,10 +268,11 @@ BOOST_AUTO_TEST_CASE(replaceECSWithLarger) { BOOST_CHECK(qtype == QType::A); g_ECSOverride = true; - handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, remote); + handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, largerPacket, &ednsAdded, &ecsAdded, remote); BOOST_CHECK((size_t) len > query.size()); BOOST_CHECK_EQUAL(largerPacket.size(), 0); BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); validateQuery(packet, len); /* not large enought packet */ @@ -270,14 +283,15 @@ BOOST_AUTO_TEST_CASE(replaceECSWithLarger) { BOOST_CHECK(qtype == QType::A); g_ECSOverride = true; - handleEDNSClientSubnet((char*) query.data(), query.size(), consumed, &len, largerPacket, &ednsAdded, remote); + handleEDNSClientSubnet((char*) query.data(), query.size(), consumed, &len, largerPacket, &ednsAdded, &ecsAdded, remote); BOOST_CHECK_EQUAL((size_t) len, query.size()); BOOST_CHECK(largerPacket.size() > query.size()); BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); validateQuery(largerPacket.c_str(), largerPacket.size()); } -BOOST_AUTO_TEST_CASE(removeEDNS) { +BOOST_AUTO_TEST_CASE(removeEDNSWhenFirst) { DNSName name("www.powerdns.com."); vector response; @@ -287,10 +301,44 @@ BOOST_AUTO_TEST_CASE(removeEDNS) { pw.xfr32BitInt(0x01020304); pw.addOpt(512, 0, 0); pw.commit(); + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); vector newResponse; int res = rewriteResponseWithoutEDNS((const char *) response.data(), response.size(), newResponse); + BOOST_CHECK_EQUAL(res, 0); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */; + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize); + + validateResponse((const char *) newResponse.data(), newResponse.size(), false, 1); +} + +BOOST_AUTO_TEST_CASE(removeEDNSWhenIntermediary) { + DNSName name("www.powerdns.com."); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + pw.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + pw.addOpt(512, 0, 0); + pw.commit(); + pw.startRecord(DNSName("yetanother.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + vector newResponse; + int res = rewriteResponseWithoutEDNS((const char *) response.data(), response.size(), newResponse); BOOST_CHECK_EQUAL(res, 0); unsigned int consumed = 0; @@ -301,7 +349,410 @@ BOOST_AUTO_TEST_CASE(removeEDNS) { size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */; BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize); - validateResponse((const char *) newResponse.data(), newResponse.size(), false); + validateResponse((const char *) newResponse.data(), newResponse.size(), false, 2); +} + +BOOST_AUTO_TEST_CASE(removeEDNSWhenLast) { + DNSName name("www.powerdns.com."); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + pw.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + pw.addOpt(512, 0, 0); + pw.commit(); + + vector newResponse; + int res = rewriteResponseWithoutEDNS((const char *) response.data(), response.size(), newResponse); + + BOOST_CHECK_EQUAL(res, 0); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */; + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize); + + validateResponse((const char *) newResponse.data(), newResponse.size(), false, 1); +} + +BOOST_AUTO_TEST_CASE(removeECSWhenOnlyOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + DNSPacketWriter::optvect_t opts; + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + char * optStart = NULL; + size_t optLen = 0; + bool last = false; + + int res = locateEDNSOptRR((char *) response.data(), response.size(), &optStart, &optLen, &last); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(last, true); + + size_t responseLen = response.size(); + size_t existingOptLen = optLen; + BOOST_CHECK(existingOptLen < responseLen); + res = removeEDNSOptionFromOPT(optStart, &optLen, EDNSOptionCode::ECS); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4)); + responseLen -= (existingOptLen - optLen); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse((const char *) response.data(), responseLen, true, 1); +} + +BOOST_AUTO_TEST_CASE(removeECSWhenFirstOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt; + cookiesOpt.client = string("deadbeef"); + cookiesOpt.server = string("deadbeef"); + string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt); + DNSPacketWriter::optvect_t opts; + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr)); + opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + char * optStart = NULL; + size_t optLen = 0; + bool last = false; + + int res = locateEDNSOptRR((char *) response.data(), response.size(), &optStart, &optLen, &last); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(last, true); + + size_t responseLen = response.size(); + size_t existingOptLen = optLen; + BOOST_CHECK(existingOptLen < responseLen); + res = removeEDNSOptionFromOPT(optStart, &optLen, EDNSOptionCode::ECS); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4)); + responseLen -= (existingOptLen - optLen); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse((const char *) response.data(), responseLen, true, 1); +} + +BOOST_AUTO_TEST_CASE(removeECSWhenIntermediaryOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + + EDNSCookiesOpt cookiesOpt; + cookiesOpt.client = string("deadbeef"); + cookiesOpt.server = string("deadbeef"); + string cookiesOptionStr1 = makeEDNSCookiesOptString(cookiesOpt); + string cookiesOptionStr2 = makeEDNSCookiesOptString(cookiesOpt); + + DNSPacketWriter::optvect_t opts; + opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr1)); + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr)); + opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr2)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + char * optStart = NULL; + size_t optLen = 0; + bool last = false; + + int res = locateEDNSOptRR((char *) response.data(), response.size(), &optStart, &optLen, &last); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(last, true); + + size_t responseLen = response.size(); + size_t existingOptLen = optLen; + BOOST_CHECK(existingOptLen < responseLen); + res = removeEDNSOptionFromOPT(optStart, &optLen, EDNSOptionCode::ECS); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4)); + responseLen -= (existingOptLen - optLen); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse((const char *) response.data(), responseLen, true, 1); +} + +BOOST_AUTO_TEST_CASE(removeECSWhenLastOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + EDNSCookiesOpt cookiesOpt; + cookiesOpt.client = string("deadbeef"); + cookiesOpt.server = string("deadbeef"); + string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt); + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + DNSPacketWriter::optvect_t opts; + opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr)); + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + char * optStart = NULL; + size_t optLen = 0; + bool last = false; + + int res = locateEDNSOptRR((char *) response.data(), response.size(), &optStart, &optLen, &last); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(last, true); + + size_t responseLen = response.size(); + size_t existingOptLen = optLen; + BOOST_CHECK(existingOptLen < responseLen); + res = removeEDNSOptionFromOPT(optStart, &optLen, EDNSOptionCode::ECS); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4)); + responseLen -= (existingOptLen - optLen); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse((const char *) response.data(), responseLen, true, 1); +} + +BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenOnlyOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + DNSPacketWriter::optvect_t opts; + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + vector newResponse; + int res = rewriteResponseWithoutEDNSOption((const char *) response.data(), response.size(), EDNSOptionCode::ECS, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4)); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse((const char *) newResponse.data(), newResponse.size(), true, 1); +} + +BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenFirstOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt; + cookiesOpt.client = string("deadbeef"); + cookiesOpt.server = string("deadbeef"); + string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt); + DNSPacketWriter::optvect_t opts; + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr)); + opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + vector newResponse; + int res = rewriteResponseWithoutEDNSOption((const char *) response.data(), response.size(), EDNSOptionCode::ECS, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4)); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse((const char *) newResponse.data(), newResponse.size(), true, 1); +} + +BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenIntermediaryOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt; + cookiesOpt.client = string("deadbeef"); + cookiesOpt.server = string("deadbeef"); + string cookiesOptionStr1 = makeEDNSCookiesOptString(cookiesOpt); + string cookiesOptionStr2 = makeEDNSCookiesOptString(cookiesOpt); + DNSPacketWriter::optvect_t opts; + opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr1)); + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr)); + opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr2)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + vector newResponse; + int res = rewriteResponseWithoutEDNSOption((const char *) response.data(), response.size(), EDNSOptionCode::ECS, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4)); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse((const char *) newResponse.data(), newResponse.size(), true, 1); +} + +BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenLastOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + vector response; + DNSPacketWriter pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt; + cookiesOpt.client = string("deadbeef"); + cookiesOpt.server = string("deadbeef"); + string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt); + DNSPacketWriter::optvect_t opts; + opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr)); + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + vector newResponse; + int res = rewriteResponseWithoutEDNSOption((const char *) response.data(), response.size(), EDNSOptionCode::ECS, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4)); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, NULL, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse((const char *) newResponse.data(), newResponse.size(), true, 1); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/regression-tests.dnsdist/clientsubnetoption.py b/regression-tests.dnsdist/clientsubnetoption.py index 4097f4cedc..5306985288 100644 --- a/regression-tests.dnsdist/clientsubnetoption.py +++ b/regression-tests.dnsdist/clientsubnetoption.py @@ -141,7 +141,7 @@ class ClientSubnetOption(dns.edns.Option): test = test[-(mask_bits // 8):] format = "!HBB%ds" % (mask_bits // 8) - data = struct.pack(format, self.family, self.mask, 0, test) + data = struct.pack(format, self.family, self.mask, self.scope, test) file.write(data) def from_wire(cls, otype, wire, current, olen): diff --git a/regression-tests.dnsdist/cookiesoption.py b/regression-tests.dnsdist/cookiesoption.py new file mode 100644 index 0000000000..60c55db219 --- /dev/null +++ b/regression-tests.dnsdist/cookiesoption.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python2 + +import dns +import dns.edns +import dns.flags +import dns.message +import dns.query + +class CookiesOption(dns.edns.Option): + """Implementation of draft-ietf-dnsop-cookies-09. + """ + + def __init__(self, client, server): + super(CookiesOption, self).__init__(10) + + if len(client) != 8: + raise Exception('invalid client cookie length') + + if server is not None and len(server) != 0 and (len(server) < 8 or len(server) > 32): + raise Exception('invalid server cookie length') + + self.client = client + self.server = server + + def to_wire(self, file): + """Create EDNS packet as definied in draft-ietf-dnsop-cookies-09.""" + + file.write(self.client) + if self.server and len(self.server) > 0: + file.write(self.server) + + def from_wire(cls, otype, wire, current, olen): + """Read EDNS packet as defined in draft-ietf-dnsop-cookies-09. + + Returns: + An instance of CookiesOption based on the EDNS packet + """ + + data = wire[current:current + olen] + if len(data) != 8 and (len(data) < 16 or len(data) > 40): + raise Exception('Invalid EDNS Cookies option') + + client = data[:8] + if len(data) > 8: + server = data[8:] + else: + server = None + + return cls(client, server) + + from_wire = classmethod(from_wire) + + def __repr__(self): + return '%s(%s, %s)' % ( + self.__class__.__name__, + self.client, + self.server + ) + + def __eq__(self, other): + if not isinstance(other, CookiesOption): + return False + if self.client != other.client: + return False + if self.server != other.server: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + +dns.edns._type_to_class[0x000A] = CookiesOption + diff --git a/regression-tests.dnsdist/test_EdnsClientSubnet.py b/regression-tests.dnsdist/test_EdnsClientSubnet.py index 3b4386a8ca..959d4996b6 100644 --- a/regression-tests.dnsdist/test_EdnsClientSubnet.py +++ b/regression-tests.dnsdist/test_EdnsClientSubnet.py @@ -2,6 +2,7 @@ import unittest import dns import clientsubnetoption +import cookiesoption from dnsdisttests import DNSDistTest class TestEdnsClientSubnetNoOverride(DNSDistTest): @@ -45,6 +46,8 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, -1) + self.assertEquals(len(receivedResponse.options), 0) (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) self.assertTrue(receivedQuery) @@ -52,6 +55,8 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, -1) + self.assertEquals(len(receivedResponse.options), 0) def testWithEDNSNoECS(self): """ @@ -82,6 +87,8 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) self.assertTrue(receivedQuery) @@ -89,6 +96,8 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) def testWithEDNSECS(self): """ @@ -117,6 +126,8 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest): receivedQuery.id = query.id self.assertEquals(query, receivedQuery) self.assertEquals(response, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) self.assertTrue(receivedQuery) @@ -124,6 +135,237 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest): receivedQuery.id = query.id self.assertEquals(query, receivedQuery) self.assertEquals(response, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) + + def testWithoutEDNSResponseWithECS(self): + """ + ECS: No existing EDNS (BE returning ECS) + + Send a query without EDNS, check that the query + received by the responder has the correct ECS value + and that the response received from dnsdist does not + have an EDNS pseudo-RR. + This time the response returned by the backend contains + an ECS option with scope set. + """ + name = 'withoutedns.bereturnsecs.ecs.tests.powerdns.com.' + ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24) + query = dns.message.make_query(name, 'A', 'IN') + expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512) + response = dns.message.make_response(expectedQuery) + ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, scope=24) + response.use_edns(edns=True, payload=4096, options=[ecsoResponse]) + expectedResponse = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + response.answer.append(rrset) + expectedResponse.answer.append(rrset) + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, -1) + self.assertEquals(len(receivedResponse.options), 0) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, -1) + self.assertEquals(len(receivedResponse.options), 0) + + def testWithEDNSNoECSResponseWithECS(self): + """ + ECS: Existing EDNS without ECS (BE returning only the ECS option) + + Send a query with EDNS but no ECS value. + Check that the query received by the responder + has a valid ECS value and that the response + received from dnsdist contains an EDNS pseudo-RR. + This time the response returned by the backend contains + an ECS option with scope set. + """ + name = 'withednsnoecs.bereturnsecs.ecs.tests.powerdns.com.' + ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24) + query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096) + expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[ecso]) + response = dns.message.make_response(expectedQuery) + ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, scope=24) + response.use_edns(edns=True, payload=4096, options=[ecsoResponse]) + expectedResponse = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + response.answer.append(rrset) + expectedResponse.answer.append(rrset) + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) + + def testWithEDNSNoECSResponseWithCookiesThenECS(self): + """ + ECS: Existing EDNS without ECS (BE returning Cookies then ECS options) + + Send a query with EDNS but no ECS value. + Check that the query received by the responder + has a valid ECS value and that the response + received from dnsdist contains an EDNS pseudo-RR. + This time the response returned by the backend contains + one cookies then one ECS option. + """ + name = 'withednsnoecs.bereturnscookiesthenecs.ecs.tests.powerdns.com.' + ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24) + query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096) + expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[ecso]) + response = dns.message.make_response(expectedQuery) + ecoResponse = cookiesoption.CookiesOption('deadbeef', 'deadbeef') + ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, scope=24) + response.use_edns(edns=True, payload=4096, options=[ecoResponse, ecsoResponse]) + expectedResponse = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + response.answer.append(rrset) + expectedResponse.answer.append(rrset) + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 1) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 1) + + def testWithEDNSNoECSResponseWithECSThenCookies(self): + """ + ECS: Existing EDNS without ECS (BE returning ECS then Cookies options) + + Send a query with EDNS but no ECS value. + Check that the query received by the responder + has a valid ECS value and that the response + received from dnsdist contains an EDNS pseudo-RR. + This time the response returned by the backend contains + one ECS then one Cookies option. + """ + name = 'withednsnoecs.bereturnsecsthencookies.ecs.tests.powerdns.com.' + ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24) + query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096) + expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[ecso]) + response = dns.message.make_response(expectedQuery) + ecoResponse = cookiesoption.CookiesOption('deadbeef', 'deadbeef') + ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, scope=24) + response.use_edns(edns=True, payload=4096, options=[ecsoResponse, ecoResponse]) + expectedResponse = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + response.answer.append(rrset) + expectedResponse.answer.append(rrset) + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 1) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 1) + + def testWithEDNSNoECSResponseWithCookiesThenECSThenCookies(self): + """ + ECS: Existing EDNS without ECS (BE returning Cookies, ECS then Cookies options) + + Send a query with EDNS but no ECS value. + Check that the query received by the responder + has a valid ECS value and that the response + received from dnsdist contains an EDNS pseudo-RR. + This time the response returned by the backend contains + one Cookies, one ECS then one Cookies option. + """ + name = 'withednsnoecs.bereturnscookiesecscookies.ecs.tests.powerdns.com.' + ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24) + query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096) + expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[ecso]) + response = dns.message.make_response(expectedQuery) + ecoResponse = cookiesoption.CookiesOption('deadbeef', 'deadbeef') + ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, scope=24) + response.use_edns(edns=True, payload=4096, options=[ecoResponse, ecsoResponse, ecoResponse]) + expectedResponse = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + response.answer.append(rrset) + expectedResponse.answer.append(rrset) + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 2) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEquals(expectedQuery, receivedQuery) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 2) + class TestEdnsClientSubnetOverride(DNSDistTest): """ @@ -168,6 +410,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, -1) + self.assertEquals(len(receivedResponse.options), 0) (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) self.assertTrue(receivedQuery) @@ -175,6 +419,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, -1) + self.assertEquals(len(receivedResponse.options), 0) def testWithEDNSNoECS(self): """ @@ -205,6 +451,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) self.assertTrue(receivedQuery) @@ -212,6 +460,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) def testWithEDNSShorterInitialECS(self): """ @@ -244,6 +494,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(response, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) self.assertTrue(receivedQuery) @@ -251,6 +503,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(response, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) def testWithEDNSLongerInitialECS(self): """ @@ -283,6 +537,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(response, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) self.assertTrue(receivedQuery) @@ -290,6 +546,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(response, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) def testWithEDNSSameSizeInitialECS(self): """ @@ -322,6 +580,8 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(response, receivedResponse) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0) (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) self.assertTrue(receivedQuery) @@ -329,8 +589,5 @@ class TestEdnsClientSubnetOverride(DNSDistTest): receivedQuery.id = expectedQuery.id self.assertEquals(expectedQuery, receivedQuery) self.assertEquals(response, receivedResponse) - - -if __name__ == '__main__': - unittest.main() - exit(0) + self.assertEquals(receivedResponse.edns, 0) + self.assertEquals(len(receivedResponse.options), 0)