Skip to content

Commit 23291df

Browse files
grcevskiMrAlias
andauthored
Switch to using per header field parsing for Go versions >= 1.24 (#1636)
* switch to using per header field parsing * address review feedback * update change log * fix lint issue * add depends on for the new probe * Update internal/pkg/instrumentation/bpf/net/http/server/probe.go Co-authored-by: Tyler Yahn <[email protected]> * update change log * Update changelog after 2.0 --------- Co-authored-by: Tyler Yahn <[email protected]>
1 parent 85b7615 commit 23291df

File tree

7 files changed

+134
-12
lines changed

7 files changed

+134
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
88

99
## [Unreleased]
1010

11+
### Added
12+
- Preemptively update instrumentation for `net/http` to support Go `1.24` when swiss maps are going to be added. ([#1636](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1636))
13+
1114
## [v0.20.0] - 2025-01-29
1215

1316
### Added

internal/include/utils.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ static __always_inline bool bpf_memcmp(char *s1, char *s2, s32 size)
2020
return true;
2121
}
2222

23+
// assumes s2 is all lowercase
24+
static __always_inline int bpf_memicmp(const char *s1, const char *s2, s32 size) {
25+
for (int i = 0; i < size; i++) {
26+
if (s1[i] != s2[i] && s1[i] != (s2[i] - 32)) // compare with each uppercase character
27+
{
28+
return i + 1;
29+
}
30+
}
31+
32+
return 0;
33+
}
34+
2335
static __always_inline void generate_random_bytes(unsigned char *buff, u32 size)
2436
{
2537
for (int i = 0; i < (size / 4); i++)

internal/pkg/instrumentation/bpf/net/http/server/bpf/probe.bpf.c

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ struct
5050
__uint(max_entries, MAX_CONCURRENT);
5151
} http_server_uprobes SEC(".maps");
5252

53+
struct
54+
{
55+
__uint(type, BPF_MAP_TYPE_LRU_HASH);
56+
__type(key, void *);
57+
__type(value, struct span_context);
58+
__uint(max_entries, MAX_CONCURRENT);
59+
} http_server_context_headers SEC(".maps");
60+
5361
struct
5462
{
5563
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
@@ -84,11 +92,13 @@ volatile const bool pattern_path_supported;
8492
// In case pattern handlers are supported the following offsets will be used:
8593
volatile const u64 req_pat_pos;
8694
volatile const u64 pat_str_pos;
95+
// A flag indicating whether the Go version is using swiss maps
96+
volatile const bool swiss_maps_used;
8797

8898
// Extracts the span context from the request headers by looking for the 'traceparent' header.
8999
// Fills the parent_span_context with the extracted span context.
90100
// Returns 0 on success, negative value on error.
91-
static __always_inline long extract_context_from_req_headers(void *headers_ptr_ptr, struct span_context *parent_span_context)
101+
static __always_inline long extract_context_from_req_headers_go_map(void *headers_ptr_ptr, struct span_context *parent_span_context)
92102
{
93103
void *headers_ptr;
94104
long res;
@@ -178,6 +188,23 @@ static __always_inline long extract_context_from_req_headers(void *headers_ptr_p
178188
return -1;
179189
}
180190

191+
static __always_inline long extract_context_from_req_headers_pre_parsed(void *key, struct span_context *parent_span_context) {
192+
struct span_context *parsed_header_context = bpf_map_lookup_elem(&http_server_context_headers, &key);
193+
if (!parsed_header_context) {
194+
return -1;
195+
}
196+
197+
__builtin_memcpy(parent_span_context, parsed_header_context, sizeof(struct span_context));
198+
return 0;
199+
}
200+
201+
static __always_inline long extract_context_from_req_headers(void *key, struct span_context *parent_span_context) {
202+
if (swiss_maps_used) {
203+
return extract_context_from_req_headers_pre_parsed(key, parent_span_context);
204+
}
205+
return extract_context_from_req_headers_go_map(key, parent_span_context);
206+
}
207+
181208
static __always_inline void read_go_string(void *base, int offset, char *output, int maxLen, const char *errorMsg) {
182209
void *ptr = (void *)(base + offset);
183210
if (!get_go_string_from_user_ptr(ptr, output, maxLen)) {
@@ -225,8 +252,17 @@ int uprobe_serverHandler_ServeHTTP(struct pt_regs *ctx)
225252
.psc = &http_server_span->psc,
226253
.sc = &http_server_span->sc,
227254
.get_parent_span_context_fn = extract_context_from_req_headers,
228-
.get_parent_span_context_arg = (void*)(req_ptr + headers_ptr_pos),
229255
};
256+
257+
// If Go is using swiss maps, we currently rely on the uretprobe setup
258+
// on readContinuedLineSlice to store the parsed value in a map, which
259+
// we query with the same goroutine/context key.
260+
if (swiss_maps_used) {
261+
start_span_params.get_parent_span_context_arg = key;
262+
} else {
263+
start_span_params.get_parent_span_context_arg = (void*)(req_ptr + headers_ptr_pos);
264+
}
265+
230266
start_span(&start_span_params);
231267

232268
bpf_map_update_elem(&http_server_uprobes, &key, uprobe_data, 0);
@@ -246,6 +282,7 @@ int uprobe_serverHandler_ServeHTTP_Returns(struct pt_regs *ctx) {
246282
struct uprobe_data_t *uprobe_data = bpf_map_lookup_elem(&http_server_uprobes, &key);
247283
if (uprobe_data == NULL) {
248284
bpf_printk("uprobe/HandlerFunc_ServeHTTP_Returns: entry_state is NULL");
285+
bpf_map_delete_elem(&http_server_context_headers, &key);
249286
return 0;
250287
}
251288

@@ -280,5 +317,31 @@ int uprobe_serverHandler_ServeHTTP_Returns(struct pt_regs *ctx) {
280317

281318
stop_tracking_span(&http_server_span->sc, &http_server_span->psc);
282319
bpf_map_delete_elem(&http_server_uprobes, &key);
320+
bpf_map_delete_elem(&http_server_context_headers, &key);
321+
return 0;
322+
}
323+
324+
// This instrumentation attaches uprobe to the following function:
325+
// func (r *Reader) readContinuedLineSlice(lim int64, validateFirstLine func([]byte) error) ([]byte, error) {
326+
SEC("uprobe/textproto_Reader_readContinuedLineSlice")
327+
int uprobe_textproto_Reader_readContinuedLineSlice_Returns(struct pt_regs *ctx) {
328+
struct go_iface go_context = {0};
329+
get_Go_context(ctx, 4, ctx_ptr_pos, false, &go_context);
330+
void *key = get_consistent_key(ctx, go_context.data);
331+
332+
u64 len = (u64)GO_PARAM2(ctx);
333+
u8 *buf = (u8 *)GO_PARAM1(ctx);
334+
335+
if (len >= (W3C_KEY_LENGTH + W3C_VAL_LENGTH + 2)) {
336+
u8 temp[W3C_KEY_LENGTH + W3C_VAL_LENGTH + 2];
337+
bpf_probe_read(temp, sizeof(temp), buf);
338+
339+
if (!bpf_memicmp((const char *)temp, "traceparent: ", W3C_KEY_LENGTH + 2)) {
340+
struct span_context parent_span_context = {};
341+
w3c_string_to_span_context((char *)(temp + W3C_KEY_LENGTH + 2), &parent_span_context);
342+
bpf_map_update_elem(&http_server_context_headers, &key, &parent_span_context, BPF_ANY);
343+
}
344+
}
345+
283346
return 0;
284347
}

internal/pkg/instrumentation/bpf/net/http/server/bpf_arm64_bpfel.go

Lines changed: 12 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/pkg/instrumentation/bpf/net/http/server/bpf_x86_bpfel.go

Lines changed: 12 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/pkg/instrumentation/bpf/net/http/server/probe.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,19 @@ import (
2828

2929
const (
3030
// pkg is the package being instrumented.
31-
pkg = "net/http"
31+
pkg = "net/http"
32+
minGoSwissMaps = "1.24.0"
3233
)
3334

35+
var goWithSwissMaps = probe.PackageConstrainst{
36+
Package: "std",
37+
Constraints: version.MustConstraints(
38+
version.NewConstraint(">= " + minGoSwissMaps),
39+
),
40+
// Don't warn, we have a backup path.
41+
FailureMode: probe.FailureModeIgnore,
42+
}
43+
3444
// New returns a new [probe.Probe].
3545
func New(logger *slog.Logger, version string) probe.Probe {
3646
id := probe.ID{
@@ -102,13 +112,22 @@ func New(logger *slog.Logger, version string) probe.Probe {
102112
MinVersion: patternPathMinVersion,
103113
},
104114
patternPathSupportedConst{},
115+
swissMapsUsedConst{},
105116
},
106117
Uprobes: []*probe.Uprobe{
107118
{
108119
Sym: "net/http.serverHandler.ServeHTTP",
109120
EntryProbe: "uprobe_serverHandler_ServeHTTP",
110121
ReturnProbe: "uprobe_serverHandler_ServeHTTP_Returns",
111122
},
123+
{
124+
Sym: "net/textproto.(*Reader).readContinuedLineSlice",
125+
ReturnProbe: "uprobe_textproto_Reader_readContinuedLineSlice_Returns",
126+
PackageConstrainsts: []probe.PackageConstrainst{
127+
goWithSwissMaps,
128+
},
129+
DependsOn: []string{"net/http.serverHandler.ServeHTTP"},
130+
},
112131
},
113132
SpecFn: loadBpf,
114133
},
@@ -130,6 +149,14 @@ func (c patternPathSupportedConst) InjectOption(td *process.TargetDetails) (inje
130149
return inject.WithKeyValue("pattern_path_supported", isPatternPathSupported), nil
131150
}
132151

152+
type swissMapsUsedConst struct{}
153+
154+
func (c swissMapsUsedConst) InjectOption(td *process.TargetDetails) (inject.Option, error) {
155+
minGoSwissMapsVersion := version.Must(version.NewVersion(minGoSwissMaps))
156+
isUsingGoSwissMaps := td.GoVersion.GreaterThanOrEqual(minGoSwissMapsVersion)
157+
return inject.WithKeyValue("swiss_maps_used", isUsingGoSwissMaps), nil
158+
}
159+
133160
// event represents an event in an HTTP server during an HTTP
134161
// request-response.
135162
type event struct {

internal/pkg/instrumentation/probe/probe.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func (i *Base[BPFObj, BPFEvent]) loadUprobes(exec *link.Executable, td *process.
188188
logFn = i.Logger.Warn
189189
default:
190190
// Unknown and FailureModeError.
191-
return fmt.Errorf("uprobe %s package constraint (%s) not meet", up.Sym, pc.Constraints.String())
191+
return fmt.Errorf("uprobe %s package constraint (%s) not met, version %v", up.Sym, pc.Constraints.String(), td.Modules[pc.Package])
192192
}
193193

194194
logFn(
@@ -197,6 +197,7 @@ func (i *Base[BPFObj, BPFEvent]) loadUprobes(exec *link.Executable, td *process.
197197
"symbol", up.Sym,
198198
"package", pc.Package,
199199
"constraint", pc.Constraints.String(),
200+
"version", td.Modules[pc.Package],
200201
)
201202

202203
skip = true

0 commit comments

Comments
 (0)