Skip to content

Commit d127402

Browse files
committed
bpf: use configurable large buffers for HTTP requests
Signed-off-by: Mattia Meleleo <[email protected]>
1 parent 0a0bab2 commit d127402

File tree

15 files changed

+161
-62
lines changed

15 files changed

+161
-62
lines changed

bpf/common/common.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#define HTTP_HEADER_MAX_LEN 100
4343
#define HTTP_CONTENT_TYPE_MAX_LEN 16
4444

45+
volatile const u32 http_buffer_size = 0;
4546
volatile const u32 mysql_buffer_size = 0;
4647
volatile const u32 postgres_buffer_size = 0;
4748

@@ -241,4 +242,4 @@ typedef struct mongo_go_client_req {
241242
unsigned char coll[32];
242243
connection_info_t conn;
243244
tp_info_t tp;
244-
} mongo_go_client_req_t;
245+
} mongo_go_client_req_t;

bpf/common/http_info.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ typedef struct http_info {
2929
u32 task_tid;
3030
u16 status;
3131
unsigned char buf[FULL_BUF_SIZE];
32-
u8 _pad[6];
32+
u8 has_large_buffers;
33+
u8 _pad[5];
3334
} http_info_t;

bpf/generictracer/iovec_len.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
#pragma once
55

6-
enum { k_iovec_max_len = 512 };
6+
enum { k_iovec_max_len = 8192 };

bpf/generictracer/protocol_http.h

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
#include <bpfcore/vmlinux.h>
77
#include <bpfcore/bpf_helpers.h>
88

9+
#include <common/common.h>
910
#include <common/http_types.h>
1011
#include <common/pin_internal.h>
1112
#include <common/ringbuf.h>
1213
#include <common/runtime.h>
1314
#include <common/trace_common.h>
15+
#include <common/scratch_mem.h>
1416

1517
#include <generictracer/maps/http_info_mem.h>
1618
#include <generictracer/protocol_common.h>
@@ -20,6 +22,11 @@
2022

2123
volatile const u32 high_request_volume;
2224

25+
enum {
26+
k_http_large_buf_max_size = 1 << 14, // 16K
27+
k_http_large_buf_max_size_mask = k_http_large_buf_max_size - 1,
28+
};
29+
2330
// empty_http_info zeroes and return the unique percpu copy in the map
2431
// this function assumes that a given thread is not trying to use many
2532
// instances at the same time
@@ -386,6 +393,53 @@ static __always_inline void handle_http_response(unsigned char *small_buf,
386393
cleanup_http_request_data(pid_conn, info);
387394
}
388395

