From: Remi Gacogne Date: Thu, 24 Aug 2023 15:28:10 +0000 (+0200) Subject: dnsdist: Log Extended DNS Errors (EDE) to protobuf X-Git-Tag: rec-5.0.0-alpha2~22^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=83dc6a3887d53637547658a8d709271d605b3366;p=thirdparty%2Fpdns.git dnsdist: Log Extended DNS Errors (EDE) to protobuf --- diff --git a/pdns/dnscrypt.hh b/pdns/dnscrypt.hh index 356b4c47d4..a42be6ab2b 100644 --- a/pdns/dnscrypt.hh +++ b/pdns/dnscrypt.hh @@ -21,6 +21,7 @@ */ #pragma once #include "config.h" +#include #ifndef HAVE_DNSCRYPT @@ -43,7 +44,6 @@ private: #else /* HAVE_DNSCRYPT */ -#include #include #include #include diff --git a/pdns/dnsdist-ecs.hh b/pdns/dnsdist-ecs.hh index 653052df81..f5d215f1a0 100644 --- a/pdns/dnsdist-ecs.hh +++ b/pdns/dnsdist-ecs.hh @@ -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); } diff --git a/pdns/dnsdist-idstate.hh b/pdns/dnsdist-idstate.hh index 456e703fb3..313e434ea8 100644 --- a/pdns/dnsdist-idstate.hh +++ b/pdns/dnsdist-idstate.hh @@ -27,6 +27,7 @@ #include "dnsdist-protocols.hh" #include "gettime.hh" #include "iputils.hh" +#include "noinitvector.hh" #include "uuid-utils.hh" struct ClientState; diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index 0f1d854a8f..b81600fe8c 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -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 > d_alterFunc; }; -static void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::vector>& metas) +namespace +{ +void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::vector>& 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& allowed) +void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::unordered_set& 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> metas; + std::optional> tagsToExport{std::nullopt}; + boost::optional > alterQueryFunc{boost::none}; + boost::optional > alterResponseFunc{boost::none}; + std::shared_ptr logger; + std::string serverID; + std::string ipEncryptKey; + std::optional 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& logger, boost::optional > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, std::vector>&& metas, std::optional>&& 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& logger, boost::optional > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, bool includeCNAME, std::vector>&& metas, std::optional>&& 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 > d_alterFunc; std::string d_serverID; std::string d_ipEncryptKey; + std::optional 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(vars, "serverID", serverID); - getOptionalValue(vars, "ipEncryptKey", ipEncryptKey); + RemoteLogActionConfiguration config; + config.logger = logger; + config.alterQueryFunc = std::move(alterFunc); + getOptionalValue(vars, "serverID", config.serverID); + getOptionalValue(vars, "ipEncryptKey", config.ipEncryptKey); getOptionalValue(vars, "exportTags", tags); - std::vector> metaOptions; if (metas) { for (const auto& [key, value] : *metas) { - metaOptions.push_back({key, ProtoBufMetaKey(value)}); + config.metas.push_back({key, ProtoBufMetaKey(value)}); } } - std::optional> tagsToExport{std::nullopt}; if (!tags.empty()) { - tagsToExport = std::unordered_set(); + config.tagsToExport = std::unordered_set(); if (tags != "*") { std::vector 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(new RemoteLogAction(logger, std::move(alterFunc), serverID, ipEncryptKey, std::move(metaOptions), std::move(tagsToExport))); + return std::shared_ptr(new RemoteLogAction(config)); }); luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr logger, boost::optional > alterFunc, boost::optional includeCNAME, boost::optional> vars, boost::optional> metas) { @@ -2551,35 +2587,36 @@ void setupLuaActions(LuaContext& luaCtx) } } - std::string serverID; - std::string ipEncryptKey; std::string tags; - getOptionalValue(vars, "serverID", serverID); - getOptionalValue(vars, "ipEncryptKey", ipEncryptKey); + RemoteLogActionConfiguration config; + config.logger = logger; + config.alterResponseFunc = alterFunc; + config.includeCNAME = includeCNAME ? *includeCNAME : false; + getOptionalValue(vars, "serverID", config.serverID); + getOptionalValue(vars, "ipEncryptKey", config.ipEncryptKey); getOptionalValue(vars, "exportTags", tags); + getOptionalValue(vars, "exportExtendedErrorsToMeta", config.exportExtendedErrorsToMeta); - std::vector> metaOptions; if (metas) { for (const auto& [key, value] : *metas) { - metaOptions.push_back({key, ProtoBufMetaKey(value)}); + config.metas.push_back({key, ProtoBufMetaKey(value)}); } } - std::optional> tagsToExport{std::nullopt}; if (!tags.empty()) { - tagsToExport = std::unordered_set(); + config.tagsToExport = std::unordered_set(); if (tags != "*") { std::vector 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(new RemoteLogResponseAction(logger, std::move(alterFunc), serverID, ipEncryptKey, includeCNAME ? *includeCNAME : false, std::move(metaOptions), std::move(tagsToExport))); + return std::shared_ptr(new RemoteLogResponseAction(config)); }); luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr logger, boost::optional > alterFunc) { diff --git a/pdns/dnsdist-protobuf.cc b/pdns/dnsdist-protobuf.cc index c38529cb67..453cba87f5 100644 --- a/pdns/dnsdist-protobuf.cc +++ b/pdns/dnsdist-protobuf.cc @@ -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&& values) +void DNSDistProtoBufMessage::addMeta(const std::string& key, std::vector&& strValues, const std::vector& 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 */ diff --git a/pdns/dnsdist-protobuf.hh b/pdns/dnsdist-protobuf.hh index 39305383d3..1e30f26582 100644 --- a/pdns/dnsdist-protobuf.hh +++ b/pdns/dnsdist-protobuf.hh @@ -47,7 +47,7 @@ public: void setEDNSSubnet(const Netmask& nm); void addTag(const std::string& strValue); - void addMeta(const std::string& key, std::vector&& values); + void addMeta(const std::string& key, std::vector&& strValues, const std::vector& 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 d_additionalRRs; std::vector d_additionalTags; - std::unordered_map> d_metaTags; + struct MetaValue + { + std::unordered_set d_strings; + std::unordered_set d_integers; + }; + std::unordered_map d_metaTags; const DNSQuestion& d_dq; const DNSResponse* d_dr{nullptr}; diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index c39015d50f..1a80477586 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -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 index 0000000000..794d11e6bd --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-edns.cc @@ -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> 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 index 0000000000..75c92c143a --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-edns.hh @@ -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 +#include +#include + +#include "noinitvector.hh" + +namespace dnsdist::edns +{ +std::pair, std::optional> getExtendedDNSError(const PacketBuffer& packet); +} diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index 2dae142792..19c4ed51b4 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -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 "", not ":". 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)