]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Log Extended DNS Errors (EDE) to protobuf
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 24 Aug 2023 15:28:10 +0000 (17:28 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 11 Sep 2023 08:37:06 +0000 (10:37 +0200)
pdns/dnscrypt.hh
pdns/dnsdist-ecs.hh
pdns/dnsdist-idstate.hh
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-protobuf.cc
pdns/dnsdist-protobuf.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-edns.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-edns.hh [new file with mode: 0644]
pdns/dnsdistdist/docs/rules-actions.rst

index 356b4c47d4c3b810e7726a183e7fd72e1b915f0b..a42be6ab2b77541424eae9dc47eac8267435ceba 100644 (file)
@@ -21,6 +21,7 @@
  */
 #pragma once
 #include "config.h"
+#include <memory>
 
 #ifndef HAVE_DNSCRYPT
 
@@ -43,7 +44,6 @@ private:
 
 #else /* HAVE_DNSCRYPT */
 
-#include <memory>
 #include <string>
 #include <vector>
 #include <arpa/inet.h>
index 653052df81965d3ae1db422ed4a245e828d82bf5..f5d215f1a06587953774b3af7e96c31e06830827 100644 (file)
@@ -58,6 +58,7 @@ bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0);
 
 bool setEDNSOption(DNSQuestion& dq, uint16_t ednsCode, const std::string& data);
 
+struct InternalQueryState;
 namespace dnsdist {
 bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer,  uint8_t rcode, bool clearAnswers);
 }
index 456e703fb3db9b4e307de71940340582cc409757..313e434ea8252cbccc25470184802418ec599f6d 100644 (file)
@@ -27,6 +27,7 @@
 #include "dnsdist-protocols.hh"
 #include "gettime.hh"
 #include "iputils.hh"
+#include "noinitvector.hh"
 #include "uuid-utils.hh"
 
 struct ClientState;
index 0f1d854a8fadefd199ceb3e08acf07145917c0bd..b81600fe8c92aa2e7668efa78f28fc71860797a2 100644 (file)
@@ -24,6 +24,7 @@
 #include "dnsdist.hh"
 #include "dnsdist-async.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
 #include "dnsdist-lua.hh"
 #include "dnsdist-lua-ffi.hh"
 #include "dnsdist-mac-address.hh"
@@ -1484,14 +1485,16 @@ private:
   boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > d_alterFunc;
 };
 
-static void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::vector<std::pair<std::string, ProtoBufMetaKey>>& metas)
+namespace
+{
+void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::vector<std::pair<std::string, ProtoBufMetaKey>>& metas)
 {
   for (const auto& [name, meta] : metas) {
-    message.addMeta(name, meta.getValues(dq));
+    message.addMeta(name, meta.getValues(dq), {});
   }
 }
 
-static void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::unordered_set<std::string>& allowed)
+void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::unordered_set<std::string>& allowed)
 {
   if (!dq.ids.qTag) {
     return;
@@ -1511,11 +1514,40 @@ static void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion
   }
 }
 
+void addExtendedDNSErrorToProtobuf(DNSDistProtoBufMessage& message, const DNSResponse& dr, const std::string& metaKey)
+{
+  auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(dr.getData());
+  if (!infoCode) {
+    return;
+  }
+
+  if (extraText) {
+    message.addMeta(metaKey, {*extraText}, {*infoCode});
+  }
+  else {
+    message.addMeta(metaKey, {}, {*infoCode});
+  }
+}
+}
+
+struct RemoteLogActionConfiguration
+{
+  std::vector<std::pair<std::string, ProtoBufMetaKey>> metas;
+  std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
+  boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterQueryFunc{boost::none};
+  boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterResponseFunc{boost::none};
+  std::shared_ptr<RemoteLoggerInterface> logger;
+  std::string serverID;
+  std::string ipEncryptKey;
+  std::optional<std::string> exportExtendedErrorsToMeta{std::nullopt};
+  bool includeCNAME{false};
+};
+
 class RemoteLogAction : public DNSAction, public boost::noncopyable
 {
 public:
   // this action does not stop the processing
-  RemoteLogAction(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, std::vector<std::pair<std::string, ProtoBufMetaKey>>&& metas, std::optional<std::unordered_set<std::string>>&& tagsToExport): d_tagsToExport(std::move(tagsToExport)), d_metas(std::move(metas)), d_logger(logger), d_alterFunc(std::move(alterFunc)), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey)
+  RemoteLogAction(RemoteLogActionConfiguration& config): d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterQueryFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey)
   {
   }
 
