]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
feat(dnsdist): add initial OpenTelemetry Tracing
authorPieter Lexis <pieter.lexis@powerdns.com>
Thu, 25 Sep 2025 15:38:29 +0000 (17:38 +0200)
committerPieter Lexis <pieter.lexis@powerdns.com>
Tue, 14 Oct 2025 18:34:58 +0000 (20:34 +0200)
13 files changed:
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-actions-definitions.yml
pdns/dnsdistdist/dnsdist-actions-factory.cc
pdns/dnsdistdist/dnsdist-idstate.hh
pdns/dnsdistdist/dnsdist-opentelemetry.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-opentelemetry.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-protobuf.cc
pdns/dnsdistdist/dnsdist-protobuf.hh
pdns/dnsdistdist/dnsdist.cc
pdns/dnsdistdist/meson.build
pdns/dnsdistdist/protozero-trace.cc [new symlink]
pdns/dnsdistdist/protozero-trace.hh [new symlink]
pdns/protozero-trace.hh

index 609ba5559e6e5e4a3576ff6817505b9fd9846f71..ab423885d12eaa1ee8c03b64b2d266e9c5b85442 100644 (file)
@@ -234,6 +234,7 @@ dnsdist_SOURCES = \
        dnsdist-metrics.cc dnsdist-metrics.hh \
        dnsdist-nghttp2-in.hh \
        dnsdist-nghttp2.hh \
+       dnsdist-opentelemetry.cc dnsdist-opentelemetry.hh \
        dnsdist-prometheus.hh \
        dnsdist-protobuf.cc dnsdist-protobuf.hh \
        dnsdist-protocols.cc dnsdist-protocols.hh \
@@ -290,6 +291,7 @@ dnsdist_SOURCES = \
        packetcache.hh \
        pdnsexception.hh \
        pollmplexer.cc \
+       protozero-trace.cc protozero-trace.hh \
        protozero.cc protozero.hh \
        proxy-protocol.cc proxy-protocol.hh \
        qtype.cc qtype.hh \
index 05e7f99c9802625339250cf28c276a3de5138889..d7ce17c567ab6a878950d00b5e62686295ed88e7 100644 (file)
@@ -400,6 +400,12 @@ are processed after this action"
     - name: "ttl"
       type: "u32"
       description: "The TTL to use"
+- name: "SetTrace"
+  description: "Enable or disable OpenTelemetry tracing for this query. Don't forget to also use a RemoteLog action to actually send the trace. Subsequent rules are processed after this action"
+  parameters:
+    - name: "value"
+      type: "bool"
+      description: "Whether or not to enable tracing"
 - name: "SNMPTrap"
   description: "Send an SNMP trap, adding the message string as the query description. Subsequent rules are processed after this action"
   parameters:
index deb8110e167624a9820d730e0f1ba1f3ac2104f0..d3407449205fe9beeb9e1c363112a21a4c8d3974 100644 (file)
@@ -47,6 +47,8 @@
 #include "ipcipher.hh"
 #include "dnsdist-ipcrypt2.hh"
 #include "iputils.hh"
+#include "protozero-trace.hh"
+#include "qtype.hh"
 #include "remote_logger.hh"
 #include "svc-records.hh"
 #include "threadname.hh"
@@ -1670,9 +1672,41 @@ private:
   std::string d_ipEncryptMethod;
   std::optional<pdns::ipcrypt2::IPCrypt2> d_ipcrypt2{std::nullopt};
 };
-
 #endif /* DISABLE_PROTOBUF */
 
