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

Speed up HTTP2 check #923

Merged
merged 7 commits into from
Jun 12, 2024
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
2 changes: 1 addition & 1 deletion bpf/http_sock.h
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ static __always_inline void process_http2_grpc_frames(pid_connection_info_t *pid
break;
}

if (pos < (bytes_len - frame.length + FRAME_HEADER_LEN)) {
if (pos < (bytes_len - (frame.length + FRAME_HEADER_LEN))) {
pos += (frame.length + FRAME_HEADER_LEN);
//bpf_dbg_printk("New buf read pos = %d", pos);
}
Expand Down
95 changes: 95 additions & 0 deletions pkg/internal/ebpf/common/http2grpc_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,102 @@ func ReadHTTP2InfoIntoSpan(record *ringbuf.Record, filter ServiceFilter) (reques
return request.Span{}, true, nil // ignore if we couldn't parse it
}

type http2FrameType uint8

type frameHeader struct {
Length uint32
Type http2FrameType
Flags uint8
Ignore uint8
StreamID uint32
}

const (
FrameData http2FrameType = 0x0
FrameHeaders http2FrameType = 0x1
FramePriority http2FrameType = 0x2
FrameRSTStream http2FrameType = 0x3
FrameSettings http2FrameType = 0x4
FramePushPromise http2FrameType = 0x5
FramePing http2FrameType = 0x6
FrameGoAway http2FrameType = 0x7
FrameWindowUpdate http2FrameType = 0x8
FrameContinuation http2FrameType = 0x9
)

const frameHeaderLen = 9

func readHTTP2Frame(buf []uint8, len int) (*frameHeader, bool) {
if len < frameHeaderLen {
return nil, false
}

frame := frameHeader{
Length: (uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])),
Type: http2FrameType(buf[3]),
Flags: buf[4],
StreamID: binary.BigEndian.Uint32(buf[5:]) & (1<<31 - 1),
}

if frame.Length == 0 || frame.Type > FrameContinuation {
return nil, false
}

return &frame, true
}

func isHeadersFrame(frame *frameHeader) bool {
return frame.Type == FrameHeaders && frame.StreamID != 0
}

func isInvalidFrame(frame *frameHeader) bool {
return frame.Length == 0 && frame.Type == FrameData
}

func isLikelyHTTP2(data []uint8, eventLen int) bool {
pos := 0
l := eventLen
if l > len(data) {
l = len(data)
}
for i := 0; i < 8; i++ {
if pos > l-frameHeaderLen {
break
}

fr, ok := readHTTP2Frame(data[pos:], l)
if !ok {
break
}

if isHeadersFrame(fr) {
return true
}

if isInvalidFrame(fr) {
break
}

if pos < (l - int(fr.Length+frameHeaderLen)) {
pos += int(fr.Length + frameHeaderLen)
continue
}

break
}

return false
}