@@ -1660,7 +1692,7 @@ class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopya
 {
 public:
   // this action does not stop the processing
-  RemoteLogResponseAction(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, bool includeCNAME, std::vector<std::pair<std::string, ProtoBufMetaKey>>&& metas, std::optional<std::unordered_set<std::string>>&& tagsToExport): d_tagsToExport(std::move(tagsToExport)), d_metas(std::move(metas)), d_logger(logger), d_alterFunc(std::move(alterFunc)), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey), d_includeCNAME(includeCNAME)
+  RemoteLogResponseAction(RemoteLogActionConfiguration& config): d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterResponseFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey), d_exportExtendedErrorsToMeta(std::move(config.exportExtendedErrorsToMeta)), d_includeCNAME(config.includeCNAME)
   {
   }
   DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
@@ -1690,6 +1722,10 @@ public:
 
     addMetaDataToProtobuf(message, *dr, d_metas);
 
+    if (d_exportExtendedErrorsToMeta) {
+      addExtendedDNSErrorToProtobuf(message, *dr, *d_exportExtendedErrorsToMeta);
+    }
+
     if (d_alterFunc) {
       auto lock = g_lua.lock();
       (*d_alterFunc)(dr, &message);
@@ -1713,6 +1749,7 @@ private:
   boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > d_alterFunc;
   std::string d_serverID;
   std::string d_ipEncryptKey;
+  std::optional<std::string> d_exportExtendedErrorsToMeta{std::nullopt};
   bool d_includeCNAME;
 };
 
@@ -2510,35 +2547,34 @@ void setupLuaActions(LuaContext& luaCtx)
         }
       }
 
-      std::string serverID;
-      std::string ipEncryptKey;
       std::string tags;
-      getOptionalValue<std::string>(vars, "serverID", serverID);
-      getOptionalValue<std::string>(vars, "ipEncryptKey", ipEncryptKey);
+      RemoteLogActionConfiguration config;
+      config.logger = logger;
+      config.alterQueryFunc = std::move(alterFunc);
+      getOptionalValue<std::string>(vars, "serverID", config.serverID);
+      getOptionalValue<std::string>(vars, "ipEncryptKey", config.ipEncryptKey);
       getOptionalValue<std::string>(vars, "exportTags", tags);
 
-      std::vector<std::pair<std::string, ProtoBufMetaKey>> metaOptions;
       if (metas) {
         for (const auto& [key, value] : *metas) {
-          metaOptions.push_back({key, ProtoBufMetaKey(value)});
+          config.metas.push_back({key, ProtoBufMetaKey(value)});
         }
       }
 
-      std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
       if (!tags.empty()) {
-        tagsToExport = std::unordered_set<std::string>();
+        config.tagsToExport = std::unordered_set<std::string>();
         if (tags != "*") {
           std::vector<std::string> tokens;
           stringtok(tokens, tags, ",");
           for (auto& token : tokens) {
-            tagsToExport->insert(std::move(token));
+            config.tagsToExport->insert(std::move(token));
           }
         }
       }
 
       checkAllParametersConsumed("RemoteLogAction", vars);
 
-      return std::shared_ptr<DNSAction>(new RemoteLogAction(logger, std::move(alterFunc), serverID, ipEncryptKey, std::move(metaOptions), std::move(tagsToExport)));
+      return std::shared_ptr<DNSAction>(new RemoteLogAction(config));
     });
 
   luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<bool> includeCNAME, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