+class SetTraceAction : public DNSAction
+{
+public:
+  SetTraceAction(bool value) :
+    d_value{value} {};
+
+  DNSAction::Action operator()([[maybe_unused]] DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    (void)ruleresult;
+#ifndef DISABLE_PROTOBUF
+    if (d_value) {
+      dnsquestion->ids.d_OTTracer->activate();
+      dnsquestion->ids.d_OTTracer->setTraceAttribute("query.qname", AnyValue{dnsquestion->ids.qname.toStringNoDot()});
+      dnsquestion->ids.d_OTTracer->setTraceAttribute("query.qtype", AnyValue{QType(dnsquestion->ids.qtype).toString()});
+      dnsquestion->ids.d_OTTracer->setTraceAttribute("query.remote", AnyValue{dnsquestion->ids.origRemote.toLogString()});
+    }
+    else {
+      dnsquestion->ids.d_OTTracer->deactivate();
+    }
+    dnsquestion->ids.tracingEnabled = d_value;
+#endif
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return string((d_value ? "en" : "dis")) + string("able OpenTelemetry Tracing");
+  }
+
+private:
+  bool d_value;
+};
+
 class SNMPTrapAction : public DNSAction
 {
 public:
index 74839e2d69a8b4767e6a4a6e84f86bea9b29dda0..dbf700fce2ad4482cc4c7aa03ebcf24a92ad292b 100644 (file)
  */
 #pragma once
 
+#include <cstdint>
+#include <ctime>
+#include <optional>
 #include <unordered_map>
+#include <utility>
+#include <vector>
 
 #include "config.h"
 #include "dnscrypt.hh"
@@ -31,6 +36,7 @@
 #include "gettime.hh"
 #include "iputils.hh"
 #include "noinitvector.hh"
+#include "dnsdist-opentelemetry.hh"
 #include "uuid-utils.hh"
 
 struct ClientState;
@@ -107,6 +113,13 @@ struct InternalQueryState
     std::string d_requestorID;
   };
 
