]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Add support for rfc8914: Extended DNS Errors
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 5 Nov 2020 10:38:55 +0000 (11:38 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 18 Nov 2020 08:18:51 +0000 (09:18 +0100)
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

pdns/ednsoptions.hh
pdns/pdns_recursor.cc
pdns/recursordist/Makefile.am
pdns/recursordist/docs/settings.rst
pdns/recursordist/ednsextendederror.cc [new file with mode: 0644]
pdns/recursordist/ednsextendederror.hh [new file with mode: 0644]

index a8f0a87903dd4fe5a0be279135d5c87f68488c11..1c5e99b3ee6b13978d968e8aecc499798d852139 100644 (file)
@@ -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 */
index 615681d3136b512e7003d22181d65c3f3f63ec81..8aaa1d3a96c021daa172ce886edceedf65db596d 100644 (file)
@@ -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<uint16_t> 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<uint16_t>(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<string> 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");
index 6e5a0e8e540f9b0add1c2d0951df6be62581caca..eb8fc7c39b8ebf49bd7b6da7e6eb04a4039e97a9 100644 (file)
@@ -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 \
index fb384ae021d09672af49a467fc09861ce3aa9791..692a28ce45748972854ada7e77a9c17d7e026dee 100644 (file)
@@ -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 (file)
index 0000000..62d3bcc
--- /dev/null
@@ -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<uint8_t>(option.at(0)) * 256 + static_cast<uint8_t>(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<char>(static_cast<uint16_t>(eee.infoCode) / 256);
+  ret[1] = static_cast<char>(static_cast<uint16_t>(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 (file)
index 0000000..e4d46a4
--- /dev/null
@@ -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);