]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth: Implement RFC 7872 and 9018 (COOKIES)
authorPieter Lexis <pieter.lexis@powerdns.com>
Mon, 12 Apr 2021 10:58:56 +0000 (12:58 +0200)
committerPieter Lexis <pieter.lexis@powerdns.com>
Mon, 20 Sep 2021 08:54:13 +0000 (10:54 +0200)
This implements the siphash-based interoperable DNS COOKIES defined in
RFC 9018 for the authoritative server. The EDNSCookieOpt struct has been
expanded to accomodate this and can now has constructors and functions
to check and generate a server cookie.

Cookies will only be sent out if the client sent a cookie and the
edns-cookie-secret setting is configures. The auth will respond with
EDNS+FORMERR when the client cookie is malformed, BADCOOKIE when the
client sent a server cookie we can't decode or is invalid and a normal
response with a cookie (either new or sent by the client) when the
cookie can be validated.

19 files changed:
.github/actions/spell-check/expect.txt
.not-formatted
docs/settings.rst
modules/remotebackend/Makefile.am
pdns/Makefile.am
pdns/common_startup.cc
pdns/dnspacket.cc
pdns/dnspacket.hh
pdns/ednscookies.cc
pdns/ednscookies.hh
pdns/packethandler.cc
pdns/recursordist/test-ednsoptions_cc.cc
pdns/test-dnsdist_cc.cc
pdns/test-ednscookie_cc.cc [new file with mode: 0644]
pdns/test-packetcache_hh.cc
regression-tests.auth-py/authtests.py
regression-tests.auth-py/cookiesoption.py [new symlink]
regression-tests.auth-py/test_Cookies.py [new file with mode: 0644]
regression-tests.dnsdist/cookiesoption.py

index c4cf3345a51181e38ae419eb4f7f0d724dd59d7b..1d9a6be31bb0c77df4d4079b96d9e879cbc3bce9 100644 (file)
@@ -1515,6 +1515,7 @@ sigs
 SIGUSR
 singlethreaded
 Sipek
+siphash
 sizeof
 Sjoerd
 slapd
index 6a2ad40a943a1b8a8fb6bd075a6b4bb36ebcca5b..8c3a8a86ff0c36ada7e9efa113e22abf7db7ad67 100644 (file)
 ./pdns/dynloader.cc
 ./pdns/dynmessenger.cc
 ./pdns/dynmessenger.hh
-./pdns/ednscookies.cc
 ./pdns/ednsoptions.cc
 ./pdns/ednsoptions.hh
 ./pdns/ednspadding.cc
index effef87d12a55bf95d5877334775c2c62b8b994c..eb6e8cdba5cd9c36dc02819fdf96050d754b9c6d 100644 (file)
@@ -622,6 +622,21 @@ ADDITIONAL section when sending a referral.
 Seconds to cache zone metadata from the database. A value of 0
 disables caching.
 
+.. _setting-edns-cookie-secret:
+
+``edns-cookie-secret``
+--------------------------
+
+.. versionadded:: 4.6.0
+
+-  String
+-  Default: (empty)
+
+When set, PowerDNS will respond with :rfc:`9018` EDNS Cookies to queries that have the EDNS0 Cookie option.
+PowerDNS will also respond with BADCOOKIE to clients that have no or a bad server cookie (section 5.2.3 and 5.2.4 of :rfc:`7873`).
+
+This setting MUST be 32 hexadecimal characters, as the siphash algorithm's key used to create the cookie requires a 128-bit key.
+
 .. _setting-edns-subnet-processing:
 
 ``edns-subnet-processing``
index f1aaa95e0c52e2d2a8729f2bf2f6e63a09626b18..1b5fc20025856dc2b86aabad2d5f2dc1ff836151 100644 (file)
@@ -8,6 +8,10 @@ if LUA
 AM_CPPFLAGS +=$(LUA_CFLAGS)
 endif
 
+if LIBSODIUM
+AM_CPPFLAGS +=$(LIBSODIUM_CFLAGS)
+endif
+
 AM_LDFLAGS = $(THREADFLAGS)
 
 JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
@@ -114,6 +118,7 @@ libtestremotebackend_la_SOURCES = \
        ../../pdns/dnsrecords.cc \
        ../../pdns/dnssecinfra.cc \
        ../../pdns/dnswriter.cc \
+       ../../pdns/ednscookies.cc \
        ../../pdns/ednsoptions.cc ../../pdns/ednsoptions.hh \
        ../../pdns/ednssubnet.cc \
        ../../pdns/iputils.cc \
@@ -150,6 +155,10 @@ libtestremotebackend_la_LDFLAGS = \
        $(AM_LDFLAGS) \
        $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS)
 
+if LIBSODIUM
+libtestremotebackend_la_LIBADD += $(LIBSODIUM_LIBS)
+endif
+
 if REMOTEBACKEND_ZEROMQ
 libtestremotebackend_la_LIBADD += $(LIBZMQ_LIBS)
 endif
