]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Implement EDNS0 padding (rfc7830)
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 9 Mar 2020 16:45:36 +0000 (17:45 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 8 Mar 2021 09:14:22 +0000 (10:14 +0100)
pdns/dnswriter.cc
pdns/dnswriter.hh
pdns/ednspadding.cc [new file with mode: 0644]
pdns/ednspadding.hh [new file with mode: 0644]
pdns/pdns_recursor.cc
pdns/recursordist/Makefile.am
pdns/recursordist/docs/settings.rst
pdns/recursordist/ednspadding.cc [new symlink]
pdns/recursordist/ednspadding.hh [new symlink]

index 09f195db853ec226819bd4c3a865981b5e538e4e..c106ee6f903567f2540f722af8b0efa7f5feaeb8 100644 (file)
@@ -451,7 +451,7 @@ template <typename Container> void GenericDNSPacketWriter<Container>::getRecordP
   records.assign(d_content.begin() + d_sor, d_content.end());
 }
 
-template <typename Container> uint32_t GenericDNSPacketWriter<Container>::size()
+template <typename Container> uint32_t GenericDNSPacketWriter<Container>::size() const
 {
   return d_content.size();
 }
@@ -495,8 +495,18 @@ template <typename Container> void GenericDNSPacketWriter<Container>::commit()
 
 }
 
+template <typename Container> size_t GenericDNSPacketWriter<Container>::getSizeWithOpts(const optvect_t& options) const
+{
+  size_t result = size() + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE + DNS_RDLENGTH_SIZE;
+
+  for(auto const &option : options) {
+    result += 4;
+    result += option.second.size();
+  }
+
+  return result;
+}
+
 template class GenericDNSPacketWriter<std::vector<uint8_t>>;
 #include "noinitvector.hh"
 template class GenericDNSPacketWriter<PacketBuffer>;
-
-
index aef99ae49d7da5e7ca158391ec2e613fe8c947c2..a8e13cbe1bf72cf3d2eb9019738effccf041a3d0 100644 (file)
@@ -77,7 +77,7 @@ public:
    */
   void commit();
 
-  uint32_t size(); // needs to be 32 bit because otherwise we don't see the wrap coming when it happened!
+  uint32_t size() const; // needs to be 32 bit because otherwise we don't see the wrap coming when it happened!
 
   /** Should the packet have grown too big for the writer's liking, rollback removes the record currently being written */
   void rollback();
@@ -155,6 +155,9 @@ public:
   const string getRemaining() const {
     return "";
   }
+
+  size_t getSizeWithOpts(const optvect_t& options) const;
+
 private:
   uint16_t lookupName(const DNSName& name, uint16_t* matchlen);
   vector<uint16_t> d_namepositions;
diff --git a/pdns/ednspadding.cc b/pdns/ednspadding.cc
new file mode 100644 (file)
index 0000000..d5ab8ee
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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 "ednspadding.hh"
+
+std::string makeEDNSPaddingOptString(size_t bytes)
+{
+  std::string ret;
+  ret.resize(bytes, '0');
+  return ret;
+}
+
diff --git a/pdns/ednspadding.hh b/pdns/ednspadding.hh
new file mode 100644 (file)
index 0000000..9db16c6
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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 <string>
+
+std::string makeEDNSPaddingOptString(size_t bytes);
index b9e79acb5b439a8bb5556ba72fc0041f65336a71..f219de1672fecaf48d087cc877f1dba61ea8aedb 100644 (file)
@@ -90,6 +90,7 @@
 #include "rec-lua-conf.hh"
 #include "ednsoptions.hh"
 #include "ednsextendederror.hh"
+#include "ednspadding.hh"
 #include "gettime.hh"
 #include "proxy-protocol.hh"
 #include "pubsuffix.hh"
@@ -192,6 +193,7 @@ static deferredAdd_t g_deferredAdds;
 
 typedef vector<int> tcpListenSockets_t;
 typedef map<int, ComboAddress> listenSocketsAddresses_t; // is shared across all threads right now
+enum class PaddingMode { Always, PaddedQueries };
 
 static listenSocketsAddresses_t g_listenSocketsAddresses; // is shared across all threads right now
 static set<int> g_fromtosockets; // listen sockets that use 'sendfromto()' mechanism (without actually using sendfromto())
@@ -200,6 +202,7 @@ static std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads
 static std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to be setup with this
 static NetmaskGroup g_XPFAcl;
 static NetmaskGroup g_proxyProtocolACL;