396+
SCRATCH_MEM_SIZED(http_large_buffers, k_http_large_buf_max_size);
397+
398+
static __always_inline int http_send_large_buffer(http_info_t *req,
399+
const void *u_buf,
400+
u32 bytes_len,
401+
u8 packet_type,
402+
enum large_buf_action action) {
403+
if (http_buffer_size == 0) {
404+
return 0;
405+
}
406+
407+
if (!is_pow2(http_buffer_size)) {
408+
bpf_dbg_printk("http_send_large_buffer: bug: http_buffer_size is not a power of 2");
409+
return -1;
410+
}
411+
const u32 buf_len_mask = http_buffer_size - 1; // 8191
412+
413+
tcp_large_buffer_t *large_buf = (tcp_large_buffer_t *)http_large_buffers_mem();
414+
if (!large_buf) {
415+
bpf_dbg_printk("http_send_large_buffer: failed to reserve space for HTTP large buffer");
416+
return -1;
417+
}
418+
419+
large_buf->type = EVENT_TCP_LARGE_BUFFER;
420+
large_buf->packet_type = packet_type;
421+
large_buf->action = action;
422+
__builtin_memcpy((void *)&large_buf->tp, (void *)&req->tp, sizeof(tp_info_t));
423+
424+
large_buf->len = bytes_len;
425+
if (large_buf->len >= http_buffer_size) {
426+
large_buf->len = http_buffer_size;
427+
bpf_dbg_printk("WARN: http_send_large_buffer: buffer is full, truncating data");
428+
}
429+
const u16 len_real = large_buf->len & buf_len_mask;
430+
(void)len_real;
431+
bpf_probe_read(large_buf->buf, large_buf->len & buf_len_mask, u_buf);
432+
433+
u32 total_size = sizeof(tcp_large_buffer_t);
434+
total_size += large_buf->len > sizeof(void *) ? large_buf->len : sizeof(void *);
435+
436+
req->has_large_buffers = true;
437+
438+
bpf_ringbuf_output(
439+
&events, large_buf, total_size & k_http_large_buf_max_size_mask, get_flags());
440+
return 0;
441+
}
442+
389443
// k_tail_protocol_http
390444
SEC("kprobe/http")
391445
int obi_protocol_http(void *ctx) {
@@ -463,14 +517,31 @@ int obi_protocol_http(void *ctx) {
463517
bpf_dbg_printk("No META!");
464518
}
465519

466-
// we copy some small part of the buffer to the info trace event, so that we can process an event even with
467-
// incomplete trace info in user space.
468-
bpf_probe_read(info->buf, FULL_BUF_SIZE, (void *)args->u_buf);
520+
if (http_buffer_size > 0) {
521+
http_send_large_buffer(info,
522+
(void *)args->u_buf,
523+
args->bytes_len,
524+
args->packet_type,
525+
k_large_buf_action_init);
526+
} else {
527+
// we copy some small part of the buffer to the info trace event, so that we can process an event even with
528+
// incomplete trace info in user space.
529+
bpf_probe_read(info->buf, FULL_BUF_SIZE, (void *)args->u_buf);
530+
}
531+
469532
process_http_request(info, args->bytes_len, meta, args->direction, args->orig_dport);
470533
} else if ((args->packet_type == PACKET_TYPE_RESPONSE) && (info->status == 0)) {
534+
http_send_large_buffer(
535+
info, (void *)args->u_buf, args->bytes_len, args->packet_type, k_large_buf_action_init);
471536
handle_http_response(
472537
args->small_buf, &args->pid_conn, info, args->bytes_len, args->direction, args->ssl);
473538
} else if (still_reading(info)) {
539+
http_send_large_buffer(info,
540+
(void *)args->u_buf,
541+
args->bytes_len,
542+
args->packet_type,
543+
k_large_buf_action_append);
544+
474545
info->len += args->bytes_len;
475546
info->end_monotime_ns = bpf_ktime_get_ns();
476547
}

bpf/generictracer/protocol_mysql.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ static __always_inline int mysql_read_fixup_buffer(const connection_info_t *conn
133133
bpf_dbg_printk("mysql_read_fixup_buffer: bug: mysql_buffer_size is not a power of 2");
134134
return -1;
135135
}
136-
const u8 buf_len_mask = mysql_buffer_size - 1;
136+
const u32 buf_len_mask = mysql_buffer_size - 1;
137137