index d8a7cafbe076d5ca627a601033e4857806664fb1..97cd1a281ac6bd052caeb57b5a656727332ff382 100644 (file)
@@ -221,6 +221,7 @@ pdns_server_SOURCES = \
        dynhandler.cc dynhandler.hh \
        dynlistener.cc dynlistener.hh \
        dynmessenger.hh \
+       ednscookies.cc ednscookies.hh \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
        histogram.hh \
@@ -349,6 +350,7 @@ pdnsutil_SOURCES = \
        dnssecsigner.cc \
        dnswriter.cc dnswriter.hh \
        dynlistener.cc \
+       ednscookies.cc \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc \
        ipcipher.cc ipcipher.hh \
@@ -1378,6 +1380,7 @@ testrunner_SOURCES = \
        test-dnsrecordcontent.cc \
        test-dnsrecords_cc.cc \
        test-dnswriter_cc.cc \
+       test-ednscookie_cc.cc \
        test-ipcrypt_cc.cc \
        test-iputils_hh.cc \
        test-ixfr_cc.cc \
index fabd808caef9968045a1858bbe00c48953081d23..a7c8bb30bad5a33075811a1d6fc1fd9b995b545b 100644 (file)
@@ -165,6 +165,8 @@ void declareArguments()
   ::arg().setSwitch("any-to-tcp","Answer ANY queries with tc=1, shunting to TCP")="yes";
   ::arg().setSwitch("edns-subnet-processing","If we should act on EDNS Subnet options")="no";
 
+  ::arg().set("edns-cookie-secret", "When set, set a server cookie in a response to a query with a Client cookie (in hex)")="";
+
   ::arg().setSwitch("webserver","Start a webserver for monitoring (api=yes also enables the HTTP listener)")="no";
   ::arg().setSwitch("webserver-print-arguments","If the webserver should print arguments")="no";
   ::arg().set("webserver-address","IP Address of webserver/API to listen on")="127.0.0.1";
@@ -572,6 +574,25 @@ void mainthread()
    g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
    g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
 
+   if (::arg()["edns-cookie-secret"].size() != 0) {
+     // User wants cookie processing
+#ifdef HAVE_CRYPTO_SHORTHASH // we can do siphash-based cookies
+     DNSPacket::s_doEDNSCookieProcessing = true;
+     try {
+       if (::arg()["edns-cookie-secret"].size() != 32) {
+         throw std::range_error("wrong size (" + std::to_string(::arg()["edns-cookie-secret"].size()) + "), must be 32");
+       }
+       DNSPacket::s_EDNSCookieKey = makeBytesFromHex(::arg()["edns-cookie-secret"]);
+     } catch(const std::range_error &e) {
+       g_log<<Logger::Error<<"edns-cookie-secret invalid: "<<e.what()<<endl;
+       exit(1);
+     }
+#else
+     g_log<<Logger::Error<<"Support for EDNS Cookies is not available because of missing cryptographic functions"<<endl;
+     exit(1);
+#endif
+   }
+
    PC.setTTL(::arg().asNum("cache-ttl"));
    PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
    QC.setMaxEntries(::arg().asNum("max-cache-entries"));
index ba687a0566715ab2ef90a1090f15d75a4cc517fc..67443945e89f41e523e5c941dcee54de7b49df06 100644 (file)
@@ -38,6 +38,7 @@
 #include "dns.hh"
 #include "dnsbackend.hh"
 #include "ednsoptions.hh"
+#include "ednscookies.hh"
 #include "pdnsexception.hh"
 #include "dnspacket.hh"
 #include "logger.hh"
@@ -52,6 +53,8 @@
 #include "shuffle.hh"
 
 bool DNSPacket::s_doEDNSSubnetProcessing;
+bool DNSPacket::s_doEDNSCookieProcessing;
+string DNSPacket::s_EDNSCookieKey;
 uint16_t DNSPacket::s_udpTruncationThreshold;
  
 DNSPacket::DNSPacket(bool isQuery): d_isQuery(isQuery)
@@ -236,7 +239,8 @@ void DNSPacket::setCompress(bool compress)
 
 bool DNSPacket::couldBeCached() const
 {
-  return !d_wantsnsid && qclass==QClass::IN && !d_havetsig;
+  return !d_wantsnsid && qclass==QClass::IN && !d_havetsig &&
+    !(d_haveednscookie && s_doEDNSCookieProcessing);
 }
 
 unsigned int DNSPacket::getMinTTL()
@@ -326,6 +330,12 @@ void DNSPacket::wrapup()
     optsize += d_eso.source.isIPv4() ? 4 : 16;
   }
 