@@ -2551,35 +2587,36 @@ void setupLuaActions(LuaContext& luaCtx)
         }
       }
 
-      std::string serverID;
-      std::string ipEncryptKey;
       std::string tags;
-      getOptionalValue<std::string>(vars, "serverID", serverID);
-      getOptionalValue<std::string>(vars, "ipEncryptKey", ipEncryptKey);
+      RemoteLogActionConfiguration config;
+      config.logger = logger;
+      config.alterResponseFunc = alterFunc;
+      config.includeCNAME = includeCNAME ? *includeCNAME : false;
+      getOptionalValue<std::string>(vars, "serverID", config.serverID);
+      getOptionalValue<std::string>(vars, "ipEncryptKey", config.ipEncryptKey);
       getOptionalValue<std::string>(vars, "exportTags", tags);
+      getOptionalValue<std::string>(vars, "exportExtendedErrorsToMeta", config.exportExtendedErrorsToMeta);
 
-      std::vector<std::pair<std::string, ProtoBufMetaKey>> metaOptions;
       if (metas) {
         for (const auto& [key, value] : *metas) {
-          metaOptions.push_back({key, ProtoBufMetaKey(value)});
+          config.metas.push_back({key, ProtoBufMetaKey(value)});
         }
       }
 
-      std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
       if (!tags.empty()) {
-        tagsToExport = std::unordered_set<std::string>();
+        config.tagsToExport = std::unordered_set<std::string>();
         if (tags != "*") {
           std::vector<std::string> tokens;
           stringtok(tokens, tags, ",");
           for (auto& token : tokens) {
-            tagsToExport->insert(std::move(token));
+            config.tagsToExport->insert(std::move(token));
           }
         }
       }
 
       checkAllParametersConsumed("RemoteLogResponseAction", vars);
 
-      return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(logger, std::move(alterFunc), serverID, ipEncryptKey, includeCNAME ? *includeCNAME : false, std::move(metaOptions), std::move(tagsToExport)));
+      return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(config));
     });
 
   luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > alterFunc) {
index c38529cb67a0a1e32ff820e3abf18059006433e9..453cba87f55457028e73eb20382a61cb2910d018 100644 (file)
@@ -104,11 +104,14 @@ void DNSDistProtoBufMessage::addTag(const std::string& strValue)
   d_additionalTags.push_back(strValue);
 }
 
