SIGUSR
singlethreaded
Sipek
+siphash
sizeof
Sjoerd
slapd
./pdns/dynloader.cc
./pdns/dynmessenger.cc
./pdns/dynmessenger.hh
-./pdns/ednscookies.cc
./pdns/ednsoptions.cc
./pdns/ednsoptions.hh
./pdns/ednspadding.cc
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``
AM_CPPFLAGS +=$(LUA_CFLAGS)
endif
+if LIBSODIUM
+AM_CPPFLAGS +=$(LIBSODIUM_CFLAGS)
+endif
+
AM_LDFLAGS = $(THREADFLAGS)
JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
../../pdns/dnsrecords.cc \
../../pdns/dnssecinfra.cc \
../../pdns/dnswriter.cc \
+ ../../pdns/ednscookies.cc \
../../pdns/ednsoptions.cc ../../pdns/ednsoptions.hh \
../../pdns/ednssubnet.cc \
../../pdns/iputils.cc \
$(AM_LDFLAGS) \
$(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS)
+if LIBSODIUM
+libtestremotebackend_la_LIBADD += $(LIBSODIUM_LIBS)
+endif
+
if REMOTEBACKEND_ZEROMQ
libtestremotebackend_la_LIBADD += $(LIBZMQ_LIBS)
endif
dynhandler.cc dynhandler.hh \
dynlistener.cc dynlistener.hh \
dynmessenger.hh \
+ ednscookies.cc ednscookies.hh \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc ednssubnet.hh \
histogram.hh \
dnssecsigner.cc \
dnswriter.cc dnswriter.hh \
dynlistener.cc \
+ ednscookies.cc \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc \
ipcipher.cc ipcipher.hh \
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 \
::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";
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"));
#include "dns.hh"
#include "dnsbackend.hh"
#include "ednsoptions.hh"
+#include "ednscookies.hh"
#include "pdnsexception.hh"
#include "dnspacket.hh"
#include "logger.hh"
#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)
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()
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
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) {
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);
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;
d_havetsig = mdp.getTSIGPos();
d_haveednssubnet = false;
d_haveednssection = false;
+ d_haveednscookie = false;
if(getEDNSOpts(mdp, &edo)) {
d_haveednssection=true;
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;
}
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)
#include <sys/types.h>
#include "iputils.hh"
#include "ednssubnet.hh"
+#include "ednscookies.hh"
#include <unordered_set>
#include <sys/socket.h>
#include <netinet/in.h>
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)
{
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
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
bool d_tsigtimersonly{false};
bool d_wantsnsid{false};
bool d_haveednssubnet{false};
+ bool d_haveednscookie{false};
bool d_haveednssection{false};
bool d_isQuery;
};
* 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
+}
*/
#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);
+};
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) {
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);
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));
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));
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);
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));
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));
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));
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));
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));
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));
--- /dev/null
+/*
+ * 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()
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();
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();
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();
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();
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))
--- /dev/null
+../regression-tests.dnsdist/cookiesoption.py
\ No newline at end of file
--- /dev/null
+#!/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()
dns.edns._type_to_class[0x000A] = CookiesOption
+
+dns.rcode.BADCOOKIE = 23