From b5ccfaa072dcec53a4a9e5e4106b657877569cd3 Mon Sep 17 00:00:00 2001 From: Anushree R Date: Wed, 10 Jun 2026 20:57:24 +0530 Subject: [PATCH 1/2] test(bpf): finish table-driving decode_test.go (#139) --- internal/bpf/decode_test.go | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/internal/bpf/decode_test.go b/internal/bpf/decode_test.go index 8ea032a..06c54cc 100644 --- a/internal/bpf/decode_test.go +++ b/internal/bpf/decode_test.go @@ -1,3 +1,5 @@ +//go:build linux + // Copyright 2026 Optiqor contributors // SPDX-License-Identifier: Apache-2.0 @@ -94,7 +96,7 @@ func TestDecodeTCPEvent(t *testing.T) { SAddr: binary.BigEndian.Uint32([]byte{10, 0, 0, 1}), DAddr: binary.BigEndian.Uint32([]byte{8, 8, 8, 8}), SPort: 54321, - DPort: 443, // already in host byte order (BPF normalizes before writing) + DPort: 443, EventType: TCPEventRTT, RTTUs: 250, } @@ -489,20 +491,4 @@ func TestIsSyscallError(t *testing.T) { } } -func TestTCPEventPortByteOrder(t *testing.T) { - // Verify that ports in TCPEvent are host byte order. - // The BPF program applies bpf_ntohs() to dport before writing, - // so both fields should be readable directly without swapping. - e := TCPEvent{ - SPort: 54321, // ephemeral source port, host order - DPort: 443, // HTTPS destination port, host order (not 47873) - } - if e.DPort == 0xBB01 { // 47873 = 443 byte-swapped - t.Error("DPort is in network byte order; expected host byte order after BPF normalization") - } - if e.DPort != 443 { - t.Errorf("DPort = %d, want 443", e.DPort) - } -} - var _ = net.IPv4(0, 0, 0, 0) From 28cc5665b2a9d2c119b8a60db23e937bd17b372f Mon Sep 17 00:00:00 2001 From: Anushree R Date: Thu, 11 Jun 2026 17:41:13 +0530 Subject: [PATCH 2/2] test(bpf): integration-test eBPF load and event flow in privileged environment (#148) --- internal/integration/ebpf_load_test.go | 132 +++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 internal/integration/ebpf_load_test.go diff --git a/internal/integration/ebpf_load_test.go b/internal/integration/ebpf_load_test.go new file mode 100644 index 0000000..4785388 --- /dev/null +++ b/internal/integration/ebpf_load_test.go @@ -0,0 +1,132 @@ +// Copyright 2026 Optiqor contributors +// SPDX-License-Identifier: Apache-2.0 + +//go:build integration + +package integration + +import ( +"context" +"io" +"log/slog" +"net" +"testing" +"time" + +"github.com/optiqor/kerno/internal/bpf" +"github.com/optiqor/kerno/internal/collector" +"github.com/testcontainers/testcontainers-go" +"github.com/testcontainers/testcontainers-go/wait" +) + +func TestEBPFLoadAndEvents(t *testing.T) { +testcontainers.SkipIfProviderIsNotHealthy(t) + +ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) +defer cancel() + +logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + +syscallLoader := bpf.NewSyscallLatencyLoader(logger) +syscallCloser, err := syscallLoader.Load() +if err != nil { +t.Skipf("skip syscall eBPF integration test: load syscall_latency: %v", err) +} +t.Cleanup(func() { _ = syscallCloser.Close() }) + +syscallCollector := collector.NewSyscallCollector(logger, syscallLoader) +if err := syscallCollector.Start(ctx); err != nil { +t.Fatalf("start syscall collector: %v", err) +} +t.Cleanup(syscallCollector.Stop) + +tcpLoader := bpf.NewTCPMonitorLoader(logger) +tcpCloser, err := tcpLoader.Load() +if err != nil { +t.Skipf("skip TCP eBPF integration test: load tcp_monitor: %v", err) +} +t.Cleanup(func() { _ = tcpCloser.Close() }) + +tcpCollector := collector.NewTCPCollector(logger, tcpLoader) +if err := tcpCollector.Start(ctx); err != nil { +t.Fatalf("start tcp collector: %v", err) +} +t.Cleanup(tcpCollector.Stop) + +syscallGen, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ +ContainerRequest: testcontainers.ContainerRequest{ +Image: "docker.io/library/alpine:3.19", +Cmd: []string{ +"sh", +"-c", +"for i in $(seq 1 500); do ls -la /etc >/dev/null 2>&1; cat /etc/hostname >/dev/null 2>&1; done; echo 'syscall-gen-done'", +}, +WaitingFor: wait.ForLog("syscall-gen-done").WithStartupTimeout(30 * time.Second), +AlwaysPullImage: false, +}, +Started: true, +}) +if err != nil { +t.Fatalf("start syscall generator container: %v", err) +} +t.Cleanup(func() { +termCtx, termCancel := context.WithTimeout(context.Background(), 15*time.Second) +defer termCancel() +_ = syscallGen.Terminate(termCtx) +}) + +ln, err := net.Listen("tcp", "127.0.0.1:0") +if err != nil { +t.Fatalf("listen on localhost: %v", err) +} +defer ln.Close() + +tcpAddr := ln.Addr().String() +tcpReady := make(chan struct{}) + +go func() { +conn, err := ln.Accept() +if err != nil { +return +} +conn.Close() +}() + +go func() { +time.Sleep(3 * time.Second) +conn, err := net.DialTimeout("tcp", tcpAddr, 5*time.Second) +if err != nil { +t.Logf("tcp dial: %v", err) +close(tcpReady) +return +} +conn.Close() +close(tcpReady) +}() + +select { +case <-tcpReady: +case <-time.After(10 * time.Second): +t.Log("timeout waiting for TCP connection test") +} + +time.Sleep(2 * time.Second) + +syscallSnap, ok := syscallCollector.Snapshot().(*collector.SyscallSnapshot) +if !ok { +t.Fatalf("expected *SyscallSnapshot, got %T", syscallCollector.Snapshot()) +} +if syscallSnap.TotalCount == 0 { +t.Fatalf("expected syscall collector to capture events, got TotalCount=0") +} +t.Logf("Syscall collector captured %d events across %d entries", syscallSnap.TotalCount, len(syscallSnap.Entries)) + +tcpSnap, ok := tcpCollector.Snapshot().(*collector.TCPSnapshot) +if !ok { +t.Fatalf("expected *TCPSnapshot, got %T", tcpCollector.Snapshot()) +} +if tcpSnap.ActiveConnections == 0 && syscallSnap.TotalCount == 0 { +t.Fatalf("expected syscall or TCP collector to capture events, both empty") +} +t.Logf("TCP collector active connections: %d", tcpSnap.ActiveConnections) +}