+static NetmaskGroup g_paddingFrom;
 static boost::optional<ComboAddress> g_dns64Prefix{boost::none};
 static DNSName g_dns64PrefixReverse;
 static size_t g_proxyProtocolMaximumSize;
@@ -211,9 +214,11 @@ static unsigned int g_maxTCPPerClient;
 static unsigned int g_maxMThreads;
 static unsigned int g_numDistributorThreads;
 static unsigned int g_numWorkerThreads;
+static unsigned int g_paddingTag;
 static int g_tcpTimeout;
 static uint16_t g_udpTruncationThreshold;
 static uint16_t g_xpfRRCode{0};
+static PaddingMode g_paddingMode;
 static std::atomic<bool> statsWanted;
 static std::atomic<bool> g_quiet;
 static bool g_logCommonErrors;
@@ -1484,6 +1489,8 @@ static void startDoResolve(void *p)
     std::vector<pair<uint16_t, string> > ednsOpts;
     bool variableAnswer = dc->d_variable;
     bool haveEDNS=false;
+    bool paddingAllowed = g_paddingFrom.match(dc->d_remote);
+    bool padResponse = paddingAllowed ? (g_paddingMode == PaddingMode::Always) : false;
 #ifdef NOD_ENABLED
     bool hasUDR = false;
 #endif /* NOD_ENABLED */
@@ -1515,9 +1522,24 @@ static void startDoResolve(void *p)
             variableAnswer = true; // Can't packetcache an answer with NSID
             maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size();
           }
+        } else if (paddingAllowed && padResponse == false && g_paddingMode == PaddingMode::PaddedQueries && o.first == EDNSOptionCode::PADDING) {
+          /* we should only pad on 'secure' cases to limit amplification:
+             - over TCP ;
+             - from 'known-good' sources (dnsdist for example).
+             We should also support a strict mode (only pad if the query has a padding option)
+             and a relaxed mode (pad if the client supports EDNS).
+             The block-size should be configurable as well.
+          */
+          padResponse = true;
+          maxanswersize -= 4;
         }
       }
     }
+
+    if (padResponse && dc->d_tag == 0) {
+      dc->d_tag = g_paddingTag;
+    }
+
     /* perhaps there was no EDNS or no ECS but by now we looked */
     dc->d_ecsParsed = true;
     vector<DNSRecord> ret;
@@ -1968,6 +1990,26 @@ static void startDoResolve(void *p)
       }
     }
 
+    if (padResponse) {
+      size_t currentSize = pw.getSizeWithOpts(returnedEdnsOptions);
+      if (currentSize < (static_cast<size_t>(maxanswersize) - 4)) {
+        size_t remaining = static_cast<size_t>(maxanswersize) - currentSize;
+        /* from rfc8647, "4.1.  Recommended Strategy: Block-Length Padding":
+           If a server receives a query that includes the EDNS(0) "Padding"
+           option, it MUST pad the corresponding response (see Section 4 of
+           RFC 7830) and SHOULD pad the corresponding response to a
+           multiple of 468 octets (see below).
+        */
+        static const size_t blockSize = 468;
+        size_t modulo = (currentSize + 4) % blockSize;
+        size_t padSize = 0;
+        if (modulo > 0) {
+          padSize = std::min(blockSize - modulo, remaining);
+        }
+        returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize)));
+      }
+    }
+
     if (haveEDNS) {
       auto state = sr.getValidationState();
       if (dc->d_extendedErrorCode || (s_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
@@ -2599,6 +2641,10 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
         }
       }
 
+      if (dc->d_tag == 0 && g_paddingFrom.match(dc->d_remote)) {
+        dc->d_tag = g_paddingTag;
+      }
+
       const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(&conn->data[0]);
 
       if (t_protobufServers || t_outgoingProtobufServers) {
@@ -2889,13 +2935,15 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
     }
 
     RecursorPacketCache::OptPBData pbData{boost::none};
-
     if (t_protobufServers) {
       if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && policyTags.empty())) {
         protobufLogQuery(luaconfsLocal, uniqueId, source, destination, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName);
       }
     }
 
+    if (ctag == 0 && g_paddingFrom.match(fromaddr)) {
+      ctag = g_paddingTag;
+    }
     /* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable,
        but it means that the hash would not be computed. If some script decides at a later time to mark back the answer
        as cacheable we would cache it with a wrong tag, so better safe than sorry. */
@@ -4716,6 +4764,19 @@ static int serviceMain(int argc, char*argv[])
 
   g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
 