+  // Whether or not Open Telemetry tracing is enabled for this query
+  bool tracingEnabled = false;
+
+  // TODO: Do we want to keep some data *without* creating a tracer for each query?
+  // TODO: shard_ptr to work with Tracer::Closer?
+  std::unique_ptr<pdns::trace::dnsdist::Tracer> d_OTTracer{new pdns::trace::dnsdist::Tracer};
+
   InternalQueryState()
   {
     origDest.sin4.sin_family = 0;
diff --git a/pdns/dnsdistdist/dnsdist-opentelemetry.cc b/pdns/dnsdistdist/dnsdist-opentelemetry.cc
new file mode 100644 (file)
index 0000000..9b8a061
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * 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-opentelemetry.hh"
+
+#include <vector>
+
+#ifndef DISABLE_PROTOBUF
+#include "protozero-trace.hh"
+#endif
+
+namespace pdns::trace::dnsdist
+{
+
+TracesData Tracer::getTracesData() const
+{
+#ifdef DISABLE_PROTOBUF
+  return 0;
+#else
+  auto otTrace = pdns::trace::TracesData{
+    .resource_spans = {
+      pdns::trace::ResourceSpans{
+        .resource = {
+          .attributes = std::vector<pdns::trace::KeyValue>{
+            pdns::trace::KeyValue{
+              "service.name", pdns::trace::AnyValue{"dnsdist"}},
+          }},
+        .scope_spans = std::vector<pdns::trace::ScopeSpans>{{}}}}};
+
+  otTrace.resource_spans.at(0).resource.attributes.insert(
+    otTrace.resource_spans.at(0).resource.attributes.end(),
+    d_attributes.begin(),
+    d_attributes.end());
+
+  for (auto const& preActivationTrace : d_preActivationSpans) {
+    otTrace.resource_spans.at(0).scope_spans.at(0).spans.push_back(
+      {
+        .trace_id = d_traceid,
+        .span_id = preActivationTrace.span_id,
+        .parent_span_id = preActivationTrace.parent_span_id,
+        .name = preActivationTrace.name,
+        .start_time_unix_nano = preActivationTrace.start_time_unix_nano,
+        .end_time_unix_nano = preActivationTrace.end_time_unix_nano,
+      });
+  }
+
+  otTrace.resource_spans.at(0).scope_spans.at(0).spans.insert(
+    otTrace.resource_spans.at(0).scope_spans.at(0).spans.end(),
+    d_postActivationSpans.begin(),
+    d_postActivationSpans.end());
+  return otTrace;
+#endif
+}
+
+std::string Tracer::getOTProtobuf() const
+{
+#ifdef DISABLE_PROTOBUF
+  return 0;
+#else
+  // TODO: Should we close all spans?
+  return getTracesData().encode();
+#endif
+}
+
+SpanID Tracer::addSpan([[maybe_unused]] const std::string& name)
+{
+#ifdef DISABLE_PROTOBUF
+  return 0;
+#else
+  return addSpan(name, SpanID{});
+#endif
+}
+
+SpanID Tracer::addSpan([[maybe_unused]] const std::string& name, [[maybe_unused]] const SpanID& parentSpanID)
+{
+#ifdef DISABLE_PROTOBUF
+  return 0;
+#else
+  auto spanID = pdns::trace::randomSpanID();
+  if (d_activated) {
+    d_postActivationSpans.push_back({
+      .trace_id = d_traceid,
+      .span_id = spanID,
+      .parent_span_id = parentSpanID,
+      .name = name,
+      .start_time_unix_nano = pdns::trace::timestamp(),
+    });
+    return spanID;
+  }
+
+  // We're not activated, so we are in pre-activation.
+  d_preActivationSpans.push_back({
+    .name = name,
+    .span_id = spanID,
+    .parent_span_id = parentSpanID,
+    .start_time_unix_nano = pdns::trace::timestamp(),
+    .end_time_unix_nano = 0,
+  });
+
+  d_lastSpanID = spanID;
+
+  return spanID;
+#endif
+}
+
+// TODO: Figure out what to do with duplicate keys
+bool Tracer::setTraceAttribute([[maybe_unused]] const std::string& key, [[maybe_unused]] const AnyValue& value)
+{
+#ifdef DISABLE_PROTOBUF
+  // always succesfull
+  return true;
+#else
+  if (!d_activated) {
+    return false;
+  }
+  d_attributes.push_back({key, value});
+  return true;
+#endif
+}
+
+void Tracer::closeSpan([[maybe_unused]] const SpanID& spanID)
+{
+#ifndef DISABLE_PROTOBUF
+  if (d_activated) {
+    auto spanIt = std::find_if(
+      d_postActivationSpans.rbegin(),
+      d_postActivationSpans.rend(),
+      [spanID](const pdns::trace::Span& span) { return span.span_id == spanID; });
+    if (spanIt != d_postActivationSpans.rend()) {
+      if (spanIt->end_time_unix_nano == 0) {
+        spanIt->end_time_unix_nano = pdns::trace::timestamp();
+      }
+      return;
+    }
+  }
+
+  auto spanIt = std::find_if(
+    d_preActivationSpans.rbegin(),
+    d_preActivationSpans.rend(),
+    [spanID](const preActivationSpanInfo& span) { return span.span_id == spanID; });
+  if (spanIt != d_preActivationSpans.rend() && spanIt->end_time_unix_nano == 0) {
+    spanIt->end_time_unix_nano = pdns::trace::timestamp();
+    return;
+  }
+#endif
+}
+
+// TODO: Figure out what to do with duplicate keys
+void Tracer::setSpanAttribute([[maybe_unused]] const SpanID& spanid, [[maybe_unused]] const std::string& key, [[maybe_unused]] const AnyValue& value)
+{
+#ifndef DISABLE_PROTOBUF
+  if (d_activated) {
+    if (auto iter = std::find_if(d_postActivationSpans.rbegin(),
+                                 d_postActivationSpans.rend(),
+                                 [spanid](const pdns::trace::Span& span) { return span.span_id == spanid; });
+        iter != d_postActivationSpans.rend()) {
+      iter->attributes.push_back({key, value});
+      return;
+    }
+  }
+  // XXX: It is not possible to add attributes to d_preActivationTraces. Perhaps these should be converted on calling activate
+#endif
+}
+
+SpanID Tracer::getLastSpanID() const
+{
+#ifdef DISABLE_PROTOBUF
+  return 0;
+#else
+  if (d_activated && d_postActivationSpans.size() != 0) {
+    return d_postActivationSpans.back().span_id;
+  }
+  if (d_preActivationSpans.size() != 0) {
+    return d_preActivationSpans.back().span_id;
+  }
+  return SpanID{};
+#endif
+}
+
+SpanID Tracer::getLastSpanIDForName([[maybe_unused]] const std::string& name) const
+{
+#ifdef DISABLE_PROTOBUF
+  return 0;
+#else
+  if (d_activated && d_postActivationSpans.size() != 0) {
+    if (auto iter = std::find_if(d_postActivationSpans.rbegin(),
+                                 d_postActivationSpans.rend(),
+                                 [name](const pdns::trace::Span& span) { return span.name == name; });
+        iter != d_postActivationSpans.rend()) {
+      return iter->span_id;
+    }
+  }
+
+  if (d_preActivationSpans.size() != 0) {
+    if (auto iter = std::find_if(d_preActivationSpans.rbegin(),
+                                 d_preActivationSpans.rend(),
+                                 [name](const preActivationSpanInfo& span) { return span.name == name; });
+        iter != d_preActivationSpans.rend()) {
+      return iter->span_id;
+    }
+  }
+  return SpanID{};
+#endif
+}
+
+TraceID Tracer::getTraceID() const
+{
+#ifdef DISABLE_PROTOBUF
+  return 0;
+#else
+  return d_traceid;
+#endif
+}
+
+Tracer::Closer Tracer::getCloser([[maybe_unused]] const SpanID& spanid)
+{
+#ifdef DISABLE_PROTOBUF
+  return Tracer::Closer();
+#else
+  return {this, spanid};
+#endif
+}
+
+Tracer::Closer Tracer::openSpan([[maybe_unused]] const std::string& name)
+{
+#ifdef DISABLE_PROTOBUF
+  return Tracer::Closer();
+#else
+  auto spanid = addSpan(name);
+  return getCloser(spanid);
+#endif
+}
+
+Tracer::Closer Tracer::openSpan([[maybe_unused]] const std::string& name, [[maybe_unused]] const SpanID& parentSpanID)
+{
+#ifdef DISABLE_PROTOBUF
+  return Tracer::Closer();
+#else
+  auto spanid = addSpan(name, parentSpanID);
+  return getCloser(spanid);
+#endif
+}
+
+SpanID Tracer::Closer::getSpanID() const
+{
+#ifdef DISABLE_PROTOBUF
+  return 0;
+#else
+  return d_spanID;
+#endif
+}
+
+} // namespace pdns::trace::dnsdist
diff --git a/pdns/dnsdistdist/dnsdist-opentelemetry.hh b/pdns/dnsdistdist/dnsdist-opentelemetry.hh
new file mode 100644 (file)
index 0000000..c1316ca
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * 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>
+#include <vector>
+
+#ifndef DISABLE_PROTOBUF
+#include "protozero-trace.hh"
+using TraceID = pdns::trace::TraceID;
+using SpanID = pdns::trace::SpanID;
+using AnyValue = pdns::trace::AnyValue;
+using TracesData = pdns::trace::TracesData;
+#else
+// Define the minimal things needed
+#include <variant>
+using TraceID = int;
+using SpanID = int;
+using AnyValue = std::variant<std::string, int>;
+using TracesData = int;
+#endif
+
+/*
+ * This namespace contains all the bits and pieces required to do OpenTelemetry
+ * traces in dnsdist. It is contained in this header and cc-file to ensure the rest
+ * of the code is not littered with #ifdefs for DISABLE_PROTOBUF. All functions and
+ * other public members can be safely called/manipulated in a non-protobuf build of
+ * dnsdist.
+ *
+ * The idea is inspired by the rec-eventtrace.{cc,hh} files.
+ *
+ * Although the namespace contains dnsdist, it might be general enough to be
+ * reused (after renaming the namespace) by auth and recursor
+ */
+namespace pdns::trace::dnsdist
+{
+
+/**
+ * @class Tracer
+ * @brief This class holds a single trace instance
+ *
+ */
+class Tracer
+{
+public:
+  Tracer() = default;
+  ~Tracer() = default;
+  Tracer(const Tracer&) = delete;
+  Tracer& operator=(const Tracer) = delete;
+  Tracer& operator=(Tracer&&) = delete;
+  Tracer(Tracer&&) = delete;
+
+  /**
+   * @brief Activate the Tracer
+   *
+   * Once activated, all new events are stored as actual Spans
+   */
+  void activate()
+  {
+#ifndef DISABLE_PROTOBUF
+    d_activated = true;
+    if (d_traceid == pdns::trace::s_emptyTraceID) {
+      pdns::trace::random(d_traceid);
+    }
+#endif
+  }
+
+  /**
+   * @brief Deactivate the tracer
+   */
+  void deactivate()
+  {
+#ifndef DISABLE_PROTOBUF
+    // Set deactivated, but don't delete any data.
+    // The Tracer can be activated later
+    d_activated = false;
+#endif
+  }
+
+  /**
+   * @brief Add an attribute to the Trace
+   *
+   * This only works when Tracer is active
+   *
+   * @param key
+   * @param value
+   * @return true on success, false when attribute was not added
+   */
+  bool setTraceAttribute(const std::string& key, const AnyValue& value);
+
+  /**
+   * @brief Set an attribute on a Span
+   *
+   * This does not work when the Tracer is not active
+   *
+   * @param spanID The SpanID of the Span to add the attribute to
+   * @param key
+   * @param value
+   */
+  void setSpanAttribute(const SpanID& spanID, const std::string& key, const AnyValue& value);
+
+  /**
+   * @brief Sets the stop timestamp for a span
+   *
+   * When a Span is already closed, the timestamp is not updated
+   *
+   * @param spanID The ID of the Span to set the end time for
+   */
+  void closeSpan(const SpanID& spanID);
+
+  /**
+   * @brief Get the last SpanID generated
+   *
+   * @return The last generated SpanID, or empty SpanID when none exist
+   */
+  [[nodiscard]] SpanID getLastSpanID() const;
+
+  /**
+   * @brief Get the SpanID for the most recently added span with a name
+   *
+   * @param name The name of the Span
+   * @return The SpanID, or empty SpanID when none are found
+   */
+  [[nodiscard]] SpanID getLastSpanIDForName(const std::string& name) const;
+
+  /**
+   * @brief Retrieve the TraceID for this Tracer
+   */
+  [[nodiscard]] TraceID getTraceID() const;
+
+  /**
+   * @brief Generate the TracesData from all data in this Tracer
+   *
+   * @return pdns::trace::TracesData
+   */
+  [[nodiscard]] TracesData getTracesData() const;
+
+  /**
+   * @brief Get the TracesData as protobuf encoded OpenTelemetry data
+   */
+  [[nodiscard]] std::string getOTProtobuf() const;
+
+  /**
+   * @class Closer
+   * @brief Automatically closes a Span when it goes out of scope
+   *
+   * This is a helper that _somewhat_ implements Go's `defer` in C++ semantics
+   * Basically, it stores a pointer to the Tracer and a SpanID.
+   * When the object goes out of scope, the closeSpan function is called
+   */
+  class Closer
+  {
+  public:
+    /**
+     * @brief An empty Closer, not really useful
+     */
+#ifdef DISABLE_PROTOBUF
+    Closer() = default;
+#else
+    Closer() :
+      d_tracer(nullptr), d_spanID(SpanID{}) {};
+    /**
+     * @brief Create a Closer
+     *
+     * There should be no need to call this directly. Use one of these functions to get one:
+     *
+     * Tracer::getCloser
+     * Tracer::openSpan
+     *
+     * @param tracer A pointer to the Tracer where we want to close a Span
+     * @param spanid The SpanID to close in the Tracer
+     */
+    Closer(Tracer* tracer, const SpanID& spanid) :
+      d_tracer(tracer), d_spanID(spanid) {};
+#endif
+
+    /**
+     * @brief Closes the Span in the Tracer
+     */
+    ~Closer()
+    {
+#ifndef DISABLE_PROTOBUF
+      if (d_tracer != nullptr) {
+        d_tracer->closeSpan(d_spanID);
+      }
+#endif
+    };
+    Closer(const Closer&) = default;
+    Closer& operator=(const Closer&) = default;
+    Closer& operator=(Closer&&) noexcept = default;
+    Closer(Closer&&) = default;
+
+    /**
+     * @brief Get the SpanID
+     *
+     * @return
+     */
+    [[nodiscard]] SpanID getSpanID() const;
+
+  private:
+#ifndef DISABLE_PROTOBUF
+    // XXX: Should we make this a shared_ptr and force all consumers to Tracer to keep it as a shared_ptr as well?
+    Tracer* d_tracer;
+    SpanID d_spanID;
+#endif
+  };
+
+  /**
+   * @brief Get a Closer for spanid in this Tracer
+   *
+   * @param spanid The SpanID that will close when the Closer is destructed
+   * @return Tracer::Closer
+   */
+  Closer getCloser(const SpanID& spanid);
+
+  /**
+   * @brief Add a new Span
+   *
+   * @param name The name for this span
+   * @return Tracer::Closer for the newly created Span
+   */
+  Closer openSpan(const std::string& name);
+
+  /**
+   * @brief Add a new Span which is a child of another Span
+   *
+   * @param name The name for this span
+   * @param parentSpanID The SpanID of the parent Trace
+   * @return Tracer::Closer for the newly created Span
+   */
+  Closer openSpan(const std::string& name, const SpanID& parentSpanID);
+
+private:
+  /**
+   * @brief Create a new Span
+   *
+   * The Span's start time is set to the current time
+   *
+   * @param name The name for this span
+   * @return The SpanID of the created Span
+   */
+  SpanID addSpan(const std::string& name);
+
+  /**
+   * @brief Create a new Span with a parent
+   *
+   * The Span's start time is set to the current time
+   *
+   * @param name The name for this span
+   * @param parentSpanID The SpanID of the parent Span (not verified)
+   * @return The SpanID of the created Span
+   */
+  SpanID addSpan(const std::string& name, const SpanID& parentSpanID);
+
+#ifndef DISABLE_PROTOBUF
+  /**
+   * @class preActivationSpanInfo
+   * @brief Used before the Tracer is activated to store Span information
+   */
+  struct preActivationSpanInfo
+  {
+    std::string name;
+    SpanID span_id;
+    SpanID parent_span_id;
+    uint64_t start_time_unix_nano;
+    uint64_t end_time_unix_nano;
+  };
+
+  /**
+   * @brief Stores all preActivationSpanInfos. It is used until d_activated is true
+   */
+  std::vector<preActivationSpanInfo> d_preActivationSpans;
+  /**
+   * @brief Stores all Spans. It is used when d_activated is true
+   */
+  std::vector<Span> d_postActivationSpans;
+  /**
+   * @brief All attributes related to this Trace
+   */
+  std::vector<pdns::trace::KeyValue> d_attributes;
+
+  /**
+   * @brief The TraceID for this Tracer. It is stable for the lifetime of the Tracer
+   */
+  TraceID d_traceid{};
+  /**
+   * @brief The last SpanID that was added to this Tracer
+   */
+  SpanID d_lastSpanID{};
+  /**
+   * @brief Whether or not we are storing full Spans or minimal Spans
+   */
+  bool d_activated{false};
+#endif
+};
+} // namespace pdns::trace::dnsdist
index e02b6d875548abb1fd7dc50e3f722337908580b3..10774465c28dbb3306ff2ea4a6bc247d303e2961 100644 (file)
@@ -20,6 +20,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include "config.h"
+#include "protozero-trace.hh"
+#include <vector>
 
 #ifndef DISABLE_PROTOBUF
 #include "base64.hh"
@@ -241,6 +243,11 @@ void DNSDistProtoBufMessage::serialize(std::string& data) const
       msg.setMeta(key, {std::string()}, {});
     }
   }