138138
struct mysql_state_data *state_data = bpf_map_lookup_elem(&mysql_state, conn_info);
139139
if (state_data != NULL) {

bpf/generictracer/protocol_postgres.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ static __always_inline int postgres_send_large_buffer(tcp_req_t *req,
6262
bpf_dbg_printk("postgres_send_large_buffer: bug: postgres_buffer_size is not a power of 2");
6363
return -1;
6464
}
65-
const u8 buf_len_mask = postgres_buffer_size - 1;
65+
const u32 buf_len_mask = postgres_buffer_size - 1;
6666

6767
tcp_large_buffer_t *large_buf = (tcp_large_buffer_t *)postgres_large_buffers_mem();
6868
if (!large_buf) {

pkg/components/ebpf/common/common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ func ReadBPFTraceAsSpan(parseCtx *EBPFParseContext, cfg *config.EBPFTracer, reco
223223
case EventTypeSQL:
224224
return ReadSQLRequestTraceAsSpan(record)
225225
case EventTypeKHTTP:
226-
return ReadHTTPInfoIntoSpan(record, filter)
226+
return ReadHTTPInfoIntoSpan(parseCtx, record, filter)
227227
case EventTypeKHTTP2:
228228
return ReadHTTP2InfoIntoSpan(parseCtx, record, filter)
229229
case EventTypeTCP:

pkg/components/ebpf/common/httpfltr_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ func TestURL(t *testing.T) {
2323
event := BPFHTTPInfo{
2424
Buf: [bufSize]byte{'G', 'E', 'T', ' ', '/', 'p', 'a', 't', 'h', '?', 'q', 'u', 'e', 'r', 'y', '=', '1', '2', '3', '4', ' ', 'H', 'T', 'T', 'P', '/', '1', '.', '1'},
2525
}
26-
assert.Equal(t, "/path?query=1234", event.url())
26+
assert.Equal(t, "/path?query=1234", httpURLFromBuf(event.Buf[:]))
2727
event = BPFHTTPInfo{}
28-
assert.Empty(t, event.url())
28+
assert.Empty(t, httpURLFromBuf(event.Buf[:]))
2929
}
3030

3131
func TestMethod(t *testing.T) {
3232
event := BPFHTTPInfo{
3333
Buf: [bufSize]byte{'G', 'E', 'T', ' ', '/', 'p', 'a', 't', 'h', ' ', 'H', 'T', 'T', 'P', '/', '1', '.', '1'},
3434
}
3535

36-
assert.Equal(t, "GET", event.method())
36+
assert.Equal(t, "GET", httpMethodFromBuf(event.Buf[:]))
3737
event = BPFHTTPInfo{}
38-
assert.Empty(t, event.method())
38+
assert.Empty(t, httpMethodFromBuf(event.Buf[:]))
3939
}
4040

4141
func TestHostInfo(t *testing.T) {
@@ -107,7 +107,7 @@ func TestToRequestTrace(t *testing.T) {
107107
err := binary.Write(buf, binary.LittleEndian, &record)
108108
require.NoError(t, err)
109109

110-
result, _, err := ReadHTTPInfoIntoSpan(&ringbuf.Record{RawSample: buf.Bytes()}, &fltr)
110+
result, _, err := ReadHTTPInfoIntoSpan(nil, &ringbuf.Record{RawSample: buf.Bytes()}, &fltr)
111111
require.NoError(t, err)
112112

113113
expected := request.Span{
@@ -143,7 +143,7 @@ func TestToRequestTraceNoConnection(t *testing.T) {
143143
err := binary.Write(buf, binary.LittleEndian, &record)
144144
require.NoError(t, err)
145145

146-
result, _, err := ReadHTTPInfoIntoSpan(&ringbuf.Record{RawSample: buf.Bytes()}, &fltr)
146+
result, _, err := ReadHTTPInfoIntoSpan(nil, &ringbuf.Record{RawSample: buf.Bytes()}, &fltr)
147147
require.NoError(t, err)
148148

149149
// change the expected port just before testing
@@ -182,7 +182,7 @@ func TestToRequestTrace_BadHost(t *testing.T) {
182182
err := binary.Write(buf, binary.LittleEndian, &record)
183183
require.NoError(t, err)
184184

185-
result, _, err := ReadHTTPInfoIntoSpan(&ringbuf.Record{RawSample: buf.Bytes()}, &fltr)
185+
result, _, err := ReadHTTPInfoIntoSpan(nil, &ringbuf.Record{RawSample: buf.Bytes()}, &fltr)
186186
require.NoError(t, err)
187187

188188
expected := request.Span{
@@ -201,28 +201,28 @@ func TestToRequestTrace_BadHost(t *testing.T) {
201201
}
202202
assert.Equal(t, expected, result)
203203

204-
s, p := record.hostFromBuf()
204+
s, p := httpHostFromBuf(record.Buf[:])
205205
assert.Equal(t, "example.c", s)
206206
assert.Equal(t, -1, p)
207207

208208
var record1 BPFHTTPInfo
209209
copy(record1.Buf[:], "GET /hello HTTP/1.1\r\nHost: example.c:23")
210210

211-
s, p = record1.hostFromBuf()
211+
s, p = httpHostFromBuf(record1.Buf[:])
212212
assert.Equal(t, "example.c", s)
213213
assert.Equal(t, 23, p)
214214

215215
var record2 BPFHTTPInfo
216216
copy(record2.Buf[:], "GET /hello HTTP/1.1\r\nHost: ")
217217

218-
s, p = record2.hostFromBuf()
218+
s, p = httpHostFromBuf(record2.Buf[:])
219219
assert.Empty(t, s)
220220
assert.Equal(t, -1, p)
221221

222222
var record3 BPFHTTPInfo
223223
copy(record3.Buf[:], "GET /hello HTTP/1.1\r\nHost")
224224

225-
s, p = record3.hostFromBuf()
225+
s, p = httpHostFromBuf(record3.Buf[:])
226226
assert.Empty(t, s)
227227
assert.Equal(t, -1, p)
228228
}

pkg/components/ebpf/common/httpfltr_transform.go

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package ebpfcommon
55

66
import (
77
"bytes"
8+
"log/slog"
89
"net"
910
"strconv"
1011
"strings"
@@ -64,7 +65,7 @@ type HTTPInfo struct {
6465
HeaderHost string
6566
}
6667

67-
func ReadHTTPInfoIntoSpan(record *ringbuf.Record, filter ServiceFilter) (request.Span, bool, error) {
68+
func ReadHTTPInfoIntoSpan(parseCtx *EBPFParseContext, record *ringbuf.Record, filter ServiceFilter) (request.Span, bool, error) {
6869
event, err := ReinterpretCast[BPFHTTPInfo](record.RawSample)
6970
if err != nil {
7071
return request.Span{}, true, err
@@ -75,15 +76,31 @@ func ReadHTTPInfoIntoSpan(record *ringbuf.Record, filter ServiceFilter) (request
7576
return request.Span{}, true, nil
7677
}
7778

78-
return HTTPInfoEventToSpan(event)
79+
return HTTPInfoEventToSpan(parseCtx, event)
7980
}
8081

81-
func HTTPInfoEventToSpan(event *BPFHTTPInfo) (request.Span, bool, error) {
82+
func HTTPInfoEventToSpan(parseCtx *EBPFParseContext, event *BPFHTTPInfo) (request.Span, bool, error) {
8283
result := HTTPInfo{BPFHTTPInfo: *event}
8384

84-
var bufHost string
85-
var bufPort int
86-
parsedHost := false
85+
var (
86+
bufHost string
87+
bufPort int
88+
parsedHost bool
89+
requestBuffer []byte
90+
)
91+
92+
if event.HasLargeBuffers == 1 {
93+
b, ok := extractTCPLargeBuffer(parseCtx, event.Tp.TraceId, event.Tp.SpanId, packetTypeRequest)
94+
if !ok {
95+
slog.Debug("missing large buffer for HTTP request", "traceID", event.Tp.TraceId, "spanID", event.Tp.SpanId)
96+
return request.Span{}, false, nil
97+
}
98+
requestBuffer = b
99+
100+
// TODO: response?
101+
} else {
102+
requestBuffer = event.Buf[:]
103+
}
87104

88105
// When we can't find the connection info, we signal that through making the
89106
// source and destination ports equal to max short. E.g. async SSL
@@ -92,34 +109,34 @@ func HTTPInfoEventToSpan(event *BPFHTTPInfo) (request.Span, bool, error) {
92109
result.Host = target
93110
result.Peer = source
94111
} else {
95-
bufHost, bufPort = event.hostFromBuf()
112+
bufHost, bufPort = httpHostFromBuf(requestBuffer)
96113
parsedHost = true
97114

98115
if bufPort >= 0 {
99116
result.Host = bufHost
100117
result.ConnInfo.D_port = uint16(bufPort)
101118
}
102119
}
103-
result.URL = event.url()
104-
result.Method = event.method()
120+
result.URL = httpURLFromBuf(requestBuffer)
121+
result.Method = httpMethodFromBuf(requestBuffer)
105122

106123
if request.EventType(result.Type) == request.EventTypeHTTPClient && !parsedHost {
107-
bufHost, _ = event.hostFromBuf()
124+
bufHost, _ = httpHostFromBuf(requestBuffer)
108125
}
109126

110127
result.HeaderHost = bufHost
111128

112129
return httpInfoToSpan(&result), false, nil
113130
}
114131

115-
func (event *BPFHTTPInfo) url() string {
116-
buf := string(event.Buf[:])
132+
func httpURLFromBuf(req []byte) string {
133+
buf := string(req)
117134
space := strings.Index(buf, " ")
118135
if space < 0 {
119136
return ""
120137
}
121138

122-
bufEnd := bytes.IndexByte(event.Buf[:], 0) // We assume the buffer was zero initialized in eBPF
139+
bufEnd := bytes.IndexByte(req, 0) // We assume the buffer was zero initialized in eBPF
123140
if bufEnd < 0 {
124141
bufEnd = len(buf)
125142
}
@@ -141,8 +158,8 @@ func (event *BPFHTTPInfo) url() string {
141158
return buf[space+1 : end]
142159
}
143160

144-
func (event *BPFHTTPInfo) method() string {
145-
buf := string(event.Buf[:])
161+
func httpMethodFromBuf(req []byte) string {
162+
buf := string(req)
146163
space := strings.Index(buf, " ")
147164
if space < 0 {
148165
return ""
@@ -151,8 +168,8 @@ func (event *BPFHTTPInfo) method() string {
151168
return buf[:space]
152169
}
153170

154-
func (event *BPFHTTPInfo) hostFromBuf() (string, int) {
155-
buf := cstr(event.Buf[:])
171+
func httpHostFromBuf(req []byte) (string, int) {
172+
buf := cstr(req)
156173

157174
host := "Host: "
158175
idx := strings.Index(buf, host)

0 commit comments

Comments
 (0)