-void DNSDistProtoBufMessage::addMeta(const std::string& key, std::vector<std::string>&& values)
+void DNSDistProtoBufMessage::addMeta(const std::string& key, std::vector<std::string>&& strValues, const std::vector<int64_t>& intValues)
 {
   auto& entry = d_metaTags[key];
-  for (auto& value : values) {
-    entry.insert(std::move(value));
+  for (auto& value : strValues) {
+    entry.d_strings.insert(std::move(value));
+  }
+  for (const auto& value : intValues) {
+    entry.d_integers.insert(value);
   }
 }
 
@@ -210,8 +213,8 @@ void DNSDistProtoBufMessage::serialize(std::string& data) const
   }
 
   for (const auto& [key, values] : d_metaTags) {
-    if (!values.empty()) {
-      m.setMeta(key, values, {});
+    if (!values.d_strings.empty() || !values.d_integers.empty()) {
+      m.setMeta(key, values.d_strings, values.d_integers);
     }
     else {
       /* the MetaValue field is _required_ to exist, even if we have no value */
index 39305383d3968f0d01e550f1c688e79eacc6da03..1e30f2658254784e5d19b146320653641117ace2 100644 (file)
@@ -47,7 +47,7 @@ public:
   void setEDNSSubnet(const Netmask& nm);
 
   void addTag(const std::string& strValue);
-  void addMeta(const std::string& key, std::vector<std::string>&& values);
+  void addMeta(const std::string& key, std::vector<std::string>&& strValues, const std::vector<int64_t>& intValues);
   void addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& data);
 
   void serialize(std::string& data) const;
@@ -76,7 +76,12 @@ private:
 
   std::vector<PBRecord> d_additionalRRs;
   std::vector<std::string> d_additionalTags;
-  std::unordered_map<std::string, std::unordered_set<std::string>> d_metaTags;
+  struct MetaValue
+  {
+    std::unordered_set<std::string> d_strings;
+    std::unordered_set<int64_t> d_integers;
+  };
+  std::unordered_map<std::string, MetaValue> d_metaTags;
 
   const DNSQuestion& d_dq;
   const DNSResponse* d_dr{nullptr};
index c39015d50f937a64f6f84695b2c4e060e2202333..1a80477586f7cff531b72c3f3f3c9c4a228ce850 100644 (file)
@@ -157,6 +157,7 @@ dnsdist_SOURCES = \
        dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
        dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
        dnsdist-ecs.cc dnsdist-ecs.hh \
+       dnsdist-edns.cc dnsdist-edns.hh \
        dnsdist-healthchecks.cc dnsdist-healthchecks.hh \
        dnsdist-idstate.hh \
        dnsdist-internal-queries.cc dnsdist-internal-queries.hh \
@@ -268,6 +269,7 @@ testrunner_SOURCES = \
        dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
        dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
        dnsdist-ecs.cc dnsdist-ecs.hh \
+       dnsdist-edns.cc dnsdist-edns.hh \
        dnsdist-idstate.hh \
        dnsdist-kvs.cc dnsdist-kvs.hh \
        dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
diff --git a/pdns/dnsdistdist/dnsdist-edns.cc b/pdns/dnsdistdist/dnsdist-edns.cc
new file mode 100644 (file)
index 0000000..794d11e
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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 "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
+#include "ednsoptions.hh"
+
+namespace dnsdist::edns
+{
+std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSError(const PacketBuffer& packet)
+{
+  uint16_t optStart;
+  size_t optLen = 0;
+  bool last = false;
+
+  int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
+
+  if (res != 0) {
+    return std::make_pair(std::nullopt, std::nullopt);
+  }
+
+  size_t optContentStart = 0;
+  uint16_t optContentLen = 0;
+  uint16_t infoCode{0};
+  std::string extraText;
+  /* 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);
+  }
+  memcpy(&infoCode, &packet.at(optContentStart), sizeof(infoCode));
+  infoCode = ntohs(infoCode);
+
+  if (optContentLen > sizeof(infoCode)) {
+    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/dnsdist-edns.hh b/pdns/dnsdistdist/dnsdist-edns.hh
new file mode 100644 (file)
index 0000000..75c92c1
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 <optional>
+#include <string>
+#include <utility>
+
+#include "noinitvector.hh"
+
+namespace dnsdist::edns
+{
+std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSError(const PacketBuffer& packet);
+}
index 2dae142792945679a56797b9a48baa603aa7c99d..19c4ed51b4ae3166950c2c542af12c56c126ec68 100644 (file)
@@ -1420,6 +1420,9 @@ The following actions exist.
     ``metas`` optional parameter added.
     ``exportTags`` optional key added to the options table.
 
+  .. versionchanged:: 1.9.0
+    ``exportExtendedErrorsToMeta`` optional key added to the options table.
+
   Send the content of this response to a remote logger via Protocol Buffer.
   ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
   ``includeCNAME`` indicates whether CNAME records inside the response should be parsed and exported.
@@ -1438,6 +1441,7 @@ The following actions exist.
   * ``serverID=""``: str - Set the Server Identity field.
   * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
   * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
+  * ``exportExtendedErrorsToMeta=""``: str - Export Extended DNS Errors present in the DNS response, if any, into the specified ``tags`` Protocol Buffer field. The EDE info code will be exported as an integer value, and the EDE extra text, if present, as a string value.
 
 .. function:: SetAdditionalProxyProtocolValueAction(type, value)