+  if (d_haveednscookie) {
+    if (d_eco.isWellFormed()) {
+        optsize += 24;
+    }
+  }
+
   if (d_trc.d_algoName.countLabels())
   {
     // TSIG is not OPT, but we count it in optsize anyway
@@ -335,7 +345,7 @@ void DNSPacket::wrapup()
     static_assert(EVP_MAX_MD_SIZE <= 64, "EVP_MAX_MD_SIZE is overly huge on this system, please check");
   }
 
-  if(!d_rrs.empty() || !opts.empty() || d_haveednssubnet || d_haveednssection) {
+  if(!d_rrs.empty() || !opts.empty() || d_haveednssubnet || d_haveednssection || d_haveednscookie) {
     try {
       uint8_t maxScopeMask=0;
       for(pos=d_rrs.begin(); pos < d_rrs.end(); ++pos) {
@@ -366,6 +376,11 @@ void DNSPacket::wrapup()
         opts.push_back(make_pair(8, opt)); // 'EDNS SUBNET'
       }
 
+      if (d_haveednscookie && d_eco.isWellFormed()) {
+        d_eco.makeServerCookie(s_EDNSCookieKey, getRemote());
+        opts.push_back(make_pair(EDNSOptionCode::COOKIE, d_eco.makeOptString()));
+      }
+
       if(!opts.empty() || d_haveednssection || d_dnssecOk)
       {
         pw.addOpt(s_udpTruncationThreshold, d_ednsrcode, d_dnssecOk ? EDNSOpts::DNSSECOK : 0, opts);
@@ -428,8 +443,10 @@ std::unique_ptr<DNSPacket> DNSPacket::replyPacket() const
   r->d_wantsnsid = d_wantsnsid;
   r->d_dnssecOk = d_dnssecOk;
   r->d_eso = d_eso;
+  r->d_eco = d_eco;
   r->d_haveednssubnet = d_haveednssubnet;
   r->d_haveednssection = d_haveednssection;
+  r->d_haveednscookie = d_haveednscookie;
   r->d_ednsversion = 0;
   r->d_ednsrcode = 0;
 
@@ -567,6 +584,7 @@ try
   d_havetsig = mdp.getTSIGPos();
   d_haveednssubnet = false;
   d_haveednssection = false;
+  d_haveednscookie = false;
 
   if(getEDNSOpts(mdp, &edo)) {
     d_haveednssection=true;
@@ -589,6 +607,10 @@ try
           d_haveednssubnet=true;
         } 
       }
+      else if (s_doEDNSCookieProcessing && option.first == EDNSOptionCode::COOKIE) {
+        d_haveednscookie = true;
+        d_eco.makeFromString(option.second);
+      }
       else {
         // cerr<<"Have an option #"<<iter->first<<": "<<makeHexDump(iter->second)<<endl;
       }
@@ -656,6 +678,27 @@ bool DNSPacket::hasEDNS() const
   return d_haveednssection;
 }
 
+bool DNSPacket::hasEDNSCookie() const
+{
+  return d_haveednscookie;
+}
+
+bool DNSPacket::hasWellFormedEDNSCookie() const
+{
+  if (!d_haveednscookie) {
+    return false;
+  }
+  return d_eco.isWellFormed();
+}
+
+bool DNSPacket::hasValidEDNSCookie()
+{
+  if (!hasWellFormedEDNSCookie()) {
+    return false;
+  }
+  return d_eco.isValid(s_EDNSCookieKey, d_remote);
+}
+
 Netmask DNSPacket::getRealRemote() const
 {
   if(d_haveednssubnet)
index 7ad8579530d948841f95ff26554403f859c1fda6..9ee8895e3b6b078e05a6a155de025a36d2ced3be 100644 (file)
@@ -26,6 +26,7 @@
 #include <sys/types.h>
 #include "iputils.hh"
 #include "ednssubnet.hh"
+#include "ednscookies.hh"
 #include <unordered_set>
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -124,6 +125,9 @@ public:
   bool couldBeCached() const; //!< returns 0 if this query should bypass the packet cache
   bool hasEDNSSubnet() const;
   bool hasEDNS() const;
+  bool hasEDNSCookie() const;
+  bool hasWellFormedEDNSCookie() const;
+  bool hasValidEDNSCookie(); // Not const, some cookie params might be set
   uint8_t getEDNSVersion() const { return d_ednsversion; };
   void setEDNSRcode(uint16_t extRCode)
   {
@@ -167,6 +171,8 @@ public:
 
   static uint16_t s_udpTruncationThreshold; 
   static bool s_doEDNSSubnetProcessing;
+  static bool s_doEDNSCookieProcessing;
+  static string s_EDNSCookieKey;
 
 private:
   void pasteQ(const char *question, int length); //!< set the question of this packet, useful for crafting replies
@@ -179,6 +185,7 @@ private:
   std::unordered_set<size_t> d_dedup;
   string d_rawpacket; // this is where everything lives 8
   EDNSSubnetOpts d_eso;
+  EDNSCookiesOpt d_eco;
 
   int d_maxreplylen{0};
   int d_socket{-1}; // 4
@@ -192,6 +199,7 @@ private:
   bool d_tsigtimersonly{false};
   bool d_wantsnsid{false};
   bool d_haveednssubnet{false};
+  bool d_haveednscookie{false};
   bool d_haveednssection{false};
   bool d_isQuery;
 };
index 5e20819cefe6a7474975a16ffce6bb5528ccaf89..5805ff7a8dd90151186c13f958f106de32d6540a 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include "ednscookies.hh"
+#include "misc.hh"
 
-bool getEDNSCookiesOptFromString(const string& option, EDNSCookiesOpt* eco)
+#include "config.h"
+#ifdef HAVE_CRYPTO_SHORTHASH
+#include <sodium.h>
+#endif
+
+EDNSCookiesOpt::EDNSCookiesOpt(const std::string& option)
 {
-  return getEDNSCookiesOptFromString(option.c_str(), option.length(), eco);
+  getEDNSCookiesOptFromString(option.c_str(), option.length());
 }
 
-bool getEDNSCookiesOptFromString(const char* option, unsigned int len, EDNSCookiesOpt* eco)
+EDNSCookiesOpt::EDNSCookiesOpt(const char* option, unsigned int len)
 {
-  if(len != 8 && len < 16)
-    return false;
-  eco->client = string(option, 8);
-  if (len > 8) {
-    eco->server = string(option + 8, len - 8);
-  }
-  return true;
+  getEDNSCookiesOptFromString(option, len);
+}
+
+bool EDNSCookiesOpt::makeFromString(const std::string& option)
+{
+  getEDNSCookiesOptFromString(option.c_str(), option.length());
+  return isWellFormed();
+}
+
+bool EDNSCookiesOpt::makeFromString(const char* option, unsigned int len)
+{
+  getEDNSCookiesOptFromString(option, len);
+  return isWellFormed();
 }
 
-string makeEDNSCookiesOptString(const EDNSCookiesOpt& eco)
+string EDNSCookiesOpt::makeOptString() const
 {
   string ret;
-  if (eco.client.length() != 8)
+  if (client.length() != 8)
     return ret;
-  if (eco.server.length() != 0 && (eco.server.length() < 8 || eco.server.length() > 32))
+  if (server.length() != 0 && (server.length() < 8 || server.length() > 32))
     return ret;
-  ret.assign(eco.client);
-  if (eco.server.length() != 0)
-    ret.append(eco.server);
+  ret.assign(client);
+  if (server.length() != 0)
+    ret.append(server);
   return ret;
 }
+
+void EDNSCookiesOpt::getEDNSCookiesOptFromString(const char* option, unsigned int len)
+{
+  checked = false;
+  valid = false;
+  should_refresh = false;
+  client.clear();
+  server.clear();
+  if (len < 8)
+    return;
+  client = string(option, 8);
+  if (len > 8) {
+    server = string(option + 8, len - 8);
+  }
+}
+
+bool EDNSCookiesOpt::isValid(const string& secret, const ComboAddress& source)
+{
+#ifdef HAVE_CRYPTO_SHORTHASH
+  if (checked && valid) {
+    // Ignore the new check, we already validated it
+    // XXX this _might_ not be the best behaviour though...
+    return valid;
+  }
+  checked = true;
+  if (server.length() != 16 || client.length() != 8) {
+    return false;
+  }
+  if (server[0] != '\x01') {
+    // Version is not 1, can't verify
+    return false;
+  }
+  uint32_t ts;
+  memcpy(&ts, &server[4], sizeof(ts));
+  ts = ntohl(ts);
+  uint32_t now = static_cast<uint32_t>(time(nullptr));
+  if (rfc1982LessThan(now + 300, ts) && rfc1982LessThan(ts + 3600, now)) {
+    return false;
+  }
+  if (rfc1982LessThan(ts + 1800, now)) {
+    // RFC 9018 section 4.3:
+    //    The DNS server SHOULD generate a new Server Cookie at least if the
+    //     received Server Cookie from the client is more than half an hour old
+    should_refresh = true;
+  }
+  if (secret.length() != crypto_shorthash_KEYBYTES) {
+    // XXX should we throw std::range_error here?
+    return false;
+  }
+
+  string toHash = client + server.substr(0, 8) + source.toByteString();
+  string hashResult;
+  hashResult.resize(8);
+  crypto_shorthash(
+    reinterpret_cast<unsigned char*>(&hashResult[0]),
+    reinterpret_cast<const unsigned char*>(&toHash[0]),
+    toHash.length(),
+    reinterpret_cast<const unsigned char*>(&secret[0]));
+  valid = (server.substr(8) == hashResult);
+  return valid;
+#else
+  return false;
+#endif
+}
+
+bool EDNSCookiesOpt::makeServerCookie(const string& secret, const ComboAddress& source)
+{
+#ifdef HAVE_CRYPTO_SHORTHASH
+  if (valid && !should_refresh) {
+    return true;
+  }
+  checked = false;
+  valid = false;
+  should_refresh = false;
+
+  if (secret.length() != crypto_shorthash_KEYBYTES) {
+    return false;
+  }
+
+  server.clear();
+  server = "\x01"; // Version
+  server.resize(4, '\0'); // 3 reserved bytes
+  uint32_t now = htonl(static_cast<uint32_t>(time(nullptr)));
+  server += string(reinterpret_cast<const char*>(&now), 4);
+  server.resize(8);
+
+  string toHash = client;
+  toHash += server;
+  toHash += source.toByteString();
+  server.resize(16);
+  crypto_shorthash(
+    reinterpret_cast<unsigned char*>(&server[8]),
+    reinterpret_cast<const unsigned char*>(&toHash[0]),
+    toHash.length(),
+    reinterpret_cast<const unsigned char*>(&secret[0]));
+  checked = true;
+  valid = true;
+  should_refresh = false;
+  return true;
+#else
+  return false;
+#endif
+}
index 4e6f42e6cd6231dab706b5e724397d9a889b8ac1..14d4653eadd136b968f16df60874aaf695d1d46a 100644 (file)
  */
 #pragma once
 #include "namespaces.hh"
+#include "iputils.hh"
 
 struct EDNSCookiesOpt
 {
+  EDNSCookiesOpt(){};
+  EDNSCookiesOpt(const std::string& option);
+  EDNSCookiesOpt(const char* option, unsigned int len);
+
+  bool makeFromString(const std::string& option);
+  bool makeFromString(const char* option, unsigned int len);
+
+  size_t size() const
+  {
+    return server.size() + client.size();
+  }
+
+  bool isWellFormed() const
+  {
+    // RFC7873 section 5.2.2
+    //    In summary, valid cookie lengths are 8 and 16 to 40 inclusive.
+    return (
+      client.size() == 8 && (server.size() == 0 || (server.size() >= 8 && server.size() <= 32)));
+  }
+
+  bool isValid(const string& secret, const ComboAddress& source);
+  bool makeServerCookie(const string& secret, const ComboAddress& source);
+  string makeOptString() const;
+  string getServer() const
+  {
+    return server;
+  }
+  string getClient() const
+  {
+    return client;
+  }
+
+private:
+  // Whether or not we checked this cookie
+  bool checked{false};
+  // Whether or not the cookie is valid
+  bool valid{false};
+  // Whether or not he cookie will expire within 30 minutes
+  bool should_refresh{false};
+
+  // the client cookie
   string client;
+  // the server cookie
   string server;
-};
 
-bool getEDNSCookiesOptFromString(const char* option, unsigned int len, EDNSCookiesOpt* eco);
-bool getEDNSCookiesOptFromString(const string& option, EDNSCookiesOpt* eco);
-string makeEDNSCookiesOptString(const EDNSCookiesOpt& eco);
+  // Checks if the server cookie is correct
+  // 1. Checks the sizes of the client and server cookie
+  // 2. checks if the timestamp is still good (now - 3600 < ts < now + 300)
+  // 3. Whether or not the hash is correct
+  bool check(const string& secret, const ComboAddress& source);
+
+  void getEDNSCookiesOptFromString(const char* option, unsigned int len);
+};
index 6b39f11bc88e8a07be5a501ca0dfef53e260c8ee..8dd451153f4321ee773678f70c345ab1a04ebdca 100644 (file)
@@ -1290,12 +1290,26 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
     return nullptr;
   }
 
-  if (p.hasEDNS() && p.getEDNSVersion() > 0) {
-    r = p.replyPacket();
+  if (p.hasEDNS()) {
+    if(p.getEDNSVersion() > 0) {
+      r = p.replyPacket();
 
-    // PacketWriter::addOpt will take care of setting this correctly in the packet
-    r->setEDNSRcode(ERCode::BADVERS);
-    return r;
+      // PacketWriter::addOpt will take care of setting this correctly in the packet
+      r->setEDNSRcode(ERCode::BADVERS);
+      return r;
+    }
+    if (p.hasEDNSCookie()) {
+      if (!p.hasWellFormedEDNSCookie()) {
+        r = p.replyPacket();
+        r->setRcode(RCode::FormErr);
+        return r;
+      }
+      if (!p.hasValidEDNSCookie()) {
+        r = p.replyPacket();
+        r->setEDNSRcode(ERCode::BADCOOKIE);
+        return r;
+      }
+    }
   }
 
   if(p.d_havetsig) {
index cb07d377d652532f055e8b3078d31c6b6f2cecc2..6d36f14e334e25daed5bdf720471713e4fb73b65 100644 (file)
@@ -22,10 +22,8 @@ static void getRawQueryWithECSAndCookie(const DNSName& name, const Netmask& ecs,
   DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
   pw.commit();
 
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = clientCookie;
-  cookiesOpt.server = serverCookie;
-  string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt(clientCookie + serverCookie);
+  string cookiesOptionStr = cookiesOpt.makeOptString();
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = ecs;
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
index 2d49ad24398a98599f60c0e08f3f686a53e151c1..17944d0f50ffeea469568898b5f510dcb1da2052 100644 (file)
@@ -1120,10 +1120,8 @@ BOOST_AUTO_TEST_CASE(removeECSWhenFirstOption) {
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV6);
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
   GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
   opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
   opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
@@ -1173,11 +1171,9 @@ BOOST_AUTO_TEST_CASE(removeECSWhenIntermediaryOption) {
   ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
 
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  string cookiesOptionStr1 = makeEDNSCookiesOptString(cookiesOpt);
-  string cookiesOptionStr2 = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr1 = cookiesOpt.makeOptString();
+  string cookiesOptionStr2 = cookiesOpt.makeOptString();
 
   GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
   opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr1));
@@ -1225,10 +1221,8 @@ BOOST_AUTO_TEST_CASE(removeECSWhenLastOption) {
   pw.xfr32BitInt(0x01020304);
   pw.commit();
 
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
@@ -1313,10 +1307,8 @@ BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenFirstOption) {
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
   GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
   opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
   opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
@@ -1355,11 +1347,9 @@ BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenIntermediaryOption) {
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  string cookiesOptionStr1 = makeEDNSCookiesOptString(cookiesOpt);
-  string cookiesOptionStr2 = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr1 = cookiesOpt.makeOptString();
+  string cookiesOptionStr2 = cookiesOpt.makeOptString();
   GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
   opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr1));
   opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
@@ -1399,10 +1389,8 @@ BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenLastOption) {
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
   GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
   opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
   opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
@@ -1467,10 +1455,8 @@ BOOST_AUTO_TEST_CASE(test_getEDNSZ) {
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
   GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
   opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
   opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
@@ -1565,10 +1551,8 @@ BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
   string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
   GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
   opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
   opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
@@ -1774,11 +1758,9 @@ BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
   const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
   const size_t sizeOfECSContent = ecsOptionStr.size();
   const size_t sizeOfECSOption = /* option code */ 2 + /* option length */ 2 + sizeOfECSContent;
-  EDNSCookiesOpt cookiesOpt;
-  cookiesOpt.client = string("deadbeef");
-  cookiesOpt.server = string("deadbeef");
-  const string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
-  const size_t sizeOfCookieOption = /* option code */ 2 + /* option length */ 2 + cookiesOpt.client.size() + cookiesOpt.server.size();
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+  const size_t sizeOfCookieOption = /* option code */ 2 + /* option length */ 2 + cookiesOpt.size();
   /*
     GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
     opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
diff --git a/pdns/test-ednscookie_cc.cc b/pdns/test-ednscookie_cc.cc
new file mode 100644 (file)
index 0000000..f7d0653
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "ednscookies.hh"
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_SUITE(test_ednscookie)
+BOOST_AUTO_TEST_CASE(test_getEDNSCookiesOptFromString)
+{
+  string cookie("");
+  EDNSCookiesOpt eco(cookie);
+  // Length 0
+  BOOST_CHECK(!eco.isWellFormed());
+
+  // Too short
+  cookie = "\x12\x34\x56\x78\x90\xab\xcd";
+  BOOST_CHECK(!eco.makeFromString(cookie));
+
+  // Correct length client cookie
+  cookie = "\x12\x34\x56\x78\x90\xab\xcd\xef";
+  BOOST_CHECK(eco.makeFromString(cookie));
+
+  // Too short server cookie
+  cookie = "\x12\x34\x56\x78\x90\xab\xcd\xef\x01";
+  BOOST_CHECK(!eco.makeFromString(cookie));
+
+  cookie = "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd";
+  BOOST_CHECK(!eco.makeFromString(cookie));
+
+  // Have server cookie of correct length
+  cookie = "\x12\x34\x56\x78\x90\xab\xcd\xef";
+  cookie += cookie; // size 16
+  BOOST_CHECK(eco.makeFromString(cookie));
+
+  cookie += cookie; // size 32
+  BOOST_CHECK(eco.makeFromString(cookie));
+
+  cookie += "\x12\x34\x56\x78\x90\xab\xcd\xef"; // size 40 (the max)
+  BOOST_CHECK(eco.makeFromString(cookie));
+
+  // Cookie total size too long
+  cookie += "\x01";
+  BOOST_CHECK(!eco.makeFromString(cookie));
+}
+
+BOOST_AUTO_TEST_CASE(test_ctor)
+{
+  string cookie("");
+  auto eco = EDNSCookiesOpt(cookie);
+  BOOST_CHECK(!eco.isWellFormed());
+
+  eco = EDNSCookiesOpt("\x12\x34\x56\x78\x90\xab\xcd\xef");
+  BOOST_CHECK(eco.isWellFormed());
+  BOOST_CHECK_EQUAL(8, eco.makeOptString().length());
+}
+
+#ifdef HAVE_CRYPTO_SHORTHASH
+BOOST_AUTO_TEST_CASE(test_createEDNSServerCookie)
+{
+  auto eco = EDNSCookiesOpt("\x12\x34\x56\x78\x90\xab\xcd\xef");
+  ComboAddress remote("192.0.2.2");
+
+  BOOST_CHECK(eco.isWellFormed());
+
+  // wrong keysize (not 128 bits)
+  string secret = "blablablabla";
+  BOOST_CHECK(!eco.makeServerCookie(secret, remote));
+  BOOST_CHECK(eco.isWellFormed());
+  BOOST_CHECK(!eco.isValid(secret, remote));
+
+  secret = "blablablablablab";
+  BOOST_CHECK(eco.makeServerCookie(secret, remote));
+  BOOST_CHECK(eco.isWellFormed());
+  BOOST_CHECK(eco.isValid(secret, remote));
+
+  EDNSCookiesOpt eco2(eco.makeOptString());
+  BOOST_CHECK(!eco2.isValid(secret, ComboAddress("192.0.2.1")));
+  BOOST_CHECK(!eco2.isValid("blablablablabla1", remote));
+  BOOST_CHECK(eco2.isValid(secret, remote));
+
+  // Check is we marked it as valid before
+  BOOST_CHECK(eco2.isValid("blablablablabla1", remote));
+}
+#endif
+
+BOOST_AUTO_TEST_SUITE_END()
index 6a55effcffe8850ce275b68591474b724338b0b0..64c60e972ea122acd9bd75429b3d08139b9d1ec2 100644 (file)
@@ -164,10 +164,8 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     opt.source = Netmask("192.0.2.1/32");
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
-    EDNSCookiesOpt cookiesOpt;
-    cookiesOpt.client = string("deadbeef");
-    cookiesOpt.server = string("deadbeef");
-    ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, makeEDNSCookiesOptString(cookiesOpt)));
+    EDNSCookiesOpt cookiesOpt(string("deadbeefdeadbeef"));
+    ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, cookiesOpt.makeOptString()));
     pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
     pw1.commit();
 
@@ -182,9 +180,8 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     opt.source = Netmask("192.0.2.1/32");
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
-    cookiesOpt.client = string("deadbeef");
-    cookiesOpt.server = string("badc0fee");
-    ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, makeEDNSCookiesOptString(cookiesOpt)));
+    cookiesOpt.makeFromString(string("deadbeefbadc0fee"));
+    ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, cookiesOpt.makeOptString()));
     pw2.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
     pw2.commit();
 
@@ -352,14 +349,8 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
     opt.source = Netmask("192.0.2.1/32");
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
-    EDNSCookiesOpt cookiesOpt;
-    cookiesOpt.client = string("deadbeef");
-    cookiesOpt.server = string("deadbeef");
-    cookiesOpt.server[4] = -20;
-    cookiesOpt.server[5] = -114;
-    cookiesOpt.server[6] = 0;
-    cookiesOpt.server[7] = 0;
-    ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, makeEDNSCookiesOptString(cookiesOpt)));
+    EDNSCookiesOpt cookiesOpt(string("deadbeefdead\x11\xee\x00\x00").c_str(), 16);
+    ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, cookiesOpt.makeOptString()));
     pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
     pw1.commit();
 
@@ -374,13 +365,8 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
     opt.source = Netmask("192.0.2.1/32");
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
-    cookiesOpt.client = string("deadbeef");
-    cookiesOpt.server = string("deadbeef");
-    cookiesOpt.server[4] = 103;
-    cookiesOpt.server[5] = 68;
-    cookiesOpt.server[6] = 0;
-    cookiesOpt.server[7] = 0;
-    ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, makeEDNSCookiesOptString(cookiesOpt)));
+    cookiesOpt.makeFromString(string("deadbeefdead\x67\x44\x00\x00").c_str(), 16);
+    ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, cookiesOpt.makeOptString()));
     pw2.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
     pw2.commit();
 
index f9bbe80b4349648aba700e583e354a822caa23a5..2047e6d57498900fe79b517f4ab9e3137ee60cbc 100644 (file)
@@ -547,8 +547,12 @@ options {
                 raise TypeError("rcode is neither a str nor int")
 
         if msg.rcode() != rcode:
-            msgRcode = dns.rcode.to_text(msg.rcode())
-            wantedRcode = dns.rcode.to_text(rcode)
+            try:
+                msgRcode = dns.rcode.to_text(msg.rcode())
+                wantedRcode = dns.rcode.to_text(rcode)
+            except AttributeError:
+                msgRcode = msg.rcode()
+                wantedRcode = rcode
 
             raise AssertionError("Rcode for %s is %s, expected %s." % (msg.question[0].to_text(), msgRcode, wantedRcode))
 
diff --git a/regression-tests.auth-py/cookiesoption.py b/regression-tests.auth-py/cookiesoption.py
new file mode 120000 (symlink)
index 0000000..11e592d
--- /dev/null
@@ -0,0 +1 @@
+../regression-tests.dnsdist/cookiesoption.py
\ No newline at end of file
diff --git a/regression-tests.auth-py/test_Cookies.py b/regression-tests.auth-py/test_Cookies.py
new file mode 100644 (file)
index 0000000..50bc75f
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+import dns
+
+from authtests import AuthTest
+
+
+class TestEdnsCookies(AuthTest):
+    _config_template = """
+launch=bind
+edns-cookie-secret=aabbccddeeff11223344556677889900
+"""
+
+    _zones = {
+        'example.org': """
+example.org.                 3600 IN SOA  {soa}
+example.org.                 3600 IN NS   ns1.example.org.
+example.org.                 3600 IN NS   ns2.example.org.
+ns1.example.org.             3600 IN A    192.0.2.10
+ns2.example.org.             3600 IN A    192.0.2.11
+
+www.example.org.             3600 IN A    192.0.2.5
+        """,
+    }
+
+    def sendAndExpectNoCookie(self, msg, rcode):
+        res = self.sendUDPQuery(msg)
+        self.assertRcodeEqual(res, rcode)
+        self.assertFalse(any([opt.otype == dns.edns.COOKIE for
+                              opt in res.options]))
+
+    def getCookieFromServer(self):
+        opts = [
+            dns.edns.GenericOption(dns.edns.COOKIE,
+                                   b'\x22\x11\x33\x44\x55\x66\x77\x88')]
+        query = dns.message.make_query('www.example.org', 'A', options=opts)
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, 23)  # BADCOOKIE
+        for opt in res.options:
+            if opt.otype == dns.edns.COOKIE:
+                return opt
+        self.fail()
+
+    def testNoCookie(self):
+        query = dns.message.make_query('www.example.org', 'A', use_edns=0)
+        self.sendAndExpectNoCookie(query, dns.rcode.NOERROR)
+
+    def testClientCookieTooShort(self):
+        opts = [dns.edns.GenericOption(dns.edns.COOKIE, b'\x22')]
+        query = dns.message.make_query('www.example.org', 'A', options=opts)
+        self.sendAndExpectNoCookie(query, dns.rcode.FORMERR)
+
+        opts = [dns.edns.GenericOption(dns.edns.COOKIE,
+                                       b'\x22\x11\x33\x44\x55\x66\x77')]
+        query = dns.message.make_query('www.example.org', 'A', options=opts)
+        self.sendAndExpectNoCookie(query, dns.rcode.FORMERR)
+
+    def testServerCookieTooShort(self):
+        opts = [
+            dns.edns.GenericOption(dns.edns.COOKIE,
+                                   b'\x22\x11\x33\x44\x55\x66\x77\x88\x99')]
+        query = dns.message.make_query('www.example.org', 'A', options=opts)
+        self.sendAndExpectNoCookie(query, dns.rcode.FORMERR)
+
+        opts = [
+            dns.edns.GenericOption(dns.edns.COOKIE,
+                                   b'\x22\x11\x33\x44\x55\x66\x77\x88' +
+                                   b'\x22\x11\x33\x44\x55\x66\x77')]
+        query = dns.message.make_query('www.example.org', 'A', options=opts)
+        self.sendAndExpectNoCookie(query, dns.rcode.FORMERR)
+
+    def testOnlyClientCookie(self):
+        opts = [
+            dns.edns.GenericOption(dns.edns.COOKIE,
+                                   b'\x22\x11\x33\x44\x55\x66\x77\x88')]
+        query = dns.message.make_query('www.example.org', 'A', options=opts)
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, 23)  # BADCOOKIE
+        self.assertTrue(any([opt.otype == dns.edns.COOKIE for
+                             opt in res.options]))
+
+    def testCorrectCookie(self):
+        opts = [self.getCookieFromServer()]
+        query = dns.message.make_query('www.example.org', 'A', options=opts)
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+
+    def testBrokenCookie(self):
+        data = self.getCookieFromServer().data
+        data = data.replace(b'\x11', b'\x12')
+        opts = [dns.edns.GenericOption(dns.edns.COOKIE, data)]
+        query = dns.message.make_query('www.example.org', 'A', options=opts)
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, 23)
+        for opt in res.options:
+            if opt.otype == dns.edns.COOKIE:
+                self.assertNotEqual(opt.data, opts[0].data)
+                return
+        self.fail()
index d977e1030db6f83991dcd200c33bd68d0df0c2a6..8e4016d27b07c625070b8ee2b0de83842a580bb0 100644 (file)
@@ -96,3 +96,5 @@ class CookiesOption(dns.edns.Option):
 
 
 dns.edns._type_to_class[0x000A] = CookiesOption
+
+dns.rcode.BADCOOKIE = 23