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

Propagate HTTP trace context in Go #491

Merged
merged 10 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions bpf/go_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,31 @@ static __always_inline u64 find_parent_goroutine(void *goroutine_addr) {
return 0;
}

static __always_inline void decode_go_traceparent(unsigned char *buf, unsigned char *trace_id, unsigned char *span_id) {
static __always_inline void decode_go_traceparent(unsigned char *buf, unsigned char *trace_id, unsigned char *span_id, unsigned char *flags) {
unsigned char *t_id = buf + 2 + 1; // strlen(ver) + strlen("-")
unsigned char *s_id = buf + 2 + 1 + 32 + 1; // strlen(ver) + strlen("-") + strlen(trace_id) + strlen("-")
unsigned char *f_id = buf + 2 + 1 + 32 + 1 + 16 + 1; // strlen(ver) + strlen("-") + strlen(trace_id) + strlen("-") + strlen(span_id) + strlen("-")

decode_hex(trace_id, t_id, TRACE_ID_CHAR_LEN);
decode_hex(span_id, s_id, SPAN_ID_CHAR_LEN);
decode_hex(flags, f_id, FLAGS_CHAR_LEN);
}

static __always_inline void server_trace_parent(void *goroutine_addr, tp_info_t *tp, void *req_header) {
// May get overriden when decoding existing traceparent, but otherwise we set sample ON
tp->flags = 1;
// Get traceparent from the Request.Header
void *traceparent_ptr = extract_traceparent_from_req_headers(req_header);
if (traceparent_ptr != NULL) {
unsigned char buf[W3C_VAL_LENGTH];
unsigned char buf[TP_MAX_VAL_LENGTH];
long res = bpf_probe_read(buf, sizeof(buf), traceparent_ptr);
if (res < 0) {
bpf_dbg_printk("can't copy traceparent header");
urand_bytes(tp->trace_id, TRACE_ID_SIZE_BYTES);
*((u64 *)tp->parent_id) = 0;
} else {
bpf_dbg_printk("Decoding traceparent from headers %s", buf);
decode_go_traceparent(buf, tp->trace_id, tp->parent_id);
decode_go_traceparent(buf, tp->trace_id, tp->parent_id, &tp->flags);
}
} else {
bpf_dbg_printk("No traceparent in headers, generating");
Expand All @@ -110,20 +114,25 @@ static __always_inline void server_trace_parent(void *goroutine_addr, tp_info_t
bpf_map_update_elem(&go_trace_map, &goroutine_addr, tp, BPF_ANY);
}

static __always_inline void client_trace_parent(void *goroutine_addr, tp_info_t *tp_i, void *req_header) {
static __always_inline u8 client_trace_parent(void *goroutine_addr, tp_info_t *tp_i, void *req_header) {
// Get traceparent from the Request.Header
u8 found_trace_id = 0;
u8 trace_id_exists = 0;

// May get overriden when decoding existing traceparent or finding a server span, but otherwise we set sample ON
tp_i->flags = 1;

if (req_header) {
void *traceparent_ptr = extract_traceparent_from_req_headers(req_header);
if (traceparent_ptr != NULL) {
unsigned char buf[W3C_VAL_LENGTH];
trace_id_exists = 1;
long res = bpf_probe_read(buf, sizeof(buf), traceparent_ptr);
if (res < 0) {
bpf_dbg_printk("can't copy traceparent header");
} else {
found_trace_id = 1;
decode_go_traceparent(buf, tp_i->trace_id, tp_i->span_id);
decode_go_traceparent(buf, tp_i->trace_id, tp_i->span_id, &tp_i->flags);
}
}
}
Expand All @@ -142,12 +151,15 @@ static __always_inline void client_trace_parent(void *goroutine_addr, tp_info_t
*((u64 *)tp_i->trace_id) = *((u64 *)tp->trace_id);
*((u64 *)(tp_i->trace_id + 8)) = *((u64 *)(tp->trace_id + 8));
*((u64 *)tp_i->parent_id) = *((u64 *)tp->span_id);
tp_i->flags = tp->flags;
} else {
urand_bytes(tp_i->trace_id, TRACE_ID_SIZE_BYTES);
}

urand_bytes(tp_i->span_id, SPAN_ID_SIZE_BYTES);
}

return trace_id_exists;
}


Expand Down
91 changes: 90 additions & 1 deletion bpf/go_nethttp.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ int uprobe_ServeHTTP(struct pt_regs *ctx) {

if (req) {
server_trace_parent(goroutine_addr, &invocation.tp, (void*)(req + req_header_ptr_pos));
// TODO: if context propagation is supported, overwrite the header value in the map with the
// new span context and the same thread id.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we don't do it now? Is the feature incomplete?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see it as incomplete in terms of Beyla instrumentation, but it would be nice to make Beyla work well for instrumented applications with the Go SDK. As it stands now, Beyla will correctly propagate the context, but if there's SDK instrumentation as well, the two spans will look parallel, just as it was before. If we overwrite the header value, we'll manage to nest them. So it's an extension to the feature to make it work with auto and manual instrumentation.

}

// Write event
Expand Down Expand Up @@ -193,6 +195,16 @@ int uprobe_WriteHeader(struct pt_regs *ctx) {
return 0;
}

#ifndef NO_HEADER_PROPAGATION
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, void *); // key: pointer to the request header map
__type(value, u64); // the goroutine of the transport request
__uint(max_entries, MAX_CONCURRENT_REQUESTS);
} header_req_map SEC(".maps");

#endif

/* HTTP Client. We expect to see HTTP client in both HTTP server and gRPC server calls.*/

SEC("uprobe/roundTrip")
Expand All @@ -210,13 +222,25 @@ int uprobe_roundTrip(struct pt_regs *ctx) {
.tp = {0}
};

client_trace_parent(goroutine_addr, &invocation.tp, (void*)(req + req_header_ptr_pos));
__attribute__((__unused__)) u8 existing_tp = client_trace_parent(goroutine_addr, &invocation.tp, (void*)(req + req_header_ptr_pos));

// Write event
if (bpf_map_update_elem(&ongoing_http_client_requests, &goroutine_addr, &invocation, BPF_ANY)) {
bpf_dbg_printk("can't update http client map element");
}

#ifndef NO_HEADER_PROPAGATION
if (!existing_tp) {
void *headers_ptr = 0;
bpf_probe_read(&headers_ptr, sizeof(headers_ptr), (void*)(req + req_header_ptr_pos));
bpf_dbg_printk("goroutine_addr %lx, req ptr %llx, headers_ptr %llx", goroutine_addr, req, headers_ptr);

if (headers_ptr) {
bpf_map_update_elem(&header_req_map, &headers_ptr, &goroutine_addr, BPF_ANY);
}
}
#endif

return 0;
}

Expand Down Expand Up @@ -290,3 +314,68 @@ int uprobe_roundTripReturn(struct pt_regs *ctx) {

return 0;
}

#ifndef NO_HEADER_PROPAGATION
// Context propagation through HTTP headers
SEC("uprobe/header_writeSubset")
int uprobe_writeSubset(struct pt_regs *ctx) {
bpf_dbg_printk("=== uprobe/proc header writeSubset === ");

void *header_addr = GO_PARAM1(ctx);
void *io_writer_addr = GO_PARAM3(ctx);

bpf_dbg_printk("goroutine_addr %lx, header ptr %llx", GOROUTINE_PTR(ctx), header_addr);

u64 *request_goaddr = bpf_map_lookup_elem(&header_req_map, &header_addr);

if (!request_goaddr) {
bpf_dbg_printk("Can't find parent go routine for header %llx", header_addr);
return 0;
}

u64 parent_goaddr = *request_goaddr;

http_func_invocation_t *func_inv = bpf_map_lookup_elem(&ongoing_http_client_requests, &parent_goaddr);
if (!func_inv) {
bpf_dbg_printk("Can't find client request for goroutine %llx", parent_goaddr);
return 0;
}

unsigned char buf[TRACEPARENT_LEN];

make_tp_string(buf, &func_inv->tp);

void *buf_ptr = 0;
bpf_probe_read(&buf_ptr, sizeof(buf_ptr), (void *)(io_writer_addr + io_writer_buf_ptr_pos));
if (!buf_ptr) {
return 0;
}

s64 size = 0;
bpf_probe_read(&size, sizeof(s64), (void *)(io_writer_addr + io_writer_buf_ptr_pos + 8)); // grab size

s64 len = 0;
bpf_probe_read(&len, sizeof(s64), (void *)(io_writer_addr + io_writer_n_pos)); // grab len

bpf_dbg_printk("buf_ptr %llx, len=%d, size=%d", (void*)buf_ptr, len, size);

if (len < (size - TP_MAX_VAL_LENGTH - TP_MAX_KEY_LENGTH - 4)) { // 4 = strlen(":_") + strlen("\r\n")
char key[TP_MAX_KEY_LENGTH + 2] = "Traceparent: ";
char end[2] = "\r\n";
bpf_probe_write_user(buf_ptr + (len & 0x0ffff), key, sizeof(key));
len += TP_MAX_KEY_LENGTH + 2;
bpf_probe_write_user(buf_ptr + (len & 0x0ffff), buf, sizeof(buf));
len += TP_MAX_VAL_LENGTH;
bpf_probe_write_user(buf_ptr + (len & 0x0ffff), end, sizeof(end));
len += 2;
bpf_probe_write_user((void *)(io_writer_addr + io_writer_n_pos), &len, sizeof(len));
}

return 0;
}
#else
SEC("uprobe/header_writeSubset")
int uprobe_writeSubset(struct pt_regs *ctx) {
return 0;
}
#endif
2 changes: 2 additions & 0 deletions bpf/go_nethttp.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ volatile const u64 host_ptr_pos;
volatile const u64 content_length_ptr_pos;
volatile const u64 resp_req_pos;
volatile const u64 req_header_ptr_pos;
volatile const u64 io_writer_buf_ptr_pos;
volatile const u64 io_writer_n_pos;

#endif
7 changes: 7 additions & 0 deletions bpf/trace_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ static __always_inline unsigned char *extract_span_id(unsigned char *tp_start) {
return tp_start + 13 + 2 + 1 + 32 + 1; // strlen("Traceparent: ") + strlen(ver) + strlen("-") + strlen(trace_id) + strlen("-")
}

static __always_inline unsigned char *extract_flags(unsigned char *tp_start) {
return tp_start + 13 + 2 + 1 + 32 + 1 + 16 + 1; // strlen("Traceparent: ") + strlen(ver) + strlen("-") + strlen(trace_id) + strlen("-") + strlen(span_id) + strlen("-")
}

static __always_inline u64 current_epoch() {
u64 ts = bpf_ktime_get_ns();
u64 temp = ts / NANOSECONDS_PER_EPOCH;
Expand All @@ -122,6 +126,7 @@ static __always_inline void get_or_create_trace_info(http_connection_metadata_t
}

tp->epoch = current_epoch();
tp->flags = 1;
urand_bytes(tp->span_id, SPAN_ID_SIZE_BYTES);
bpf_memset(tp->parent_id, 0, sizeof(tp->span_id));

Expand Down Expand Up @@ -151,8 +156,10 @@ static __always_inline void get_or_create_trace_info(http_connection_metadata_t
bpf_dbg_printk("Found traceparent %s", res);
unsigned char *t_id = extract_trace_id(res);
unsigned char *s_id = extract_span_id(res);
unsigned char *f_id = extract_flags(res);

decode_hex(tp->trace_id, t_id, TRACE_ID_CHAR_LEN);
decode_hex((unsigned char *)&tp->flags, f_id, FLAGS_CHAR_LEN);
if (meta && meta->type == EVENT_HTTP_CLIENT) {
decode_hex(tp->span_id, s_id, SPAN_ID_CHAR_LEN);
} else {
Expand Down
2 changes: 0 additions & 2 deletions bpf/trace_util.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#ifndef TRACE_UTIL_H
#define TRACE_UTIL_H

#include "utils.h"

// 55+13
#define TRACE_PARENT_HEADER_LEN 68

Expand Down
23 changes: 23 additions & 0 deletions bpf/tracing.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
#ifndef TRACING_H
#define TRACING_H
#include "vmlinux.h"
#include "trace_util.h"

#define TRACE_ID_SIZE_BYTES 16
#define SPAN_ID_SIZE_BYTES 8
#define FLAGS_SIZE_BYTES 1
#define TRACE_ID_CHAR_LEN 32
#define SPAN_ID_CHAR_LEN 16
#define FLAGS_CHAR_LEN 2
#define TP_MAX_VAL_LENGTH 55
#define TP_MAX_KEY_LENGTH 11

typedef struct tp_info {
unsigned char trace_id[TRACE_ID_SIZE_BYTES];
unsigned char span_id[SPAN_ID_SIZE_BYTES];
unsigned char parent_id[SPAN_ID_SIZE_BYTES];
u64 epoch;
u8 flags;
} tp_info_t;

static __always_inline void make_tp_string(unsigned char *buf, tp_info_t *tp) {
// Version
*buf++ = '0'; *buf++ = '0'; *buf++ = '-';

// TraceID
encode_hex(buf, tp->trace_id, TRACE_ID_SIZE_BYTES);
buf += TRACE_ID_CHAR_LEN;
*buf++ = '-';

// SpanID
encode_hex(buf, tp->span_id, SPAN_ID_SIZE_BYTES);
buf += SPAN_ID_CHAR_LEN;
*buf++ = '-';

// Flags
*buf++ = '0'; *buf = (tp->flags == 0) ? '0' : '1';
}

#endif
4 changes: 4 additions & 0 deletions configs/offsets/tracker_input.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
],
"context.valueCtx": [
"val"
],
"bufio.Writer": [
"buf",
"n"
]
}
},
Expand Down
1 change: 1 addition & 0 deletions examples/greeting-apps/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ services:
- --config=/configs/beyla-config.yml
volumes:
- ./configs/:/configs
- ./system/sys/kernel/security:/sys/kernel/security
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we have to document this e.g. to allow running Beyla in Kubernetes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh this is a very good point. I need to document this. As it stands now, if security isn't there we'll assume we can propagate the context, since the security file is missing. But it could be because the users didn't mount it and the host machine doesn't allow it. I'll follow-up with a PR on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened an issue so I don't forget #502

container_name: demo-javabeyla
privileged: true
network_mode: "service:javatestserver"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
none [integrity] confidentiality
4 changes: 4 additions & 0 deletions pkg/internal/ebpf/common/bpf_bpfel_arm64.go

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

Binary file modified pkg/internal/ebpf/common/bpf_bpfel_arm64.o
Binary file not shown.
4 changes: 4 additions & 0 deletions pkg/internal/ebpf/common/bpf_bpfel_x86.go

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

Binary file modified pkg/internal/ebpf/common/bpf_bpfel_x86.o
Binary file not shown.
2 changes: 2 additions & 0 deletions pkg/internal/ebpf/common/spanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func HTTPRequestTraceToSpan(trace *HTTPRequestTrace) request.Span {
TraceID: trace2.TraceID(trace.Tp.TraceId),
SpanID: trace2.SpanID(trace.Tp.SpanId),
ParentSpanID: trace2.SpanID(trace.Tp.ParentId),
Flags: trace.Tp.Flags,
Pid: request.PidInfo{
HostPID: trace.Pid.HostPid,
UserPID: trace.Pid.UserPid,
Expand Down Expand Up @@ -98,6 +99,7 @@ func SQLRequestTraceToSpan(trace *SQLRequestTrace) request.Span {
TraceID: trace2.TraceID(trace.Tp.TraceId),
SpanID: trace2.SpanID(trace.Tp.SpanId),
ParentSpanID: trace2.SpanID(trace.Tp.ParentId),
Flags: trace.Tp.Flags,
Pid: request.PidInfo{
HostPID: trace.Pid.HostPid,
UserPID: trace.Pid.UserPid,
Expand Down
2 changes: 2 additions & 0 deletions pkg/internal/ebpf/goruntime/bpf_bpfel_arm64.go

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

Binary file modified pkg/internal/ebpf/goruntime/bpf_bpfel_arm64.o
Binary file not shown.
2 changes: 2 additions & 0 deletions pkg/internal/ebpf/goruntime/bpf_bpfel_x86.go

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

Binary file modified pkg/internal/ebpf/goruntime/bpf_bpfel_x86.o
Binary file not shown.
2 changes: 2 additions & 0 deletions pkg/internal/ebpf/goruntime/bpf_debug_bpfel_arm64.go

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

Binary file modified pkg/internal/ebpf/goruntime/bpf_debug_bpfel_arm64.o
Binary file not shown.
Loading