]> granicus.if.org Git - pdns/commitdiff
dnsdist: Remove ECS option from response's OPT RR when necessary
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 10 Mar 2016 08:31:33 +0000 (09:31 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 25 Mar 2016 10:44:36 +0000 (11:44 +0100)
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.

14 files changed:
pdns/dnsdist-ecs.cc
pdns/dnsdist-ecs.hh
pdns/dnsdist-tcp.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/ednscookies.cc [new symlink]
pdns/dnsdistdist/ednscookies.hh [new symlink]
pdns/ednscookies.cc [new file with mode: 0644]
pdns/ednscookies.hh [new file with mode: 0644]
pdns/test-dnsdist_cc.cc
regression-tests.dnsdist/clientsubnetoption.py
regression-tests.dnsdist/cookiesoption.py [new file with mode: 0644]
regression-tests.dnsdist/test_EdnsClientSubnet.py

index cf9fdaaf9c64d1f7ae19f5fa09cb88465b93e18f..0bc87ae85ada0c09be59da39844d2f3f60ed4a3c 100644 (file)
@@ -87,7 +87,6 @@ int rewriteResponseWithoutEDNS(const char * packet, const size_t len, vector<uin
     pr.xfrBlob(blob);
     pw.xfrBlob(blob);
   }
-
   /* consume AR, looking for OPT */
   for (idx = 0; idx < arcount; idx++) {
     rrname = pr.getName();
@@ -106,7 +105,7 @@ int rewriteResponseWithoutEDNS(const char * packet, const size_t len, vector<uin
   return 0;
 }
 
-int locateEDNSOptRR(const char * packet, const size_t len, const char ** optStart, size_t * optLen, bool * last)
+int locateEDNSOptRR(char * packet, const size_t len, char ** optStart, size_t * optLen, bool * last)
 {
   assert(packet != NULL);
   assert(optStart != NULL);
@@ -286,12 +285,13 @@ static void replaceEDNSClientSubnetOption(char * const packet, const size_t pack
   }
 }
 
-void handleEDNSClientSubnet(char * const packet, const size_t packetSize, const unsigned int consumed, uint16_t * const len, string& largerPacket, bool * const ednsAdded, const ComboAddress& remote)
+void handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, string& largerPacket, bool* const ednsAdded, bool* const ecsAdded, const ComboAddress& remote)
 {
   assert(packet != NULL);
   assert(len != NULL);
   assert(consumed <= (size_t) *len);
   assert(ednsAdded != NULL);
+  assert(ecsAdded != NULL);
   unsigned char * optRDLen = NULL;
   size_t remaining = 0;
         
@@ -334,6 +334,7 @@ void handleEDNSClientSubnet(char * const packet, const size_t packetSize, const
         largerPacket.append(packet, *len);
         largerPacket.append(ECSOption);
       }
+      *ecsAdded = true;
     }
   }
   else {
@@ -363,3 +364,156 @@ void handleEDNSClientSubnet(char * const packet, const size_t packetSize, const
     }
   }
 }
+
+static int removeEDNSOptionFromOptions(unsigned char* optionsStart, const uint16_t optionsLen, const uint16_t optionCodeToRemove, uint16_t* newOptionsLen)
+{
+  unsigned char* p = optionsStart;
+  const unsigned char* end = p + optionsLen;
+  while ((p + 4) <= end) {
+    unsigned char* optionBegin = p;
+    const uint16_t optionCode = 0x100*p[0] + p[1];
+    p += sizeof(optionCode);
+    const uint16_t optionLen = 0x100*p[0] + p[1];
+    p += sizeof(optionLen);
+    if ((p + optionLen) > 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<uint8_t>& 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<uint8_t> 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;
+}
index c28985c4f3dffe0e241f2be77ae10811fcc8ff24..94930b10f8893c32bcc8dc21776ee4669726f22c 100644 (file)
@@ -1,6 +1,8 @@
 #pragma once
 
 int rewriteResponseWithoutEDNS(const char * packet, size_t len, vector<uint8_t>& 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<uint8_t>& newContent);
index a6cca5dfbe95bc64de438d78abb8499b968fcba2..81954a30fc12ba51d847706288e33295ac4a9d42 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "dnsdist.hh"
 #include "dnsdist-ecs.hh"
+#include "ednsoptions.hh"
 #include "dolog.hh"
 #include "lock.hh"
 #include <thread>
@@ -187,7 +188,6 @@ void* tcpClientThread(int pipefd)
     string poolname;
     string largerQuery;
     vector<uint8_t> rewrittenResponse;
-    bool ednsAdded = false;
     shared_ptr<DownstreamState> 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;
         }
 
index 091a3557eb6d01cf6c3782c4a04b9bfd9c48b8e9..d658cb630dc9038d2ab3f406f7884222f789d576 100644 (file)
@@ -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<uint8_t>& 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<uint8_t>& 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<char*>(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<char*>(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<char*>(rewrittenResponse.data());
+          }
+          else {
+            warnlog("Error rewriting content");
+          }
         }
       }
     }
@@ -354,7 +383,7 @@ void* responderThread(std::shared_ptr<DownstreamState> 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
index 6e92f515164c66d2ee5ce1d9597270d61273643a..224f750f8bf31646edb69c0831c65f168b580954 100644 (file)
@@ -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<NetmaskTree<DynBlock> >& localDynBlock, LocalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSAction> > > >& localRulactions, blockfilter_t blockFilter, DNSQuestion& dq, string& poolname, int* delayMsec, const struct timespec& now);
 bool processResponse(LocalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > >& localRespRulactions, DNSQuestion& dq);
-bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, const DNSName& qname, uint16_t origFlags, bool ednsAdded, std::vector<uint8_t>& 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<uint8_t>& rewrittenResponse, uint16_t addRoom);
 void restoreFlags(struct dnsheader* dh, uint16_t origFlags);
 
 #ifdef HAVE_DNSCRYPT
index 91035f791bee85590ebb168659d36ebefcbaeca1..54d74f30907cfb771225106f50bf4aa7f0c2d8fd 100644 (file)
@@ -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 (symlink)
index 0000000..e8c4721
--- /dev/null
@@ -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 (symlink)
index 0000000..f29d488
--- /dev/null
@@ -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 (file)
index 0000000..e48f6df
--- /dev/null
@@ -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 (file)
index 0000000..50812fd
--- /dev/null
@@ -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
index 5e470f9aa4d90272ce57ce9d08d757c33d580378..fe8a8ff36061c77cb37343739c06055c7e466b48 100644 (file)
@@ -33,6 +33,7 @@
 #include "dnsparser.hh"
 #include "dnswriter.hh"
 #include "ednsoptions.hh"
+#include "ednscookies.hh"
 #include "ednssubnet.hh"
 #include <unistd.h>
 
@@ -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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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();
index 4097f4cedcb55f12ce86c4997ba0232b076b5a2c..5306985288fa3f82d8a4c3914bf57a77bd8f618f 100644 (file)
@@ -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 (file)
index 0000000..60c55db
--- /dev/null
@@ -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
+
index 3b4386a8ca0cd7089cfb0c14244a327905a1db7d..959d4996b665aa856b583a16471ad426740d88ca 100644 (file)
@@ -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)