From: Remi Gacogne Date: Thu, 5 Nov 2020 10:38:55 +0000 (+0100) Subject: rec: Add support for rfc8914: Extended DNS Errors X-Git-Tag: auth-4.4.0-beta1~2^2~11 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=94e2a9b00ead2430efd64b63539be3a7d2068b83;p=thirdparty%2Fpdns.git rec: Add support for rfc8914: Extended DNS Errors Still needs: - unit tests - regression tests Would be nice to have: - ability to set an extended error from Lua (FFI or not) - same for RPZ matches --- diff --git a/pdns/ednsoptions.hh b/pdns/ednsoptions.hh index a8f0a87903..1c5e99b3ee 100644 --- a/pdns/ednsoptions.hh +++ b/pdns/ednsoptions.hh @@ -24,7 +24,7 @@ struct EDNSOptionCode { - enum EDNSOptionCodeEnum {NSID=3, DAU=5, DHU=6, N3U=7, ECS=8, EXPIRE=9, COOKIE=10, TCPKEEPALIVE=11, PADDING=12, CHAIN=13, KEYTAG=14}; + enum EDNSOptionCodeEnum {NSID=3, DAU=5, DHU=6, N3U=7, ECS=8, EXPIRE=9, COOKIE=10, TCPKEEPALIVE=11, PADDING=12, CHAIN=13, KEYTAG=14, EXTENDEDERROR=15}; }; /* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */ diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index 615681d313..8aaa1d3a96 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -88,6 +88,7 @@ #include "validate-recursor.hh" #include "rec-lua-conf.hh" #include "ednsoptions.hh" +#include "ednsextendederror.hh" #include "gettime.hh" #include "proxy-protocol.hh" #include "pubsuffix.hh" @@ -245,6 +246,7 @@ static std::set s_avoidUdpSourcePorts; static uint16_t s_minUdpSourcePort; static uint16_t s_maxUdpSourcePort; static double s_balancingFactor; +static bool s_addExtendedDNSErrors; RecursorControlChannel s_rcc; // only active in the handler thread RecursorStats g_stats; @@ -1847,11 +1849,73 @@ static void startDoResolve(void *p) sa.reset(); sa.sin4.sin_family = eo.source.getNetwork().sin4.sin_family; eo.scope = Netmask(sa, 0); + auto ecsPayload = makeEDNSSubnetOptsString(eo); - returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(eo))); + maxanswersize -= 2 + 2 + ecsPayload.size(); + + returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::ECS, std::move(ecsPayload))); } if (haveEDNS) { + auto state = sr.getValidationState(); + if (s_addExtendedDNSErrors && vStateIsBogus(state) && pw.size() < maxanswersize && (maxanswersize - pw.size()) > (2 + 2 + 2)) { + EDNSExtendedError::code code; + + switch (state) { + case vState::BogusNoValidDNSKEY: + code = EDNSExtendedError::code::DNSKEYMissing; + break; + case vState::BogusInvalidDenial: + code = EDNSExtendedError::code::NSECMissing; + break; + case vState::BogusUnableToGetDSs: + code = EDNSExtendedError::code::DNSSECBogus; + break; + case vState::BogusUnableToGetDNSKEYs: + code = EDNSExtendedError::code::DNSKEYMissing; + break; + case vState::BogusSelfSignedDS: + code = EDNSExtendedError::code::DNSSECBogus; + break; + case vState::BogusNoRRSIG: + code = EDNSExtendedError::code::RRSIGsMissing; + break; + case vState::BogusNoValidRRSIG: + code = EDNSExtendedError::code::DNSSECBogus; + break; + case vState::BogusMissingNegativeIndication: + code = EDNSExtendedError::code::NSECMissing; + break; + case vState::BogusSignatureNotYetValid: + code = EDNSExtendedError::code::SignatureNotYetValid; + break; + case vState::BogusSignatureExpired: + code = EDNSExtendedError::code::SignatureExpired; + break; + case vState::BogusUnsupportedDNSKEYAlgo: + code = EDNSExtendedError::code::UnsupportedDNSKEYAlgorithm; + break; + case vState::BogusUnsupportedDSDigestType: + code = EDNSExtendedError::code::UnsupportedDSDigestType; + break; + case vState::BogusNoZoneKeyBitSet: + code = EDNSExtendedError::code::NoZoneKeyBitSet; + break; + case vState::BogusRevokedDNSKEY: + code = EDNSExtendedError::code::DNSSECBogus; + break; + case vState::BogusInvalidDNSKEYProtocol: + code = EDNSExtendedError::code::DNSSECBogus; + break; + default: + throw std::runtime_error("Bogus validation state not handled: " + vStateToString(state)); + } + + EDNSExtendedError eee; + eee.infoCode = static_cast(code); + returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee))); + } + /* we try to add the EDNS OPT RR even for truncated answers, as rfc6891 states: "The minimal response MUST be the DNS header, question section, and an @@ -4576,12 +4640,13 @@ static int serviceMain(int argc, char*argv[]) } else { TCPConnection::s_maxInFlight = maxInFlight; } - g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options"); g_statisticsInterval = ::arg().asNum("statistics-interval"); + s_addExtendedDNSErrors = ::arg().mustDo("extended-errors"); + { SuffixMatchNode dontThrottleNames; vector parts; @@ -5322,6 +5387,9 @@ int main(int argc, char **argv) ::arg().set("unique-response-db-size", "Size of the DB used to track unique responses in terms of number of cells. Defaults to 67108864")="67108864"; ::arg().set("unique-response-pb-tag", "If protobuf is configured, the tag to use for messages containing unique DNS responses. Defaults to 'pdns-udr'")="pdns-udr"; #endif /* NOD_ENABLED */ + + ::arg().setSwitch("extended-errors", "If set, send the EDNS Extended Error extension on DNSSEC validation failures")="no"; + ::arg().setCmd("help","Provide a helpful message"); ::arg().setCmd("version","Print version string"); ::arg().setCmd("config","Output blank configuration"); diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 6e5a0e8e54..eb8fc7c39b 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -118,6 +118,7 @@ pdns_recursor_SOURCES = \ dnssecinfra.hh dnssecinfra.cc \ dnsseckeeper.hh \ dnswriter.cc dnswriter.hh \ + ednsextendederror.cc ednsextendederror.hh \ ednsoptions.cc ednsoptions.hh \ ednssubnet.cc ednssubnet.hh \ filterpo.cc filterpo.hh \ diff --git a/pdns/recursordist/docs/settings.rst b/pdns/recursordist/docs/settings.rst index fb384ae021..692a28ce45 100644 --- a/pdns/recursordist/docs/settings.rst +++ b/pdns/recursordist/docs/settings.rst @@ -621,6 +621,17 @@ If set, all hostnames in the `export-etc-hosts`_ file are loaded in canonical fo So an entry called 'pc' with ``export-etc-hosts-search-suffix='home.com'`` will lead to the generation of 'pc.home.com' within the recursor. An entry called 'server1.home' will be stored as 'server1.home', regardless of this setting. +.. _setting-extended-errors: + +``extended-errors`` +------------------- +.. versionadded:: 4.5.0 + +- Boolean +- Default: no + +If set, the recursor will add an EDNS Extended Error to responses failing DNSSEC validation, explaining the failure. + .. _setting-forward-zones: ``forward-zones`` diff --git a/pdns/recursordist/ednsextendederror.cc b/pdns/recursordist/ednsextendederror.cc new file mode 100644 index 0000000000..62d3bcc584 --- /dev/null +++ b/pdns/recursordist/ednsextendederror.cc @@ -0,0 +1,60 @@ +/* + * 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 "ednsextendederror.hh" +#include "views.hh" + +static bool getEDNSExtendedErrorOptFromString(const pdns_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 getEDNSExtendedErrorOptFromString(option, eee); +} + +bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee) +{ + return getEDNSExtendedErrorOptFromString(pdns_string_view(option, len), eee); +} + +string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee) +{ + 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.hh b/pdns/recursordist/ednsextendederror.hh new file mode 100644 index 0000000000..e4d46a4fb6 --- /dev/null +++ b/pdns/recursordist/ednsextendederror.hh @@ -0,0 +1,34 @@ +/* + * 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 }; + 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);