+  g_paddingFrom.toMasks(::arg()["edns-padding-from"]);
+  if (::arg()["edns-padding-mode"] == "always") {
+    g_paddingMode = PaddingMode::Always;
+  }
+  else if (::arg()["edns-padding-mode"] == "padded-queries-only") {
+    g_paddingMode = PaddingMode::PaddedQueries;
+  }
+  else {
+    g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl;
+    exit(1);
+  }
+  g_paddingTag = ::arg().asNum("edns-padding-tag");
+
   g_numDistributorThreads = ::arg().asNum("distributor-threads");
   g_numWorkerThreads = ::arg().asNum("threads");
   if (g_numWorkerThreads < 1) {
@@ -5530,6 +5591,10 @@ int main(int argc, char **argv)
 
     ::arg().setSwitch("aggressive-nsec-cache-size", "The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198")="100000";
 
+    ::arg().set("edns-padding-from", "Sources (proxy IP in case of XPF or proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses")="";
+    ::arg().set("edns-padding-mode", "Whether to add EDNS padding to all responses ('always') or only to the ones to padded queries ('padded-queries-only')")="padded-queries-only";
+    ::arg().set("edns-padding-tag", "Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to non-whitelisted clients.")="7830";
+
     ::arg().setCmd("help","Provide a helpful message");
     ::arg().setCmd("version","Print version string");
     ::arg().setCmd("config","Output blank configuration");
index fe925a4b59b86434ee2fa446d9056dcc2e493ffd..64e3c8f9d4323185e05f827b91517bc3724c21eb 100644 (file)
@@ -117,6 +117,7 @@ pdns_recursor_SOURCES = \
        dnswriter.cc dnswriter.hh \
        ednsextendederror.cc ednsextendederror.hh \
        ednsoptions.cc ednsoptions.hh \
+       ednspadding.cc ednspadding.hh \
        ednssubnet.cc ednssubnet.hh \
        filterpo.cc filterpo.hh \
        fstrm_logger.cc fstrm_logger.hh \
@@ -246,6 +247,7 @@ testrunner_SOURCES = \
        ednscookies.cc ednscookies.hh \
        ednsextendederror.cc ednsextendederror.hh \
        ednsoptions.cc ednsoptions.hh \
+       ednspadding.cc ednspadding.hh \
        ednssubnet.cc ednssubnet.hh \
        filterpo.cc filterpo.hh \
        gettime.cc gettime.hh \
index 4a76db041b914aaf493de8d771abaa277b371c3c..82f8ac64f6e24924a9dae87d01613fff4b62010a 100644 (file)
@@ -610,6 +610,40 @@ found, the recursor fallbacks to sending 127.0.0.1.
 This is the value set for the EDNS0 buffer size in outgoing packets.
 Lower this if you experience timeouts.
 
+.. _setting-edns-padding-from:
+
+``edns-padding-from``
+---------------------
+.. versionadded:: 4.4.0
+
+-  Comma separated list of netmasks
+-  Default: (none)
+
+List of netmasks (proxy IP in case of XPF or proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses. See also `edns-padding-mode`_.
+
+.. _setting-edns-padding-mode:
+
+``edns-padding-mode``
+---------------------
+.. versionadded:: 4.4.0
+
+-  One of ``always``, ``padded-queries-only``, String
+-  Default: ``padded-queries-only``
+
+Whether to add EDNS padding to all responses (``always``) or only to the ones for padded queries (``padded-queries-only``, the default) coming from `edns-padding-from`_ sources.
+
+.. _setting-edns-padding-mode:
+
+``edns-padding-tag``
+--------------------
+.. versionadded:: 4.4.0
+
+-  Integer
+-  Default: 7830
+
+The packetcache tag to use for padded responses, to prevent a client not allowed by the `edns-padding-from`_ list to be served a cached answer generated for an allowed one. This
+effectively divides the packet cache in two when `edns-padding-from`_ is used. Note that this will not override a tag set from one of the ``Lua`` hooks.
+
 .. _setting-edns-subnet-whitelist:
 
 ``edns-subnet-whitelist``
diff --git a/pdns/recursordist/ednspadding.cc b/pdns/recursordist/ednspadding.cc
new file mode 120000 (symlink)
index 0000000..fa7a852
--- /dev/null
@@ -0,0 +1 @@
+../ednspadding.cc
\ No newline at end of file
diff --git a/pdns/recursordist/ednspadding.hh b/pdns/recursordist/ednspadding.hh
new file mode 120000 (symlink)
index 0000000..8701869
--- /dev/null
@@ -0,0 +1 @@
+../ednspadding.hh
\ No newline at end of file