Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to using per header field parsing for Go versions >= 1.24 #1636

Merged
merged 11 commits into from
Jan 30, 2025
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http

## [Unreleased]

### Added
- 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))

## [v0.20.0] - 2025-01-29

### Added
Expand Down
12 changes: 12 additions & 0 deletions internal/include/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ static __always_inline bool bpf_memcmp(char *s1, char *s2, s32 size)
return true;
}

// assumes s2 is all lowercase
static __always_inline int bpf_memicmp(const char *s1, const char *s2, s32 size) {
for (int i = 0; i < size; i++) {
if (s1[i] != s2[i] && s1[i] != (s2[i] - 32)) // compare with each uppercase character
{
return i + 1;
}
}

return 0;
}

static __always_inline void generate_random_bytes(unsigned char *buff, u32 size)
{
for (int i = 0; i < (size / 4); i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ struct
__uint(max_entries, MAX_CONCURRENT);
} http_server_uprobes SEC(".maps");

struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__type(key, void *);
__type(value, struct span_context);
__uint(max_entries, MAX_CONCURRENT);
} http_server_context_headers SEC(".maps");

struct
{
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
Expand Down Expand Up @@ -84,11 +92,13 @@ volatile const bool pattern_path_supported;
// In case pattern handlers are supported the following offsets will be used:
volatile const u64 req_pat_pos;
volatile const u64 pat_str_pos;
// A flag indicating whether the Go version is using swiss maps
volatile const bool swiss_maps_used;

// Extracts the span context from the request headers by looking for the 'traceparent' header.
// Fills the parent_span_context with the extracted span context.
// Returns 0 on success, negative value on error.
static __always_inline long extract_context_from_req_headers(void *headers_ptr_ptr, struct span_context *parent_span_context)
static __always_inline long extract_context_from_req_headers_go_map(void *headers_ptr_ptr, struct span_context *parent_span_context)
{
void *headers_ptr;
long res;
Expand Down Expand Up @@ -178,6 +188,23 @@ static __always_inline long extract_context_from_req_headers(void *headers_ptr_p
return -1;
}

static __always_inline long extract_context_from_req_headers_pre_parsed(void *key, struct span_context *parent_span_context) {
struct span_context *parsed_header_context = bpf_map_lookup_elem(&http_server_context_headers, &key);
if (!parsed_header_context) {
return -1;
}

__builtin_memcpy(parent_span_context, parsed_header_context, sizeof(struct span_context));
return 0;
}

static __always_inline long extract_context_from_req_headers(void *key, struct span_context *parent_span_context) {
if (swiss_maps_used) {
return extract_context_from_req_headers_pre_parsed(key, parent_span_context);
}
return extract_context_from_req_headers_go_map(key, parent_span_context);
}

static __always_inline void read_go_string(void *base, int offset, char *output, int maxLen, const char *errorMsg) {
void *ptr = (void *)(base + offset);
if (!get_go_string_from_user_ptr(ptr, output, maxLen)) {
Expand Down Expand Up @@ -225,8 +252,17 @@ int uprobe_serverHandler_ServeHTTP(struct pt_regs *ctx)
.psc = &http_server_span->psc,
.sc = &http_server_span->sc,
.get_parent_span_context_fn = extract_context_from_req_headers,
.get_parent_span_context_arg = (void*)(req_ptr + headers_ptr_pos),
};

// If Go is using swiss maps, we currently rely on the uretprobe setup
// on readContinuedLineSlice to store the parsed value in a map, which
// we query with the same goroutine/context key.
if (swiss_maps_used) {
start_span_params.get_parent_span_context_arg = key;
} else {
start_span_params.get_parent_span_context_arg = (void*)(req_ptr + headers_ptr_pos);
}

start_span(&start_span_params);

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

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

stop_tracking_span(&http_server_span->sc, &http_server_span->psc);
bpf_map_delete_elem(&http_server_uprobes, &key);
bpf_map_delete_elem(&http_server_context_headers, &key);
return 0;
}

// This instrumentation attaches uprobe to the following function:
// func (r *Reader) readContinuedLineSlice(lim int64, validateFirstLine func([]byte) error) ([]byte, error) {
SEC("uprobe/textproto_Reader_readContinuedLineSlice")
int uprobe_textproto_Reader_readContinuedLineSlice_Returns(struct pt_regs *ctx) {
struct go_iface go_context = {0};
get_Go_context(ctx, 4, ctx_ptr_pos, false, &go_context);
void *key = get_consistent_key(ctx, go_context.data);

u64 len = (u64)GO_PARAM2(ctx);
u8 *buf = (u8 *)GO_PARAM1(ctx);

if (len >= (W3C_KEY_LENGTH + W3C_VAL_LENGTH + 2)) {
u8 temp[W3C_KEY_LENGTH + W3C_VAL_LENGTH + 2];
bpf_probe_read(temp, sizeof(temp), buf);

if (!bpf_memicmp((const char *)temp, "traceparent: ", W3C_KEY_LENGTH + 2)) {
struct span_context parent_span_context = {};
w3c_string_to_span_context((char *)(temp + W3C_KEY_LENGTH + 2), &parent_span_context);
bpf_map_update_elem(&http_server_context_headers, &key, &parent_span_context, BPF_ANY);
}
}

return 0;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 28 additions & 1 deletion internal/pkg/instrumentation/bpf/net/http/server/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,19 @@ import (

const (
// pkg is the package being instrumented.
pkg = "net/http"
pkg = "net/http"
minGoSwissMaps = "1.24.0"
)

var goWithSwissMaps = probe.PackageConstrainst{
Package: "std",
Constraints: version.MustConstraints(
version.NewConstraint(">= " + minGoSwissMaps),
),
// Don't warn, we have a backup path.
FailureMode: probe.FailureModeIgnore,
}

// New returns a new [probe.Probe].
func New(logger *slog.Logger, version string) probe.Probe {
id := probe.ID{
Expand Down Expand Up @@ -102,13 +112,22 @@ func New(logger *slog.Logger, version string) probe.Probe {
MinVersion: patternPathMinVersion,
},
patternPathSupportedConst{},
swissMapsUsedConst{},
},
Uprobes: []*probe.Uprobe{
{
Sym: "net/http.serverHandler.ServeHTTP",
EntryProbe: "uprobe_serverHandler_ServeHTTP",
ReturnProbe: "uprobe_serverHandler_ServeHTTP_Returns",
},
{
Sym: "net/textproto.(*Reader).readContinuedLineSlice",
ReturnProbe: "uprobe_textproto_Reader_readContinuedLineSlice_Returns",
PackageConstrainsts: []probe.PackageConstrainst{
goWithSwissMaps,
},
DependsOn: []string{"net/http.serverHandler.ServeHTTP"},
},
},
SpecFn: loadBpf,
},
Expand All @@ -130,6 +149,14 @@ func (c patternPathSupportedConst) InjectOption(td *process.TargetDetails) (inje
return inject.WithKeyValue("pattern_path_supported", isPatternPathSupported), nil
}

type swissMapsUsedConst struct{}

func (c swissMapsUsedConst) InjectOption(td *process.TargetDetails) (inject.Option, error) {
minGoSwissMapsVersion := version.Must(version.NewVersion(minGoSwissMaps))
isUsingGoSwissMaps := td.GoVersion.GreaterThanOrEqual(minGoSwissMapsVersion)
return inject.WithKeyValue("swiss_maps_used", isUsingGoSwissMaps), nil
}

// event represents an event in an HTTP server during an HTTP
// request-response.
type event struct {
Expand Down
3 changes: 2 additions & 1 deletion internal/pkg/instrumentation/probe/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (i *Base[BPFObj, BPFEvent]) loadUprobes(exec *link.Executable, td *process.
logFn = i.Logger.Warn
default:
// Unknown and FailureModeError.
return fmt.Errorf("uprobe %s package constraint (%s) not meet", up.Sym, pc.Constraints.String())
return fmt.Errorf("uprobe %s package constraint (%s) not met, version %v", up.Sym, pc.Constraints.String(), td.Modules[pc.Package])
}

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

skip = true
Expand Down
Loading