From: Remi Gacogne Date: Tue, 29 Aug 2023 09:03:07 +0000 (+0200) Subject: dnsdist: Add unit tests for Extended Error parsing X-Git-Tag: rec-5.0.0-alpha2~22^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b341ebf9e15a4938ee94c208af97f17b99bd9534;p=thirdparty%2Fpdns.git dnsdist: Add unit tests for Extended Error parsing --- diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 1a80477586..770a5ccfe4 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -301,6 +301,7 @@ testrunner_SOURCES = \ dnswriter.cc dnswriter.hh \ dolog.hh \ ednscookies.cc ednscookies.hh \ + ednsextendederror.cc ednsextendederror.hh \ ednsoptions.cc ednsoptions.hh \ ednssubnet.cc ednssubnet.hh \ ext/luawrapper/include/LuaContext.hpp \ @@ -332,6 +333,7 @@ testrunner_SOURCES = \ test-dnsdistasync.cc \ test-dnsdistbackend_cc.cc \ test-dnsdistdynblocks_hh.cc \ + test-dnsdistedns.cc \ test-dnsdistkvs_cc.cc \ test-dnsdistlbpolicies_cc.cc \ test-dnsdistluanetwork.cc \ diff --git a/pdns/dnsdistdist/dnsdist-edns.cc b/pdns/dnsdistdist/dnsdist-edns.cc index 794d11e6bd..982fe3efef 100644 --- a/pdns/dnsdistdist/dnsdist-edns.cc +++ b/pdns/dnsdistdist/dnsdist-edns.cc @@ -40,7 +40,7 @@ std::pair, std::optional> getExtendedDNSErr size_t optContentStart = 0; uint16_t optContentLen = 0; uint16_t infoCode{0}; - std::string extraText; + std::optional extraText{std::nullopt}; /* we need at least 2 bytes after the option length (info-code) */ if (!isEDNSOptionInOpt(packet, optStart, optLen, EDNSOptionCode::EXTENDEDERROR, &optContentStart, &optContentLen) || optContentLen < sizeof(infoCode)) { return std::make_pair(std::nullopt, std::nullopt); @@ -49,8 +49,9 @@ std::pair, std::optional> getExtendedDNSErr infoCode = ntohs(infoCode); if (optContentLen > sizeof(infoCode)) { - extraText.resize(optContentLen - sizeof(infoCode)); - memcpy(extraText.data(), &packet.at(optContentStart + sizeof(infoCode)), optContentLen - sizeof(infoCode)); + extraText = std::string(); + extraText->resize(optContentLen - sizeof(infoCode)); + memcpy(extraText->data(), &packet.at(optContentStart + sizeof(infoCode)), optContentLen - sizeof(infoCode)); } return std::make_pair(infoCode, std::move(extraText)); } diff --git a/pdns/dnsdistdist/ednsextendederror.cc b/pdns/dnsdistdist/ednsextendederror.cc new file mode 120000 index 0000000000..4f6ced0eb1 --- /dev/null +++ b/pdns/dnsdistdist/ednsextendederror.cc @@ -0,0 +1 @@ +../ednsextendederror.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/ednsextendederror.hh b/pdns/dnsdistdist/ednsextendederror.hh new file mode 120000 index 0000000000..2e5eee1927 --- /dev/null +++ b/pdns/dnsdistdist/ednsextendederror.hh @@ -0,0 +1 @@ +../ednsextendederror.hh \ No newline at end of file diff --git a/pdns/dnsdistdist/test-dnsdistedns.cc b/pdns/dnsdistdist/test-dnsdistedns.cc new file mode 100644 index 0000000000..93e0e0080b --- /dev/null +++ b/pdns/dnsdistdist/test-dnsdistedns.cc @@ -0,0 +1,199 @@ +/* + * 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 + +#include + +#include "dnsdist-edns.hh" +#include "dnsname.hh" +#include "dnswriter.hh" +#include "ednscookies.hh" +#include "ednsextendederror.hh" +#include "ednsoptions.hh" +#include "ednssubnet.hh" + +BOOST_AUTO_TEST_SUITE(test_dnsdist_edns) + +BOOST_AUTO_TEST_CASE(getExtendedDNSError) +{ + const DNSName name("www.powerdns.com."); + + { + /* no EDNS */ + PacketBuffer query; + GenericDNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.commit(); + + auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query); + BOOST_CHECK(!infoCode); + BOOST_CHECK(!extraText); + } + + { + /* EDNS but no EDE */ + PacketBuffer query; + GenericDNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.addOpt(512, 0, 0); + pw.commit(); + + auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query); + BOOST_CHECK(!infoCode); + BOOST_CHECK(!extraText); + } + + { + /* EDE with a numerical code but no text */ + PacketBuffer query; + GenericDNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + GenericDNSPacketWriter::optvect_t opts; + const EDNSExtendedError ede{ + .infoCode = static_cast(EDNSExtendedError::code::NetworkError), + .extraText = ""}; + opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query); + BOOST_CHECK(infoCode); + BOOST_CHECK_EQUAL(*infoCode, ede.infoCode); + BOOST_CHECK(!extraText); + } + + { + /* EDE with both code and text */ + PacketBuffer query; + GenericDNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + GenericDNSPacketWriter::optvect_t opts; + const EDNSExtendedError ede{ + .infoCode = static_cast(EDNSExtendedError::code::Synthesized), + .extraText = "Synthesized from aggressive NSEC cache"}; + opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query); + BOOST_CHECK(infoCode); + BOOST_CHECK_EQUAL(*infoCode, ede.infoCode); + BOOST_CHECK(extraText); + BOOST_CHECK_EQUAL(*extraText, ede.extraText); + } + + { + /* EDE with truncated text */ + PacketBuffer query; + GenericDNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + GenericDNSPacketWriter::optvect_t opts; + const EDNSExtendedError ede{ + .infoCode = static_cast(EDNSExtendedError::code::Synthesized), + .extraText = "Synthesized from aggressive NSEC cache"}; + opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + /* truncate the EDE text by one byte */ + query.resize(query.size() - 1U); + + BOOST_CHECK_THROW(dnsdist::edns::getExtendedDNSError(query), std::range_error); + } + + { + /* EDE before ECS */ + PacketBuffer query; + GenericDNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + GenericDNSPacketWriter::optvect_t opts; + const EDNSExtendedError ede{ + .infoCode = static_cast(EDNSExtendedError::code::Synthesized), + .extraText = "Synthesized from aggressive NSEC cache"}; + opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede)); + EDNSSubnetOpts ecsOpt; + ecsOpt.source = Netmask(ComboAddress("192.0.2.1"), 24U); + const auto ecsOptStr = makeEDNSSubnetOptsString(ecsOpt); + opts.emplace_back(EDNSOptionCode::ECS, ecsOptStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query); + BOOST_CHECK(infoCode); + BOOST_CHECK_EQUAL(*infoCode, ede.infoCode); + BOOST_CHECK(extraText); + BOOST_CHECK_EQUAL(*extraText, ede.extraText); + } + + { + /* EDE after ECS */ + PacketBuffer query; + GenericDNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + GenericDNSPacketWriter::optvect_t opts; + EDNSSubnetOpts ecsOpt; + ecsOpt.source = Netmask(ComboAddress("192.0.2.1"), 24U); + const auto ecsOptStr = makeEDNSSubnetOptsString(ecsOpt); + opts.emplace_back(EDNSOptionCode::ECS, ecsOptStr); + const EDNSExtendedError ede{ + .infoCode = static_cast(EDNSExtendedError::code::Synthesized), + .extraText = "Synthesized from aggressive NSEC cache"}; + opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede)); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query); + BOOST_CHECK(infoCode); + BOOST_CHECK_EQUAL(*infoCode, ede.infoCode); + BOOST_CHECK(extraText); + BOOST_CHECK_EQUAL(*extraText, ede.extraText); + } + + { + /* Cookie, EDE, padding */ + PacketBuffer query; + GenericDNSPacketWriter pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + GenericDNSPacketWriter::optvect_t opts; + const EDNSCookiesOpt cookieOpt("deadbeefdeadbeef"); + const auto cookieOptStr = cookieOpt.makeOptString(); + opts.emplace_back(EDNSOptionCode::COOKIE, cookieOptStr); + const EDNSExtendedError ede{ + .infoCode = static_cast(EDNSExtendedError::code::Synthesized), + .extraText = "Synthesized from aggressive NSEC cache"}; + opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede)); + std::string paddingOptStr; + paddingOptStr.resize(42U); + opts.emplace_back(EDNSOptionCode::PADDING, paddingOptStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query); + BOOST_CHECK(infoCode); + BOOST_CHECK_EQUAL(*infoCode, ede.infoCode); + BOOST_CHECK(extraText); + BOOST_CHECK_EQUAL(*extraText, ede.extraText); + } +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/pdns/dnswriter.hh b/pdns/dnswriter.hh index cfa4eb9947..4d6d286182 100644 --- a/pdns/dnswriter.hh +++ b/pdns/dnswriter.hh @@ -69,7 +69,7 @@ public: void startRecord(const DNSName& name, uint16_t qtype, uint32_t ttl=3600, uint16_t qclass=QClass::IN, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, bool compress=true); /** Shorthand way to add an Opt-record, for example for EDNS0 purposes */ - typedef vector > optvect_t; + using optvect_t = vector >; void addOpt(const uint16_t udpsize, const uint16_t extRCode, const uint16_t ednsFlags, const optvect_t& options=optvect_t(), const uint8_t version=0); /** needs to be called after the last record is added, but can be called again and again later on. Is called internally by startRecord too. diff --git a/pdns/ednsextendederror.cc b/pdns/ednsextendederror.cc new file mode 100644 index 0000000000..5010e3dba5 --- /dev/null +++ b/pdns/ednsextendederror.cc @@ -0,0 +1,65 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include + +#include "ednsextendederror.hh" + +static bool getEDNSExtendedErrorOptFromStringView(const std::string_view& option, EDNSExtendedError& eee) +{ + if (option.size() < sizeof(uint16_t)) { + return false; + } + eee.infoCode = static_cast(option.at(0)) * 256 + static_cast(option.at(1)); + + if (option.size() > sizeof(uint16_t)) { + eee.extraText = std::string(&option.at(sizeof(uint16_t)), option.size() - sizeof(uint16_t)); + } + + return true; +} + +bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee) +{ + return getEDNSExtendedErrorOptFromStringView(std::string_view(option), eee); +} + +bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee) +{ + return getEDNSExtendedErrorOptFromStringView(std::string_view(option, len), eee); +} + +string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee) +{ + if (eee.extraText.size() > static_cast(std::numeric_limits::max() - 2)) { + throw std::runtime_error("Trying to create an EDNS Extended Error option with an extra text of size " + std::to_string(eee.extraText.size())); + } + + string ret; + ret.reserve(sizeof(uint16_t) + eee.extraText.size()); + ret.resize(sizeof(uint16_t)); + + ret[0] = static_cast(static_cast(eee.infoCode) / 256); + ret[1] = static_cast(static_cast(eee.infoCode) % 256); + ret.append(eee.extraText); + + return ret; +} diff --git a/pdns/ednsextendederror.hh b/pdns/ednsextendederror.hh new file mode 100644 index 0000000000..5b264fcf69 --- /dev/null +++ b/pdns/ednsextendederror.hh @@ -0,0 +1,66 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once +#include "namespaces.hh" + +struct EDNSExtendedError +{ + enum class code : uint16_t + { + Other = 0, + UnsupportedDNSKEYAlgorithm = 1, + UnsupportedDSDigestType = 2, + StaleAnswer = 3, + ForgedAnswer = 4, + DNSSECIndeterminate = 5, + DNSSECBogus = 6, + SignatureExpired = 7, + SignatureNotYetValid = 8, + DNSKEYMissing = 9, + RRSIGsMissing = 10, + NoZoneKeyBitSet = 11, + NSECMissing = 12, + CachedError = 13, + NotReady = 14, + Blocked = 15, + Censored = 16, + Filtered = 17, + Prohibited = 18, + StaleNXDOMAINAnswer = 19, + NotAuthoritative = 20, + NotSupported = 21, + NoReachableAuthority = 22, + NetworkError = 23, + InvalidData = 24, + SignatureExpiredBeforeValid = 25, + TooEarly = 26, + UnsupportedNSEC3IterationsValue = 27, + UnableToConformToPolicy = 28, + Synthesized = 29, + }; + uint16_t infoCode; + std::string extraText; +}; + +bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee); +bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee); +string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee); diff --git a/pdns/recursordist/ednsextendederror.cc b/pdns/recursordist/ednsextendederror.cc deleted file mode 100644 index 5010e3dba5..0000000000 --- a/pdns/recursordist/ednsextendederror.cc +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include - -#include "ednsextendederror.hh" - -static bool getEDNSExtendedErrorOptFromStringView(const std::string_view& option, EDNSExtendedError& eee) -{ - if (option.size() < sizeof(uint16_t)) { - return false; - } - eee.infoCode = static_cast(option.at(0)) * 256 + static_cast(option.at(1)); - - if (option.size() > sizeof(uint16_t)) { - eee.extraText = std::string(&option.at(sizeof(uint16_t)), option.size() - sizeof(uint16_t)); - } - - return true; -} - -bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee) -{ - return getEDNSExtendedErrorOptFromStringView(std::string_view(option), eee); -} - -bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee) -{ - return getEDNSExtendedErrorOptFromStringView(std::string_view(option, len), eee); -} - -string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee) -{ - if (eee.extraText.size() > static_cast(std::numeric_limits::max() - 2)) { - throw std::runtime_error("Trying to create an EDNS Extended Error option with an extra text of size " + std::to_string(eee.extraText.size())); - } - - string ret; - ret.reserve(sizeof(uint16_t) + eee.extraText.size()); - ret.resize(sizeof(uint16_t)); - - ret[0] = static_cast(static_cast(eee.infoCode) / 256); - ret[1] = static_cast(static_cast(eee.infoCode) % 256); - ret.append(eee.extraText); - - return ret; -} diff --git a/pdns/recursordist/ednsextendederror.cc b/pdns/recursordist/ednsextendederror.cc new file mode 120000 index 0000000000..4f6ced0eb1 --- /dev/null +++ b/pdns/recursordist/ednsextendederror.cc @@ -0,0 +1 @@ +../ednsextendederror.cc \ No newline at end of file diff --git a/pdns/recursordist/ednsextendederror.hh b/pdns/recursordist/ednsextendederror.hh deleted file mode 100644 index 5b264fcf69..0000000000 --- a/pdns/recursordist/ednsextendederror.hh +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once -#include "namespaces.hh" - -struct EDNSExtendedError -{ - enum class code : uint16_t - { - Other = 0, - UnsupportedDNSKEYAlgorithm = 1, - UnsupportedDSDigestType = 2, - StaleAnswer = 3, - ForgedAnswer = 4, - DNSSECIndeterminate = 5, - DNSSECBogus = 6, - SignatureExpired = 7, - SignatureNotYetValid = 8, - DNSKEYMissing = 9, - RRSIGsMissing = 10, - NoZoneKeyBitSet = 11, - NSECMissing = 12, - CachedError = 13, - NotReady = 14, - Blocked = 15, - Censored = 16, - Filtered = 17, - Prohibited = 18, - StaleNXDOMAINAnswer = 19, - NotAuthoritative = 20, - NotSupported = 21, - NoReachableAuthority = 22, - NetworkError = 23, - InvalidData = 24, - SignatureExpiredBeforeValid = 25, - TooEarly = 26, - UnsupportedNSEC3IterationsValue = 27, - UnableToConformToPolicy = 28, - Synthesized = 29, - }; - uint16_t infoCode; - std::string extraText; -}; - -bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee); -bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee); -string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee); diff --git a/pdns/recursordist/ednsextendederror.hh b/pdns/recursordist/ednsextendederror.hh new file mode 120000 index 0000000000..2e5eee1927 --- /dev/null +++ b/pdns/recursordist/ednsextendederror.hh @@ -0,0 +1 @@ +../ednsextendederror.hh \ No newline at end of file