+
+  if (d_dq.ids.tracingEnabled) {
+    msg.setOpenTelemtryTraceID(d_dq.ids.d_OTTracer->getTraceID());
+    msg.setOpenTelemetryData(d_dq.ids.d_OTTracer->getOTProtobuf());
+  }
 }
 
 ProtoBufMetaKey::ProtoBufMetaKey(const std::string& key)
index 13d035749e60bb6d009f3276feb8bb6aab66e764..6824c716a37c28e9309e50a7980e1e0e4b3b056c 100644 (file)
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "dnsname.hh"
+#include <optional>
 
 #ifndef DISABLE_PROTOBUF
 #include <boost/multi_index_container.hpp>
@@ -29,6 +30,7 @@
 #include <boost/multi_index/key_extractors.hpp>
 
 #include "protozero.hh"
+#include "protozero-trace.hh"
 
 struct DNSQuestion;
 struct DNSResponse;
@@ -110,6 +112,8 @@ private:
 
   pdns::ProtoZero::Message::MessageType d_type{pdns::ProtoZero::Message::MessageType::DNSQueryType};
   bool d_includeCNAME{false};
+
+  std::optional<std::vector<pdns::trace::Span>> d_traceSpans{std::nullopt};
 };
 
 class ProtoBufMetaKey