func isHTTP2(data []uint8, event *TCPRequestInfo) bool {
// Parsing HTTP2 frames with the Go HTTP2/gRPC parser is very expensive.
// Therefore, we replicate some of our HTTP2 frame reader from eBPF here to
// check if this payload even remotely looks like HTTP2/gRPC, e.g. we must
// find a resonably looking HTTP "headers" frame.
if !isLikelyHTTP2(data, int(event.Len)) {
return false
}

framer := byteFramer(data)

for {
Expand Down
66 changes: 66 additions & 0 deletions pkg/internal/ebpf/common/http2grpc_transform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ebpfcommon

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestHTTP2QuickDetection(t *testing.T) {
tests := []struct {
name string
input []byte
inputLen int
expected bool
}{
{
name: "Empty",
input: []byte{},
inputLen: 100,
expected: false,
},
{
name: "Short",
input: []byte{0, 0, 70, 1, 4},
inputLen: 3,
expected: false,
},
{
name: "Regular HTTP2/gRPC Frame",
input: []byte{0, 0, 70, 1, 4, 0, 0, 0, 19, 204, 131, 4, 147, 96, 233, 45, 18, 22, 147, 175, 12, 155, 139, 103, 115, 16, 172, 98, 42, 97, 145, 31, 134, 126, 167, 0, 22, 16, 7, 36, 140, 179, 27, 50, 202, 25, 101, 105, 182, 93, 33, 66, 211, 97, 41, 64, 0, 182, 66, 44, 219, 242, 186, 217, 2, 203, 196, 3, 143, 182, 209, 86, 0, 127, 203, 202, 201, 200, 199, 0, 0, 5, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
inputLen: 10000,
expected: true,
},
{
name: "Reset frame before HTTP2/gRPC Frame",
input: []byte{0, 0, 4, 3, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 70, 1, 4, 0, 0, 0, 21, 205, 131, 4, 147, 96, 233, 45, 18, 22, 147, 175, 12, 155, 139, 103, 115, 16, 172, 98, 42, 97, 145, 31, 134, 126, 167, 0, 22, 44, 99, 27, 33, 124, 174, 72, 228, 109, 129, 233, 27, 125, 246, 133, 44, 101, 28, 111, 70, 32, 178, 85, 163, 108, 97, 149, 199, 99, 121, 169, 90, 149, 225, 188, 176, 3, 204, 203, 202, 201, 200, 0, 0, 5, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
inputLen: 10000,
expected: true,
},
{
name: "Kafka frame instead of HTTP2",
input: []byte{0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 2, 0, 6, 115, 97, 114, 97, 109, 97, 255, 255, 255, 255, 0, 0, 39, 16, 0, 0, 0, 1, 0, 9, 105, 109, 112, 111, 114, 116, 97, 110, 116, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 72},
inputLen: 10000,
expected: false,
},
{
name: "No headers frame (manually tweaked the type to fail)",
input: []byte{0, 0, 4, 3, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 70, 2, 4, 0, 0, 0, 21, 205, 131, 4, 147, 96, 233, 45, 18, 22, 147, 175, 12, 155, 139, 103, 115, 16, 172, 98, 42, 97, 145, 31, 134, 126, 167, 0, 22, 44, 99, 27, 33, 124, 174, 72, 228, 109, 129, 233, 27, 125, 246, 133, 44, 101, 28, 111, 70, 32, 178, 85, 163, 108, 97, 149, 199, 99, 121, 169, 90, 149, 225, 188, 176, 3, 204, 203, 202, 201, 200, 0, 0, 5, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
inputLen: 10000,
expected: false,
},
{
name: "Truncated frame, len should be 70 of the second frame",
input: []byte{0, 0, 4, 3, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 70, 2, 4, 0, 0, 0, 21, 205, 131},
inputLen: 10000,
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := isLikelyHTTP2(tt.input, tt.inputLen)
assert.Equal(t, tt.expected, res)
})
}
}
4 changes: 2 additions & 2 deletions pkg/internal/ebpf/common/tcp_detect_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ func ReadTCPRequestIntoSpan(record *ringbuf.Record, filter ServiceFilter) (reque
switch {
case validSQL(op, table):
return TCPToSQLToSpan(&event, op, table, sql), false, nil
case isHTTP2(b, &event):
MisclassifiedEvents <- MisclassifiedEvent{EventType: EventTypeKHTTP2, TCPInfo: &event}
case isRedis(event.Buf[:l]) && isRedis(event.Rbuf[:]):
op, text, ok := parseRedisRequest(buf)

Expand All @@ -54,6 +52,8 @@ func ReadTCPRequestIntoSpan(record *ringbuf.Record, filter ServiceFilter) (reque
k, err := ProcessPossibleKafkaEvent(b, event.Rbuf[:])
if err == nil {
return TCPToKafkaToSpan(&event, k), false, nil
} else if isHTTP2(b, &event) {
MisclassifiedEvents <- MisclassifiedEvent{EventType: EventTypeKHTTP2, TCPInfo: &event}
}
}

Expand Down
Binary file modified pkg/internal/ebpf/httpfltr/bpf_bpfel_arm64.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpfltr/bpf_bpfel_x86.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpfltr/bpf_debug_bpfel_arm64.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpfltr/bpf_debug_bpfel_x86.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpfltr/bpf_tp_bpfel_arm64.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpfltr/bpf_tp_bpfel_x86.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpfltr/bpf_tp_debug_bpfel_arm64.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpfltr/bpf_tp_debug_bpfel_x86.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpssl/bpf_bpfel_arm64.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpssl/bpf_bpfel_x86.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpssl/bpf_debug_bpfel_arm64.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpssl/bpf_debug_bpfel_x86.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpssl/bpf_tp_bpfel_arm64.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpssl/bpf_tp_bpfel_x86.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpssl/bpf_tp_debug_bpfel_arm64.o
Binary file not shown.
Binary file modified pkg/internal/ebpf/httpssl/bpf_tp_debug_bpfel_x86.o
Binary file not shown.
Loading