Line data Source code
1 : #include "source/extensions/tracers/zipkin/span_context_extractor.h"
2 :
3 : #include "source/common/common/assert.h"
4 : #include "source/common/common/utility.h"
5 : #include "source/extensions/tracers/zipkin/span_context.h"
6 : #include "source/extensions/tracers/zipkin/zipkin_core_constants.h"
7 :
8 : namespace Envoy {
9 : namespace Extensions {
10 : namespace Tracers {
11 : namespace Zipkin {
12 : namespace {
13 : constexpr int FormatMaxLength = 32 + 1 + 16 + 3 + 16; // traceid128-spanid-1-parentid
14 0 : bool validSamplingFlags(char c) {
15 0 : if (c == '1' || c == '0' || c == 'd') {
16 0 : return true;
17 0 : }
18 0 : return false;
19 0 : }
20 :
21 0 : bool getSamplingFlags(char c, const Tracing::Decision tracing_decision) {
22 0 : if (validSamplingFlags(c)) {
23 0 : return c == '0' ? false : true;
24 0 : } else {
25 0 : return tracing_decision.traced;
26 0 : }
27 0 : }
28 :
29 : } // namespace
30 :
31 : SpanContextExtractor::SpanContextExtractor(Tracing::TraceContext& trace_context)
32 0 : : trace_context_(trace_context) {}
33 :
34 0 : SpanContextExtractor::~SpanContextExtractor() = default;
35 :
36 0 : bool SpanContextExtractor::extractSampled(const Tracing::Decision tracing_decision) {
37 0 : bool sampled(false);
38 0 : auto b3_header_entry = ZipkinCoreConstants::get().B3.get(trace_context_);
39 0 : if (b3_header_entry.has_value()) {
40 : // This is an implicitly untrusted header, so only the first value is used.
41 0 : absl::string_view b3 = b3_header_entry.value();
42 0 : int sampled_pos = 0;
43 0 : switch (b3.length()) {
44 0 : case 1:
45 0 : break;
46 0 : case 35: // 16 + 1 + 16 + 2
47 0 : sampled_pos = 34;
48 0 : break;
49 0 : case 51: // 32 + 1 + 16 + 2
50 0 : sampled_pos = 50;
51 0 : break;
52 0 : case 52: // 16 + 1 + 16 + 2 + 1 + 16
53 0 : sampled_pos = 34;
54 0 : break;
55 0 : case 68: // 32 + 1 + 16 + 2 + 1 + 16
56 0 : sampled_pos = 50;
57 0 : break;
58 0 : default:
59 0 : return tracing_decision.traced;
60 0 : }
61 0 : return getSamplingFlags(b3[sampled_pos], tracing_decision);
62 0 : }
63 :
64 0 : auto x_b3_sampled_entry = ZipkinCoreConstants::get().X_B3_SAMPLED.get(trace_context_);
65 0 : if (!x_b3_sampled_entry.has_value()) {
66 0 : return tracing_decision.traced;
67 0 : }
68 : // Checking if sampled flag has been specified. Also checking for 'true' value, as some old
69 : // zipkin tracers may still use that value, although should be 0 or 1.
70 : // This is an implicitly untrusted header, so only the first value is used.
71 0 : absl::string_view xb3_sampled = x_b3_sampled_entry.value();
72 0 : sampled = xb3_sampled == SAMPLED || xb3_sampled == "true";
73 0 : return sampled;
74 0 : }
75 :
76 0 : std::pair<SpanContext, bool> SpanContextExtractor::extractSpanContext(bool is_sampled) {
77 0 : if (ZipkinCoreConstants::get().B3.get(trace_context_).has_value()) {
78 0 : return extractSpanContextFromB3SingleFormat(is_sampled);
79 0 : }
80 0 : uint64_t trace_id(0);
81 0 : uint64_t trace_id_high(0);
82 0 : uint64_t span_id(0);
83 0 : uint64_t parent_id(0);
84 :
85 0 : auto b3_trace_id_entry = ZipkinCoreConstants::get().X_B3_TRACE_ID.get(trace_context_);
86 0 : auto b3_span_id_entry = ZipkinCoreConstants::get().X_B3_SPAN_ID.get(trace_context_);
87 0 : if (b3_span_id_entry.has_value() && b3_trace_id_entry.has_value()) {
88 : // Extract trace id - which can either be 128 or 64 bit. For 128 bit,
89 : // it needs to be divided into two 64 bit numbers (high and low).
90 : // This is an implicitly untrusted header, so only the first value is used.
91 0 : const std::string tid(b3_trace_id_entry.value());
92 0 : if (b3_trace_id_entry.value().size() == 32) {
93 0 : const std::string high_tid = tid.substr(0, 16);
94 0 : const std::string low_tid = tid.substr(16, 16);
95 0 : if (!StringUtil::atoull(high_tid.c_str(), trace_id_high, 16) ||
96 0 : !StringUtil::atoull(low_tid.c_str(), trace_id, 16)) {
97 0 : throw ExtractorException(
98 0 : fmt::format("Invalid traceid_high {} or tracid {}", high_tid.c_str(), low_tid.c_str()));
99 0 : }
100 0 : } else if (!StringUtil::atoull(tid.c_str(), trace_id, 16)) {
101 0 : throw ExtractorException(absl::StrCat("Invalid trace_id ", tid.c_str()));
102 0 : }
103 :
104 : // This is an implicitly untrusted header, so only the first value is used.
105 0 : const std::string spid(b3_span_id_entry.value());
106 0 : if (!StringUtil::atoull(spid.c_str(), span_id, 16)) {
107 0 : throw ExtractorException(absl::StrCat("Invalid span id ", spid.c_str()));
108 0 : }
109 :
110 0 : auto b3_parent_id_entry = ZipkinCoreConstants::get().X_B3_PARENT_SPAN_ID.get(trace_context_);
111 0 : if (b3_parent_id_entry.has_value() && !b3_parent_id_entry.value().empty()) {
112 : // This is an implicitly untrusted header, so only the first value is used.
113 0 : const std::string pspid(b3_parent_id_entry.value());
114 0 : if (!StringUtil::atoull(pspid.c_str(), parent_id, 16)) {
115 0 : throw ExtractorException(absl::StrCat("Invalid parent span id ", pspid.c_str()));
116 0 : }
117 0 : }
118 0 : } else {
119 0 : return {SpanContext(), false};
120 0 : }
121 :
122 0 : return {SpanContext(trace_id_high, trace_id, span_id, parent_id, is_sampled), true};
123 0 : }
124 :
125 : std::pair<SpanContext, bool>
126 0 : SpanContextExtractor::extractSpanContextFromB3SingleFormat(bool is_sampled) {
127 0 : auto b3_head_entry = ZipkinCoreConstants::get().B3.get(trace_context_);
128 0 : ASSERT(b3_head_entry.has_value());
129 : // This is an implicitly untrusted header, so only the first value is used.
130 0 : const std::string b3(b3_head_entry.value());
131 0 : if (!b3.length()) {
132 0 : throw ExtractorException("Invalid input: empty");
133 0 : }
134 :
135 0 : if (b3.length() == 1) { // possibly sampling flags
136 0 : if (validSamplingFlags(b3[0])) {
137 0 : return {SpanContext(), false};
138 0 : }
139 0 : throw ExtractorException(fmt::format("Invalid input: invalid sampling flag {}", b3[0]));
140 0 : }
141 :
142 0 : if (b3.length() < 16 + 1 + 16 /* traceid64-spanid */) {
143 0 : throw ExtractorException("Invalid input: truncated");
144 0 : } else if (b3.length() > FormatMaxLength) {
145 0 : throw ExtractorException("Invalid input: too long");
146 0 : }
147 :
148 0 : uint64_t trace_id(0);
149 0 : uint64_t trace_id_high(0);
150 0 : uint64_t span_id(0);
151 0 : uint64_t parent_id(0);
152 :
153 0 : uint64_t pos = 0;
154 :
155 0 : const std::string trace_id_str = b3.substr(pos, 16);
156 0 : if (b3[pos + 32] == '-') {
157 0 : if (!StringUtil::atoull(trace_id_str.c_str(), trace_id_high, 16)) {
158 0 : throw ExtractorException(
159 0 : fmt::format("Invalid input: invalid trace id high {}", trace_id_str.c_str()));
160 0 : }
161 0 : pos += 16;
162 0 : const std::string trace_id_low_str = b3.substr(pos, 16);
163 0 : if (!StringUtil::atoull(trace_id_low_str.c_str(), trace_id, 16)) {
164 0 : throw ExtractorException(
165 0 : fmt::format("Invalid input: invalid trace id {}", trace_id_low_str.c_str()));
166 0 : }
167 0 : } else {
168 0 : if (!StringUtil::atoull(trace_id_str.c_str(), trace_id, 16)) {
169 0 : throw ExtractorException(
170 0 : fmt::format("Invalid input: invalid trace id {}", trace_id_str.c_str()));
171 0 : }
172 0 : }
173 :
174 0 : pos += 16; // traceId ended
175 0 : if (!(b3[pos++] == '-')) {
176 0 : throw ExtractorException("Invalid input: not exists span id");
177 0 : }
178 :
179 0 : const std::string span_id_str = b3.substr(pos, 16);
180 0 : if (!StringUtil::atoull(span_id_str.c_str(), span_id, 16)) {
181 0 : throw ExtractorException(fmt::format("Invalid input: invalid span id {}", span_id_str.c_str()));
182 0 : }
183 0 : pos += 16; // spanId ended
184 :
185 0 : if (b3.length() > pos) {
186 : // If we are at this point, we have more than just traceId-spanId.
187 : // If the sampling field is present, we'll have a delimiter 2 characters from now. Ex "-1"
188 : // If it is absent, but a parent ID is (which is strange), we'll have at least 17 characters.
189 : // Therefore, if we have less than two characters, the input is truncated.
190 0 : if (b3.length() == (pos + 1)) {
191 0 : throw ExtractorException("Invalid input: truncated");
192 0 : }
193 :
194 0 : if (!(b3[pos++] == '-')) {
195 0 : throw ExtractorException("Invalid input: not exists sampling field");
196 0 : }
197 :
198 : // If our position is at the end of the string, or another delimiter is one character past our
199 : // position, try to read sampled status.
200 0 : if (b3.length() == pos + 1 || ((b3.length() >= pos + 2) && (b3[pos + 1] == '-'))) {
201 0 : if (!validSamplingFlags(b3[pos])) {
202 0 : throw ExtractorException(fmt::format("Invalid input: invalid sampling flag {}", b3[pos]));
203 0 : }
204 0 : pos++; // consume the sampled status
205 0 : } else {
206 0 : throw ExtractorException("Invalid input: truncated");
207 0 : }
208 :
209 0 : if (b3.length() > pos) {
210 : // If we are at this point, we should have a parent ID, encoded as "-[0-9a-f]{16}"
211 0 : if (b3.length() != pos + 17) {
212 0 : throw ExtractorException("Invalid input: truncated");
213 0 : }
214 :
215 0 : ASSERT(b3[pos] == '-');
216 0 : pos++;
217 :
218 0 : const std::string parent_id_str = b3.substr(pos, b3.length() - pos);
219 0 : if (!StringUtil::atoull(parent_id_str.c_str(), parent_id, 16)) {
220 0 : throw ExtractorException(
221 0 : fmt::format("Invalid input: invalid parent id {}", parent_id_str.c_str()));
222 0 : }
223 0 : }
224 0 : }
225 :
226 0 : return {SpanContext(trace_id_high, trace_id, span_id, parent_id, is_sampled), true};
227 0 : }
228 :
229 : } // namespace Zipkin
230 : } // namespace Tracers
231 : } // namespace Extensions
232 : } // namespace Envoy
|