Coverage Report

Created: 2026-03-08 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pdns/pdns/dnsdistdist/dnsdist-opentelemetry.cc
Line
Count
Source
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22
23
#include "dnsdist-opentelemetry.hh"
24
#include "misc.hh"
25
#include "dnsdist-ecs.hh"
26
27
#include <memory>
28
#include <vector>
29
30
#ifndef DISABLE_PROTOBUF
31
#include "protozero-trace.hh"
32
#endif
33
34
namespace pdns::trace::dnsdist
35
{
36
37
#ifndef DISABLE_PROTOBUF
38
static const KeyValue hostnameAttr{.key = "hostname", .value = {getHostname().value_or("")}};
39
#endif
40
41
TracesData Tracer::getTracesData()
42
0
{
43
#ifdef DISABLE_PROTOBUF
44
  return 0;
45
#else
46
0
  auto traceid = getTraceID();
47
0
  {
48
0
    auto data = d_data.read_only_lock();
49
0
    auto otTrace = pdns::trace::TracesData{
50
0
      .resource_spans = {
51
0
        {.resource = {
52
0
           .attributes = {
53
0
             {"service.name", {"dnsdist"}},
54
0
           }},
55
0
         .scope_spans = {{.scope = {
56
0
                            .name = "dnsdist/queryFromFrontend",
57
0
                            .version = PACKAGE_VERSION,
58
0
                            .attributes = {data->d_attributes.cbegin(), data->d_attributes.cend()},
59
0
                          },
60
0
                          .spans = {}}}}}};
61
62
0
    otTrace.resource_spans.at(0).scope_spans.at(0).scope.attributes.push_back(hostnameAttr);
63
64
0
    for (auto const& span : data->d_spans) {
65
0
      otTrace.resource_spans.at(0).scope_spans.at(0).spans.push_back(
66
0
        {
67
0
          .trace_id = traceid,
68
0
          .span_id = span.span_id == data->d_oldAndNewRootSpanID.oldID ? data->d_oldAndNewRootSpanID.newID : span.span_id,
69
0
          .parent_span_id = span.parent_span_id == data->d_oldAndNewRootSpanID.oldID ? data->d_oldAndNewRootSpanID.newID : span.parent_span_id,
70
0
          .name = span.name,
71
0
          .kind = pdns::trace::Span::SpanKind::SPAN_KIND_SERVER,
72
0
          .start_time_unix_nano = span.start_time_unix_nano,
73
0
          .end_time_unix_nano = span.end_time_unix_nano,
74
0
          .attributes = span.attributes,
75
0
        });
76
0
    }
77
0
    return otTrace;
78
0
  }
79
0
#endif
80
0
}
81
82
std::string Tracer::getOTProtobuf()
83
0
{
84
#ifdef DISABLE_PROTOBUF
85
  return 0;
86
#else
87
  // TODO: Should we close all spans?
88
0
  return getTracesData().encode();
89
0
#endif
90
0
}
91
92
SpanID Tracer::addSpan([[maybe_unused]] const std::string& name)
93
0
{
94
#ifdef DISABLE_PROTOBUF
95
  return 0;
96
#else
97
0
  return addSpan(name, getLastSpanID());
98
0
#endif
99
0
}
100
101
SpanID Tracer::addSpan([[maybe_unused]] const std::string& name, [[maybe_unused]] const SpanID& parentSpanID)
102
0
{
103
#ifdef DISABLE_PROTOBUF
104
  return 0;
105
#else
106
0
  auto spanID = pdns::trace::SpanID::getRandomSpanID();
107
0
  {
108
0
    auto data = d_data.lock();
109
0
    data->d_spans.push_back({
110
0
      .name = name,
111
0
      .span_id = spanID,
112
0
      .parent_span_id = parentSpanID,
113
0
      .start_time_unix_nano = pdns::trace::timestamp(),
114
0
      .end_time_unix_nano = 0,
115
0
      .attributes = {},
116
0
    });
117
118
0
    data->d_spanIDStack.emplace_back(spanID);
119
0
  }
120
0
  return spanID;
121
0
#endif
122
0
}
123
124
void Tracer::setTraceID([[maybe_unused]] const TraceID& traceID)
125
0
{
126
0
#ifndef DISABLE_PROTOBUF
127
0
  d_data.lock()->d_traceid = traceID;
128
0
#endif
129
0
}
130
131
void Tracer::setRootSpanID([[maybe_unused]] const SpanID& spanID)
132
0
{
133
0
#ifndef DISABLE_PROTOBUF
134
0
  SpanID oldRootSpanID{pdns::trace::s_emptySpanID};
135
0
  {
136
0
    auto data = d_data.read_only_lock();
137
0
    if (const auto& spans = data->d_spans; !spans.empty()) {
138
0
      auto iter = std::find_if(spans.cbegin(), spans.cend(), [](const auto& span) { return span.parent_span_id == pdns::trace::s_emptySpanID; });
139
0
      if (iter != spans.cend()) {
140
0
        oldRootSpanID = iter->span_id;
141
0
      }
142
0
    }
143
0
  }
144
145
0
  if (oldRootSpanID == pdns::trace::s_emptySpanID) {
146
0
    return;
147
0
  }
148
149
0
  {
150
0
    auto data = d_data.lock();
151
0
    data->d_oldAndNewRootSpanID.oldID = oldRootSpanID;
152
0
    data->d_oldAndNewRootSpanID.newID = spanID;
153
0
  }
154
0
#endif
155
0
}
156
157
// TODO: Figure out what to do with duplicate keys
158
bool Tracer::setTraceAttribute([[maybe_unused]] const std::string& key, [[maybe_unused]] const AnyValue& value)
159
0
{
160
#ifdef DISABLE_PROTOBUF
161
  // always successful
162
  return true;
163
#else
164
0
  d_data.lock()->d_attributes.push_back({key, value});
165
0
  return true;
166
0
#endif
167
0
}
168
169
void Tracer::closeSpan([[maybe_unused]] const SpanID& spanID)
170
0
{
171
0
#ifndef DISABLE_PROTOBUF
172
0
  auto data = d_data.lock();
173
0
  auto& spans = data->d_spans;
174
0
  auto spanIt = std::find_if(
175
0
    spans.rbegin(),
176
0
    spans.rend(),
177
0
    [&spanID](const miniSpan& span) { return span.span_id == spanID; });
178
0
  if (spanIt != spans.rend() && spanIt->end_time_unix_nano == 0) {
179
0
    spanIt->end_time_unix_nano = pdns::trace::timestamp();
180
181
    // Only closers are allowed, so this can never happen
182
0
    assert(!data->d_spanIDStack.empty());
183
0
    assert(data->d_spanIDStack.back() == spanID);
184
0
    data->d_spanIDStack.pop_back();
185
0
  }
186
0
#endif
187
0
}
188
189
void Tracer::setRootSpanAttribute([[maybe_unused]] const std::string& key, [[maybe_unused]] const AnyValue& value)
190
0
{
191
0
#ifndef DISABLE_PROTOBUF
192
0
  setSpanAttribute(getRootSpanID(), key, value);
193
0
#endif
194
0
}
195
196
// TODO: Figure out what to do with duplicate keys
197
void Tracer::setSpanAttribute([[maybe_unused]] const SpanID& spanid, [[maybe_unused]] const std::string& key, [[maybe_unused]] const AnyValue& value)
198
0
{
199
0
#ifndef DISABLE_PROTOBUF
200
0
  auto data = d_data.lock();
201
0
  auto& spans = data->d_spans;
202
0
  if (auto iter = std::find_if(spans.rbegin(),
203
0
                               spans.rend(),
204
0
                               [&spanid](const auto& span) { return span.span_id == spanid; });
205
0
      iter != spans.rend()) {
206
0
    iter->attributes.push_back({key, value});
207
0
  }
208
0
#endif
209
0
}
210
211
SpanID Tracer::getRootSpanID()
212
0
{
213
#ifdef DISABLE_PROTOBUF
214
  return 0;
215
#else
216
0
  auto data = d_data.read_only_lock();
217
0
  if (data->d_oldAndNewRootSpanID.newID != pdns::trace::s_emptySpanID) {
218
0
    return data->d_oldAndNewRootSpanID.newID;
219
0
  }
220
221
0
  if (auto& spans = data->d_spans; !spans.empty()) {
222
0
    auto iter = std::find_if(spans.cbegin(), spans.cend(), [](const auto& span) { return span.parent_span_id == pdns::trace::s_emptySpanID; });
223
0
    if (iter != spans.cend()) {
224
0
      return iter->span_id;
225
0
    }
226
0
  }
227
0
  return pdns::trace::s_emptySpanID;
228
0
#endif
229
0
}
230
231
SpanID Tracer::getLastSpanID()
232
0
{
233
#ifdef DISABLE_PROTOBUF
234
  return 0;
235
#else
236
0
  auto data = d_data.read_only_lock();
237
0
  if (data->d_spanIDStack.empty()) {
238
0
    return pdns::trace::s_emptySpanID;
239
0
  }
240
0
  if (data->d_spanIDStack.size() == 1 && data->d_spanIDStack.front() == data->d_oldAndNewRootSpanID.oldID) {
241
0
    return data->d_oldAndNewRootSpanID.newID;
242
0
  }
243
0
  return data->d_spanIDStack.back();
244
0
#endif
245
0
}
246
247
SpanID Tracer::getLastSpanIDForName([[maybe_unused]] const std::string& name)
248
0
{
249
#ifdef DISABLE_PROTOBUF
250
  return 0;
251
#else
252
0
  auto data = d_data.read_only_lock();
253
0
  if (auto& spans = data->d_spans; !spans.empty()) {
254
0
    if (auto iter = std::find_if(spans.rbegin(),
255
0
                                 spans.rend(),
256
0
                                 [name](const miniSpan& span) { return span.name == name; });
257
0
        iter != spans.rend()) {
258
0
      return iter->span_id;
259
0
    }
260
0
  }
261
0
  return pdns::trace::s_emptySpanID;
262
0
#endif
263
0
}
264
265
TraceID Tracer::getTraceID()
266
0
{
267
#ifdef DISABLE_PROTOBUF
268
  return 0;
269
#else
270
0
  auto data = d_data.lock();
271
0
  auto& traceID = data->d_traceid;
272
0
  if (traceID == pdns::trace::s_emptyTraceID) {
273
0
    traceID.makeRandom();
274
0
  }
275
0
  return traceID;
276
0
#endif
277
0
}
278
279
Tracer::Closer Tracer::getCloser([[maybe_unused]] const SpanID& spanid)
280
0
{
281
#ifdef DISABLE_PROTOBUF
282
  return Tracer::Closer();
283
#else
284
0
  return {shared_from_this(), spanid};
285
0
#endif
286
0
}
287
288
Tracer::Closer Tracer::openSpan([[maybe_unused]] const std::string& name)
289
0
{
290
#ifdef DISABLE_PROTOBUF
291
  return Tracer::Closer();
292
#else
293
0
  auto spanid = addSpan(name);
294
0
  return getCloser(spanid);
295
0
#endif
296
0
}
297
298
Tracer::Closer Tracer::openSpan([[maybe_unused]] const std::string& name, [[maybe_unused]] const SpanID& parentSpanID)
299
0
{
300
#ifdef DISABLE_PROTOBUF
301
  return Tracer::Closer();
302
#else
303
0
  auto spanid = addSpan(name, parentSpanID);
304
0
  return getCloser(spanid);
305
0
#endif
306
0
}
307
308
SpanID Tracer::Closer::getSpanID() const
309
0
{
310
#ifdef DISABLE_PROTOBUF
311
  return 0;
312
#else
313
0
  return d_spanID;
314
0
#endif
315
0
}
316
317
void Tracer::Closer::setAttribute([[maybe_unused]] const std::string& key, [[maybe_unused]] const AnyValue& value)
318
0
{
319
#ifdef DISABLE_PROTOBUF
320
  return;
321
#else
322
0
  return d_tracer->setSpanAttribute(d_spanID, key, value);
323
0
#endif
324
0
}
325
326
std::vector<uint8_t> makeEDNSTraceParentOption(std::shared_ptr<Tracer> tracer)
327
0
{
328
0
  std::vector<uint8_t> ret;
329
0
#ifndef DISABLE_PROTOBUF
330
0
  if (tracer == nullptr) {
331
0
    return ret;
332
0
  }
333
0
  ret.reserve(27);
334
0
  ret.push_back(0); // Version
335
0
  ret.push_back(0); // Reserved
336
0
  auto traceId = tracer->getTraceID();
337
0
  ret.insert(ret.end(), traceId.begin(), traceId.end());
338
0
  auto spanId = tracer->getLastSpanID();
339
0
  ret.insert(ret.end(), spanId.begin(), spanId.end());
340
0
  ret.push_back(0); // Flags
341
0
#endif
342
0
  return ret;
343
0
}
344
345
bool addTraceparentEdnsOptionToPacketBuffer(PacketBuffer& origBuf, const std::shared_ptr<Tracer>& tracer, const size_t qnameWireLength, const size_t proxyProtocolPayloadSize, const uint16_t traceparentOptionCode, const bool isTCP)
346
0
{
347
0
#ifndef DISABLE_PROTOBUF
348
0
  if (tracer == nullptr) {
349
0
    return false;
350
0
  }
351
  // buf contains the whole DNS query without PROXY protocol and TCP length header
352
0
  PacketBuffer buf{origBuf.begin() + proxyProtocolPayloadSize + (isTCP ? 2 : 0), origBuf.end()};
353
354
0
  uint16_t optRDPosition;
355
0
  size_t remaining;
356
0
  bool queryHadEdns = ::dnsdist::getEDNSOptionsStart(buf, qnameWireLength, &optRDPosition, &remaining) == 0;
357
0
  if (queryHadEdns) {
358
0
    size_t optLen = buf.size() - optRDPosition - remaining;
359
0
    removeEDNSOptionFromOPT(reinterpret_cast<char*>(buf.data() + optRDPosition), &optLen, traceparentOptionCode);
360
0
  }
361
362
0
  auto opt = pdns::trace::dnsdist::makeEDNSTraceParentOption(tracer);
363
0
  bool ednsAdded{false};
364
0
  bool optionAdded{false};
365
0
  uint16_t maxEdnsSize = queryHadEdns ? (uint16_t)(buf.at(optRDPosition - 6) << 8) + buf.at(optRDPosition - 5) : 512;
366
0
  setEDNSOption(buf, traceparentOptionCode, std::string(opt.begin(), opt.end()), isTCP ? std::numeric_limits<uint16_t>().max() : maxEdnsSize, ednsAdded, optionAdded);
367
368
0
  if (isTCP) {
369
0
    const std::array<uint8_t, 2> sizeBytes{static_cast<uint8_t>(buf.size() / 256), static_cast<uint8_t>(buf.size() % 256)};
370
0
    buf.insert(buf.begin(), sizeBytes.begin(), sizeBytes.end());
371
0
  }
372
373
  // Resize the buffer to remove the existing packet, but keep any PROXYv2 data
374
0
  origBuf.resize(proxyProtocolPayloadSize);
375
  // Insert the new query into the buffer
376
0
  origBuf.insert(origBuf.end(), buf.begin(), buf.end());
377
378
0
  return ednsAdded;
379
#else
380
  return false;
381
#endif
382
0
}
383
384
} // namespace pdns::trace::dnsdist