index 3bba1fd0e8e003ba543ecb41a481562434f1afaf..ebf621d03bbcc9a3d74862da7c57aa84d0558e54 100644 (file)
 #include <grp.h>
 #include <limits>
 #include <netinet/tcp.h>
+#include <optional>
 #include <pwd.h>
 #include <set>
 #include <sys/resource.h>
 #include <unistd.h>
 
+#include "dns.hh"
+#include "dnsdist-idstate.hh"
+#include "dnsdist-opentelemetry.hh"
 #include "dnsdist-systemd.hh"
+#include "protozero-trace.hh"
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
 #endif
@@ -451,6 +456,10 @@ static bool encryptResponse(PacketBuffer& response, size_t maximumSize, bool tcp
 
 bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, DNSResponse& dnsResponse)
 {
+  pdns::trace::dnsdist::Tracer::Closer closer;
+  if (dnsResponse.ids.tracingEnabled) {
+    closer = dnsResponse.ids.d_OTTracer->openSpan("applyRulesToResponse", dnsResponse.ids.d_OTTracer->getLastSpanID());
+  }
   if (respRuleActions.empty()) {
     return true;
   }
@@ -511,8 +520,15 @@ bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>&
 
 bool processResponseAfterRules(PacketBuffer& response, DNSResponse& dnsResponse, [[maybe_unused]] bool muted)
 {
+  pdns::trace::dnsdist::Tracer::Closer closer;
+  if (dnsResponse.ids.tracingEnabled) {
+    closer = dnsResponse.ids.d_OTTracer->openSpan("processResponseAfterRules");
+  }
   bool zeroScope = false;
   if (!fixUpResponse(response, dnsResponse.ids.qname, dnsResponse.ids.origFlags, dnsResponse.ids.ednsAdded, dnsResponse.ids.ecsAdded, dnsResponse.ids.useZeroScope ? &zeroScope : nullptr)) {
+    if (dnsResponse.ids.tracingEnabled) {
+      dnsResponse.ids.d_OTTracer->setSpanAttribute(closer.getSpanID(), "result", AnyValue{"fixUpResponse->false"});
+    }
     return false;
   }
 
@@ -538,8 +554,13 @@ bool processResponseAfterRules(PacketBuffer& response, DNSResponse& dnsResponse,
       // if zeroScope, pass the pre-ECS hash-key and do not pass the subnet to the cache
       cacheKey = dnsResponse.ids.cacheKeyNoECS;
     }
-    dnsResponse.ids.packetCache->insert(cacheKey, zeroScope ? boost::none : dnsResponse.ids.subnet, dnsResponse.ids.cacheFlags, dnsResponse.ids.dnssecOK ? *dnsResponse.ids.dnssecOK : false, dnsResponse.ids.qname, dnsResponse.ids.qtype, dnsResponse.ids.qclass, response, dnsResponse.ids.forwardedOverUDP, dnsResponse.getHeader()->rcode, dnsResponse.ids.tempFailureTTL);
-
+    {
+      pdns::trace::dnsdist::Tracer::Closer cacheInsertCloser;
+      if (dnsResponse.ids.tracingEnabled) {
+        cacheInsertCloser = dnsResponse.ids.d_OTTracer->openSpan("packetCacheInsert", closer.getSpanID());
+      }
+      dnsResponse.ids.packetCache->insert(cacheKey, zeroScope ? boost::none : dnsResponse.ids.subnet, dnsResponse.ids.cacheFlags, dnsResponse.ids.dnssecOK ? *dnsResponse.ids.dnssecOK : false, dnsResponse.ids.qname, dnsResponse.ids.qtype, dnsResponse.ids.qclass, response, dnsResponse.ids.forwardedOverUDP, dnsResponse.getHeader()->rcode, dnsResponse.ids.tempFailureTTL);
+    }
     const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
     const auto& cacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules);
     if (!applyRulesToResponse(cacheInsertedRespRuleActions, dnsResponse)) {
@@ -568,6 +589,11 @@ bool processResponseAfterRules(PacketBuffer& response, DNSResponse& dnsResponse,
 
 bool processResponse(PacketBuffer& response, DNSResponse& dnsResponse, bool muted)
 {
+  pdns::trace::dnsdist::Tracer::Closer closer;
+  if (dnsResponse.ids.tracingEnabled) {
+    closer = dnsResponse.ids.d_OTTracer->openSpan("processResponse");
+  }
+
   const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
   const auto& respRuleActions = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::ResponseRules);
 
@@ -1021,6 +1047,7 @@ static bool applyRulesChainToQuery(const std::vector<dnsdist::rules::RuleAction>
 
 static bool applyRulesToQuery(DNSQuestion& dnsQuestion, const timespec& now)
 {
+  auto closer = dnsQuestion.ids.d_OTTracer->openSpan("applyRulesToQuery", dnsQuestion.ids.d_OTTracer->getLastSpanID());
   if (g_rings.shouldRecordQueries()) {
     g_rings.insertQuery(now, dnsQuestion.ids.origRemote, dnsQuestion.ids.qname, dnsQuestion.ids.qtype, dnsQuestion.getData().size(), *dnsQuestion.getHeader(), dnsQuestion.getProtocol());
   }
@@ -1426,9 +1453,18 @@ static ProcessQueryResult handleQueryTurnedIntoSelfAnsweredResponse(DNSQuestion&
 
 static ServerPolicy::SelectedBackend selectBackendForOutgoingQuery(DNSQuestion& dnsQuestion, const ServerPool& serverPool)
 {
+  auto closer = dnsQuestion.ids.d_OTTracer->openSpan("selectBackendForOutgoingQuery", dnsQuestion.ids.d_OTTracer->getLastSpanID());
+
   const auto& policy = serverPool.policy != nullptr ? *serverPool.policy : *dnsdist::configuration::getCurrentRuntimeConfiguration().d_lbPolicy;
   const auto& servers = serverPool.getServers();
-  return policy.getSelectedBackend(servers, dnsQuestion);
+  auto selectedBackend = policy.getSelectedBackend(servers, dnsQuestion);
+
+  if (dnsQuestion.ids.tracingEnabled) {
+    dnsQuestion.ids.d_OTTracer->setSpanAttribute(closer.getSpanID(), "backend.name", AnyValue{selectedBackend->getNameWithAddr()});
+    dnsQuestion.ids.d_OTTracer->setSpanAttribute(closer.getSpanID(), "backend.id", AnyValue{boost::uuids::to_string(selectedBackend->getID())});
+  }
+
+  return selectedBackend;
 }
 
 // NOLINTNEXTLINE(readability-function-cognitive-complexity): refactoring will be done in https://github.com/PowerDNS/pdns/pull/16124
@@ -1742,8 +1778,9 @@ std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion&
 
 ProcessQueryResult processQuery(DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend)
 {
-  const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
 
+  auto closer = dnsQuestion.ids.d_OTTracer->openSpan("processQuery", dnsQuestion.ids.d_OTTracer->getLastSpanID());
+  const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
   try {
     /* we need an accurate ("real") value for the response and
        to store into the IDS, but not for insertion into the
@@ -1774,6 +1811,8 @@ ProcessQueryResult processQuery(DNSQuestion& dnsQuestion, std::shared_ptr<Downst
 
 bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend)
 {
+  auto closer = dnsQuestion.ids.d_OTTracer->openSpan("assignOutgoingUDPQueryToBackend", dnsQuestion.ids.d_OTTracer->getLastSpanID());
+
   bool doh = dnsQuestion.ids.du != nullptr;
 
   bool failed = false;
@@ -1847,6 +1886,9 @@ static void processUDPQuery(ClientState& clientState, const struct msghdr* msgh,
   assert(responsesVect == nullptr || (queuedResponses != nullptr && respIOV != nullptr && respCBuf != nullptr));
   uint16_t queryId = 0;
   InternalQueryState ids;
+
+  auto closer = ids.d_OTTracer->openSpan("processUDPQuery");
+
   ids.cs = &clientState;
   ids.origRemote = remote;
   ids.hopRemote = remote;
index 2e83040bdb61f4bae93943edff9a5d0a25234d98..1ddd5797a4952234edb0357acfa765b7ef3a26b8 100644 (file)
@@ -169,6 +169,7 @@ common_sources += files(
   src_dir / 'dnsdist-metrics.cc',
   src_dir / 'dnsdist-nghttp2.cc',
   src_dir / 'dnsdist-nghttp2-in.cc',
+  src_dir / 'dnsdist-opentelemetry.cc',
   src_dir / 'dnsdist-protobuf.cc',
   src_dir / 'dnsdist-protocols.cc',
   src_dir / 'dnsdist-proxy-protocol.cc',
@@ -202,6 +203,7 @@ common_sources += files(
   src_dir / 'libssl.cc',
   src_dir / 'misc.cc',
   src_dir / 'protozero.cc',
+  src_dir / 'protozero-trace.cc',
   src_dir / 'proxy-protocol.cc',
   src_dir / 'qtype.cc',
   src_dir / 'remote_logger.cc',
diff --git a/pdns/dnsdistdist/protozero-trace.cc b/pdns/dnsdistdist/protozero-trace.cc
new file mode 120000 (symlink)
index 0000000..e55a75b
--- /dev/null
@@ -0,0 +1 @@
+../protozero-trace.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/protozero-trace.hh b/pdns/dnsdistdist/protozero-trace.hh
new file mode 120000 (symlink)
index 0000000..3352190
--- /dev/null
@@ -0,0 +1 @@
+../protozero-trace.hh
\ No newline at end of file
index 9bcbb5ec06ca727c7db61e49b12ac8084bc0901d..6141916921c1d47cbb8370400fad13c9eddfe653 100644 (file)
@@ -238,6 +238,13 @@ inline void random(SpanID& span)
   dns_random(span.data(), span.size());
 }
 
+inline SpanID randomSpanID()
+{
+  SpanID ret;
+  random(ret);
+  return ret;
+}
+
 inline void clear(TraceID& trace)
 {
   trace.fill(0);