class SetTraceAction : public DNSAction
{
public:
- SetTraceAction(bool value) :
- d_value{value} {};
+ SetTraceAction(bool value, std::optional<bool> useIncomingTraceID, std::optional<short unsigned int> incomingTraceIDOptionCode) :
+ d_value{value}, d_useIncomingTraceID(useIncomingTraceID), d_incomingTraceIDOptionCode(incomingTraceIDOptionCode) {};
DNSAction::Action operator()([[maybe_unused]] DNSQuestion* dnsquestion, [[maybe_unused]] std::string* ruleresult) const override
{
vinfolog("SetTraceAction called, but OpenTelemetry tracing is globally disabled. Did you forget to call setOpenTelemetryTracing?");
return Action::None;
}
+ dnsquestion->ids.tracingEnabled = d_value;
if (d_value) {
tracer->setRootSpanAttribute("query.qname", AnyValue{dnsquestion->ids.qname.toStringNoDot()});
tracer->setRootSpanAttribute("query.qtype", AnyValue{QType(dnsquestion->ids.qtype).toString()});
tracer->setRootSpanAttribute("query.remote.address", AnyValue{dnsquestion->ids.origRemote.toString()});
tracer->setRootSpanAttribute("query.remote.port", AnyValue{dnsquestion->ids.origRemote.getPort()});
+ if (d_useIncomingTraceID.value_or(false)) {
+ if (dnsquestion->ednsOptions == nullptr && !parseEDNSOptions(*dnsquestion)) {
+ // Maybe parsed, but no EDNS found
+ return Action::None;
+ }
+ if (dnsquestion->ednsOptions == nullptr) {
+ // Parsing failed, log a warning and return
+ vinfolog("parsing EDNS options failed while looking for OpenTelemetry Trace ID");
+ return Action::None;
+ }
+ pdns::trace::TraceID traceID;
+ pdns::trace::SpanID spanID;
+ if (pdns::trace::extractOTraceIDs(*(dnsquestion->ednsOptions), EDNSOptionCode::EDNSOptionCodeEnum(d_incomingTraceIDOptionCode.value_or(EDNSOptionCode::OTTRACEIDS)), traceID, spanID)) {
+ tracer->setTraceID(traceID);
+ if (spanID != pdns::trace::s_emptySpanID) {
+ tracer->setRootSpanID(spanID);
+ }
+ }
+ }
}
- dnsquestion->ids.tracingEnabled = d_value;
#endif
return Action::None;
}
private:
bool d_value;
+ std::optional<bool> d_useIncomingTraceID;
+ std::optional<short unsigned int> d_incomingTraceIDOptionCode;
};
class SNMPTrapAction : public DNSAction
#!/usr/bin/env python
import base64
-import dns
+import binascii
+import dns.message
+import dns.rrset
+import dns.rdataclass
+import dns.rdatatype
+import dns.edns
import time
import opentelemetry.proto.trace.v1.trace_pb2
self.assertTrue(msg)
self.assertTrue(msg.HasField("openTelemetry"))
- def sendQueryAndGetProtobuf(self, useTCP=False):
+ def sendQueryAndGetProtobuf(
+ self, useTCP=False, traceID="", spanID="", ednsTraceIDOpt=65500
+ ):
name = "query.ot.tests.powerdns.com."
target = "target.ot.tests.powerdns.com."
query = dns.message.make_query(name, "A", "IN")
+
+ if traceID != "":
+ ottrace = dns.edns.GenericOption(str(ednsTraceIDOpt), "\x00\x00")
+ ottrace.data += binascii.a2b_hex(traceID)
+ if spanID != "":
+ ottrace.data += binascii.a2b_hex(spanID)
+ query = dns.message.make_query(
+ name, "A", "IN", use_edns=True, options=[ottrace]
+ )
+
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(
class DNSDistOpenTelemetryProtobufBaseTest(DNSDistOpenTelemetryProtobufTest):
- def doTest(self, wasDelayed=False, useTCP=False):
- msg = self.sendQueryAndGetProtobuf(useTCP)
+ def doTest(self, wasDelayed=False, useTCP=False, traceID="", spanID=""):
+ msg = self.sendQueryAndGetProtobuf(useTCP, traceID, spanID)
self.assertTrue(msg.HasField("openTelemetryTraceID"))
self.assertTrue(msg.openTelemetryTraceID != "")
+ if traceID != "":
+ self.assertEqual(msg.openTelemetryTraceID, binascii.a2b_hex(traceID))
+
self.assertTrue(msg.HasField("openTelemetryData"))
traces_data = opentelemetry.proto.trace.v1.trace_pb2.TracesData()
traces_data.ParseFromString(msg.openTelemetryData)
f"span {msg_span} does not have the trace id {traceId} of the protobuf message",
)
+ if spanID != "":
+ for span in ot_data["resource_spans"][0]["scope_spans"][0]["spans"]:
+ if span["parent_span_id"] == binascii.a2b_hex("0000000000000000"):
+ self.assertEqual(binascii.a2b_hex(spanID), span["span_id"])
+ break
+
class TestOpenTelemetryTracingBaseYAML(DNSDistOpenTelemetryProtobufBaseTest):
_yaml_config_params = [
self.doTest(wasDelayed=True, useTCP=True)
+class TestOpenTelemetryTracingUseIncomingYAML(DNSDistOpenTelemetryProtobufBaseTest):
+ _yaml_config_params = [
+ "_testServerPort",
+ "_protobufServerPort",
+ ]
+ _yaml_config_template = """---
+logging:
+ open_telemetry_tracing: true
+
+backends:
+ - address: 127.0.0.1:%d
+ protocol: Do53
+
+remote_logging:
+ protobuf_loggers:
+ - name: pblog
+ address: 127.0.0.1:%d
+
+query_rules:
+ - name: Enable tracing
+ selector:
+ type: All
+ action:
+ type: SetTrace
+ value: true
+ use_incoming_traceid: true
+
+response_rules:
+ - name: Do PB logging
+ selector:
+ type: All
+ action:
+ type: RemoteLog
+ logger_name: pblog
+"""
+
+ def testNoTraceID(self):
+ self.doTest()
+
+ def testOnlyTraceID(self):
+ self.doTest(traceID="0123456789ABCDEF0123456789ABCDEF")
+
+ def testTraceIDAndSpanID(self):
+ self.doTest(
+ traceID="0123456789ABCDEF0123456789ABCDEF",
+ spanID="FEDCBA9876543210",
+ )
+
+
+class TestOpenTelemetryTracingUseIncomingLua(DNSDistOpenTelemetryProtobufBaseTest):
+ _config_params = [
+ "_testServerPort",
+ "_protobufServerPort",
+ ]
+ _config_template = """
+newServer{address="127.0.0.1:%d"}
+rl = newRemoteLogger('127.0.0.1:%d')
+setOpenTelemetryTracing(true)
+
+addAction(AllRule(), SetTraceAction(true, true), {name="Enable tracing"})
+addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {}, {}, false), {name="Do PB logging"})
+"""
+
+ def testNoTraceID(self):
+ self.doTest()
+
+ def testOnlyTraceID(self):
+ self.doTest(traceID="0123456789ABCDEF0123456789ABCDEF")
+
+ def testTraceIDAndSpanID(self):
+ self.doTest(
+ traceID="0123456789ABCDEF0123456789ABCDEF",
+ spanID="FEDCBA9876543210",
+ )
+
+
class DNSDistOpenTelemetryProtobufNoOTDataTest(DNSDistOpenTelemetryProtobufTest):
def doTest(self):
msg = self.sendQueryAndGetProtobuf()