summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-19 15:00:02 +0300
committerPaul Buetow <paul@buetow.org>2026-05-19 15:00:02 +0300
commit71ef23ae16b0e310e66f3bf622cebefb9ec6b208 (patch)
treed61bc007207fbd3f4e21de34874de0248692b9b2
parent9cc2c7b3c4c7a1f1837a4a5260f11ccea5814c83 (diff)
v6: add KindAccept and wire accept/accept4
-rw-r--r--cmd/ioworkload/scenario_socket.go46
-rw-r--r--cmd/ioworkload/scenarios.go1
-rw-r--r--integrationtests/socket_test.go16
-rw-r--r--internal/c/generated_tracepoints.c30
-rw-r--r--internal/c/generated_tracepoints_result.txt8
-rw-r--r--internal/c/types.h12
-rw-r--r--internal/event/interface_assertions.go3
-rw-r--r--internal/eventloop_exit.go36
-rw-r--r--internal/eventloop_runtime.go14
-rw-r--r--internal/eventloop_socket_test.go38
-rw-r--r--internal/generate/bpfhandler.go9
-rw-r--r--internal/generate/classify.go17
-rw-r--r--internal/generate/classify_test.go49
-rw-r--r--internal/generate/codegen_test.go17
-rw-r--r--internal/generate/kindregistry.go1
-rw-r--r--internal/generate/testdata.go31
-rw-r--r--internal/types/fastdecode.go24
-rw-r--r--internal/types/fastdecode_test.go41
-rw-r--r--internal/types/generated_types.go71
-rw-r--r--internal/types/types_test.go25
20 files changed, 466 insertions, 23 deletions
diff --git a/cmd/ioworkload/scenario_socket.go b/cmd/ioworkload/scenario_socket.go
index f633186..9885cbb 100644
--- a/cmd/ioworkload/scenario_socket.go
+++ b/cmd/ioworkload/scenario_socket.go
@@ -2,6 +2,8 @@ package main
import (
"fmt"
+ "os"
+ "path/filepath"
"syscall"
)
@@ -29,3 +31,47 @@ func socketpairBasic() error {
}
return nil
}
+
+func socketAcceptLifecycle() error {
+ dir, err := os.MkdirTemp("", "ioworkload-socket-accept-")
+ if err != nil {
+ return fmt.Errorf("create temp dir: %w", err)
+ }
+ defer os.RemoveAll(dir)
+
+ socketPath := filepath.Join(dir, "accept.sock")
+
+ listenerFD, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
+ if err != nil {
+ return fmt.Errorf("listener socket: %w", err)
+ }
+ defer syscall.Close(listenerFD) //nolint:errcheck
+
+ if err := syscall.Bind(listenerFD, &syscall.SockaddrUnix{Name: socketPath}); err != nil {
+ return fmt.Errorf("bind: %w", err)
+ }
+ if err := syscall.Listen(listenerFD, 1); err != nil {
+ return fmt.Errorf("listen: %w", err)
+ }
+
+ clientFD, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
+ if err != nil {
+ return fmt.Errorf("client socket: %w", err)
+ }
+ defer syscall.Close(clientFD) //nolint:errcheck
+
+ if err := syscall.Connect(clientFD, &syscall.SockaddrUnix{Name: socketPath}); err != nil {
+ return fmt.Errorf("connect: %w", err)
+ }
+
+ acceptedFD, _, err := syscall.Accept4(listenerFD, 0)
+ if err != nil {
+ return fmt.Errorf("accept4: %w", err)
+ }
+ defer syscall.Close(acceptedFD) //nolint:errcheck
+
+ if err := syscall.Shutdown(acceptedFD, syscall.SHUT_RDWR); err != nil {
+ return fmt.Errorf("shutdown accepted fd: %w", err)
+ }
+ return nil
+}
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index aedbffa..db5b549 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -27,6 +27,7 @@ var scenarios = map[string]func() error{
"retbytes-phase-a": retbytesPhaseA,
"socket-basic": socketBasic,
"socketpair-basic": socketpairBasic,
+ "socket-accept-lifecycle": socketAcceptLifecycle,
"family-mixed": familyMixed,
"close-basic": closeBasic,
"close-range": closeRange,
diff --git a/integrationtests/socket_test.go b/integrationtests/socket_test.go
index 481ca86..14dd9ea 100644
--- a/integrationtests/socket_test.go
+++ b/integrationtests/socket_test.go
@@ -39,6 +39,22 @@ func TestSocketpairBasic(t *testing.T) {
}
}
+func TestSocketAcceptLifecycle(t *testing.T) {
+ result, _ := runScenarioResult(t, "socket-accept-lifecycle", []ExpectedEvent{
+ {Tracepoint: "enter_bind", MinCount: 1},
+ {Tracepoint: "enter_connect", MinCount: 1},
+ {Tracepoint: "enter_listen", MinCount: 1},
+ {Tracepoint: "enter_accept4", MinCount: 1},
+ {Tracepoint: "enter_shutdown", MinCount: 1},
+ })
+
+ assertTracepointPathPrefix(t, result, "enter_bind", "socket:1:")
+ assertTracepointPathPrefix(t, result, "enter_connect", "socket:1:")
+ assertTracepointPathPrefix(t, result, "enter_listen", "socket:1:")
+ assertTracepointPathPrefix(t, result, "enter_accept4", "socket:1:")
+ assertTracepointPathPrefix(t, result, "enter_shutdown", "socket:1:")
+}
+
func assertTracepointPathPrefix(t *testing.T, result TestResult, tracepoint, wantPrefix string) {
t.Helper()
if got := countTracepointPathPrefix(result, tracepoint, wantPrefix); got == 0 {
diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c
index 03cf2b4..0f83f35 100644
--- a/internal/c/generated_tracepoints.c
+++ b/internal/c/generated_tracepoints.c
@@ -952,91 +952,93 @@ int handle_sys_exit_listen(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_accept4 is a struct fd_event
+/// sys_enter_accept4 is a struct accept_event
SEC("tracepoint/syscalls/sys_enter_accept4")
int handle_sys_enter_accept4(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0);
+ struct accept_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct accept_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_FD_EVENT;
+ ev->event_type = ENTER_ACCEPT_EVENT;
ev->trace_id = SYS_ENTER_ACCEPT4;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
ev->fd = (__s32)ctx->args[0];
+ ev->ret = -1;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_exit_accept4 is a struct ret_event (UNCLASSIFIED)
+/// sys_exit_accept4 is a struct accept_event
SEC("tracepoint/syscalls/sys_exit_accept4")
int handle_sys_exit_accept4(struct syscall_trace_exit *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct ret_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct ret_event), 0);
+ struct accept_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct accept_event), 0);
if (!ev)
return 0;
- ev->event_type = EXIT_RET_EVENT;
+ ev->event_type = EXIT_ACCEPT_EVENT;
ev->trace_id = SYS_EXIT_ACCEPT4;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ ev->fd = -1;
ev->ret = ctx->ret;
- ev->ret_type = UNCLASSIFIED;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_enter_accept is a struct fd_event
+/// sys_enter_accept is a struct accept_event
SEC("tracepoint/syscalls/sys_enter_accept")
int handle_sys_enter_accept(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0);
+ struct accept_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct accept_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_FD_EVENT;
+ ev->event_type = ENTER_ACCEPT_EVENT;
ev->trace_id = SYS_ENTER_ACCEPT;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
ev->fd = (__s32)ctx->args[0];
+ ev->ret = -1;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_exit_accept is a struct ret_event (UNCLASSIFIED)
+/// sys_exit_accept is a struct accept_event
SEC("tracepoint/syscalls/sys_exit_accept")
int handle_sys_exit_accept(struct syscall_trace_exit *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct ret_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct ret_event), 0);
+ struct accept_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct accept_event), 0);
if (!ev)
return 0;
- ev->event_type = EXIT_RET_EVENT;
+ ev->event_type = EXIT_ACCEPT_EVENT;
ev->trace_id = SYS_EXIT_ACCEPT;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ ev->fd = -1;
ev->ret = ctx->ret;
- ev->ret_type = UNCLASSIFIED;
bpf_ringbuf_submit(ev, 0);
return 0;
diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt
index 560e24b..a2ad3ca 100644
--- a/internal/c/generated_tracepoints_result.txt
+++ b/internal/c/generated_tracepoints_result.txt
@@ -1,5 +1,5 @@
-sys_enter_accept is a struct fd_event
-sys_enter_accept4 is a struct fd_event
+sys_enter_accept is a struct accept_event
+sys_enter_accept4 is a struct accept_event
sys_enter_access is a struct path_event
sys_enter_acct is a struct null_event
sys_enter_add_key is a struct null_event
@@ -365,8 +365,8 @@ sys_enter_wait4 is a struct null_event
sys_enter_waitid is a struct null_event
sys_enter_write is a struct fd_event
sys_enter_writev is a struct fd_event
-sys_exit_accept is a struct ret_event (UNCLASSIFIED)
-sys_exit_accept4 is a struct ret_event (UNCLASSIFIED)
+sys_exit_accept is a struct accept_event
+sys_exit_accept4 is a struct accept_event
sys_exit_access is a struct ret_event (UNCLASSIFIED)
sys_exit_acct is a struct ret_event (UNCLASSIFIED)
sys_exit_add_key is a struct ret_event (UNCLASSIFIED)
diff --git a/internal/c/types.h b/internal/c/types.h
index 29f18e1..6365e3f 100644
--- a/internal/c/types.h
+++ b/internal/c/types.h
@@ -25,6 +25,8 @@
#define EXIT_SOCKET_EVENT 20
#define ENTER_SOCKETPAIR_EVENT 21
#define EXIT_SOCKETPAIR_EVENT 22
+#define ENTER_ACCEPT_EVENT 23
+#define EXIT_ACCEPT_EVENT 24
#define UNCLASSIFIED 0
#define READ_CLASSIFIED 1
@@ -143,3 +145,13 @@ struct socketpair_event {
__s32 sv1;
__s64 ret;
};
+
+struct accept_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+ __s32 fd;
+ __s64 ret;
+};
diff --git a/internal/event/interface_assertions.go b/internal/event/interface_assertions.go
index 3dce64d..fea20ba 100644
--- a/internal/event/interface_assertions.go
+++ b/internal/event/interface_assertions.go
@@ -47,4 +47,7 @@ var (
// *types.SocketpairEvent carries socketpair domain/type/protocol metadata.
_ Event = (*types.SocketpairEvent)(nil)
+
+ // *types.AcceptEvent carries listening-fd input and accepted-fd return metadata.
+ _ Event = (*types.AcceptEvent)(nil)
)
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index c64515b..9953c13 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -30,6 +30,8 @@ func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool {
return e.handleSocketExit(ep, ev)
case *types.SocketpairEvent:
return e.handleSocketpairExit(ep, ev)
+ case *types.AcceptEvent:
+ return e.handleAcceptExit(ep, ev)
case *types.NullEvent:
return e.handleNullExit(ep, ev)
case *types.FcntlEvent:
@@ -282,10 +284,44 @@ func (e *eventLoop) handleSocketpairExit(ep *event.Pair, socketpairEv *types.Soc
return true
}
+func (e *eventLoop) handleAcceptExit(ep *event.Pair, acceptEv *types.AcceptEvent) bool {
+ exitEv, ok := ep.ExitEv.(*types.AcceptEvent)
+ if !ok {
+ e.recyclePair(ep, "Dropped malformed accept exit event")
+ return false
+ }
+
+ listening := e.fdState().resolve(acceptEv.Fd, acceptEv.Pid)
+ if fd := int32(exitEv.Ret); fd >= 0 {
+ fdFile := file.NewFd(fd, acceptedSocketDescriptorName(listening), -1)
+ e.fdState().set(fd, fdFile)
+ ep.File = fdFile
+ } else {
+ ep.File = listening
+ }
+ ep.Comm = e.comm(acceptEv.GetTid())
+ if !e.Filter().MatchPair(ep) {
+ ep.Recycle()
+ return false
+ }
+ return true
+}
+
func socketDescriptorName(family, typ, protocol int32) string {
return fmt.Sprintf("socket:%d:%d:%d", family, typ, protocol)
}
+func acceptedSocketDescriptorName(listening file.File) string {
+ if listening == nil {
+ return "socket:accepted"
+ }
+ name := listening.Name()
+ if name == "" {
+ return "socket:accepted"
+ }
+ return name
+}
+
func (e *eventLoop) handleNullExit(ep *event.Pair, nullEv *types.NullEvent) bool {
if ep.Is(types.SYS_ENTER_IO_URING_SETUP) {
retEvent, ok := ep.ExitEv.(*types.RetEvent)
diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go
index 55948bc..94648e0 100644
--- a/internal/eventloop_runtime.go
+++ b/internal/eventloop_runtime.go
@@ -285,6 +285,20 @@ func (e *eventLoop) registerSocketHandlers() {
}
e.tracepointExited(socketpairEv, ch)
}
+ e.rawHandlers[types.ENTER_ACCEPT_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
+ acceptEv, ok := decodeRawEvent(e, types.ENTER_ACCEPT_EVENT, raw, types.NewAcceptEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointEntered(acceptEv)
+ }
+ e.rawHandlers[types.EXIT_ACCEPT_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
+ acceptEv, ok := decodeRawEvent(e, types.EXIT_ACCEPT_EVENT, raw, types.NewAcceptEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointExited(acceptEv, ch)
+ }
}
func decodeRawEvent[T any](e *eventLoop, eventType types.EventType, raw []byte, decode func([]byte) *T) (*T, bool) {
diff --git a/internal/eventloop_socket_test.go b/internal/eventloop_socket_test.go
index 7712b3c..19c1fa8 100644
--- a/internal/eventloop_socket_test.go
+++ b/internal/eventloop_socket_test.go
@@ -4,6 +4,7 @@ import (
"testing"
"ior/internal/event"
+ "ior/internal/file"
"ior/internal/globalfilter"
"ior/internal/types"
)
@@ -107,6 +108,37 @@ func TestHandleSocketpairExitTracksReturnedFdsFromExitEvent(t *testing.T) {
verifyFileDescriptor(t, el, 62, "socket:1:1:0")
}
+func TestHandleAcceptExitTracksAcceptedFd(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+
+ el.fdState().set(11, file.NewFd(11, "socket:1:1:0", -1))
+
+ enter := &types.AcceptEvent{
+ EventType: types.ENTER_ACCEPT_EVENT,
+ TraceId: types.SYS_ENTER_ACCEPT4,
+ Time: 100,
+ Pid: 91,
+ Tid: 92,
+ Fd: 11,
+ Ret: -1,
+ }
+ exit := &types.AcceptEvent{
+ EventType: types.EXIT_ACCEPT_EVENT,
+ TraceId: types.SYS_EXIT_ACCEPT4,
+ Time: 200,
+ Pid: 91,
+ Tid: 92,
+ Fd: -1,
+ Ret: 77,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handleAcceptExit(ep, enter); !ok {
+ t.Fatal("handleAcceptExit returned false")
+ }
+ verifyFileDescriptor(t, el, 77, "socket:1:1:0")
+}
+
func TestInitRawHandlersRegistersSocketEvents(t *testing.T) {
el := mustNewEventLoop(t, eventLoopConfig{})
if _, ok := el.rawHandlers[types.ENTER_SOCKET_EVENT]; !ok {
@@ -118,4 +150,10 @@ func TestInitRawHandlersRegistersSocketEvents(t *testing.T) {
if _, ok := el.rawHandlers[types.EXIT_SOCKETPAIR_EVENT]; !ok {
t.Fatal("EXIT_SOCKETPAIR_EVENT handler is not registered")
}
+ if _, ok := el.rawHandlers[types.ENTER_ACCEPT_EVENT]; !ok {
+ t.Fatal("ENTER_ACCEPT_EVENT handler is not registered")
+ }
+ if _, ok := el.rawHandlers[types.EXIT_ACCEPT_EVENT]; !ok {
+ t.Fatal("EXIT_ACCEPT_EVENT handler is not registered")
+ }
}
diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go
index 50917e7..bada317 100644
--- a/internal/generate/bpfhandler.go
+++ b/internal/generate/bpfhandler.go
@@ -77,6 +77,8 @@ func generateExtra(tp GeneratedTracepoint, isEnter bool) string {
return generateExtraSocket()
case KindSocketpair:
return generateExtraSocketpair(isEnter)
+ case KindAccept:
+ return generateExtraAccept(isEnter)
case KindOpen:
return generateExtraOpen(f)
case KindPathname:
@@ -164,6 +166,13 @@ func generateExtraSocketpair(isEnter bool) string {
return " __s32 family = -1;\n __s32 type = -1;\n __s32 protocol = -1;\n __s32 sv0 = -1;\n __s32 sv1 = -1;\n struct socketpair_ctx *pending = bpf_map_lookup_elem(&socketpair_ctx_map, &tid);\n if (pending) {\n family = pending->family;\n type = pending->type;\n protocol = pending->protocol;\n if (ctx->ret == 0 && pending->usockvec != 0) {\n int sv[2];\n if (bpf_probe_read_user(&sv, sizeof(sv), (void *)pending->usockvec) == 0) {\n sv0 = (__s32)sv[0];\n sv1 = (__s32)sv[1];\n }\n }\n bpf_map_delete_elem(&socketpair_ctx_map, &tid);\n }\n ev->family = family;\n ev->type = type;\n ev->protocol = protocol;\n ev->sv0 = sv0;\n ev->sv1 = sv1;\n ev->ret = ctx->ret;\n"
}
+func generateExtraAccept(isEnter bool) string {
+ if isEnter {
+ return " ev->fd = (__s32)ctx->args[0];\n ev->ret = -1;\n"
+ }
+ return " ev->fd = -1;\n ev->ret = ctx->ret;\n"
+}
+
// eventStructName returns the C struct name for a TracepointKind. The mapping
// is driven by kindRegistry so adding a new kind only requires a registry entry.
func eventStructName(kind TracepointKind) string {
diff --git a/internal/generate/classify.go b/internal/generate/classify.go
index 56f5cd2..abe6b2e 100644
--- a/internal/generate/classify.go
+++ b/internal/generate/classify.go
@@ -17,6 +17,7 @@ const (
KindOpenByHandleAt
KindSocket
KindSocketpair
+ KindAccept
)
type RetClassification string
@@ -87,6 +88,22 @@ func classifyNameOnly(name string) (ClassificationResult, bool) {
return ClassificationResult{Kind: KindSocketpair}, true
case "sys_exit_socketpair":
return ClassificationResult{Kind: KindSocketpair}, true
+ case "sys_enter_accept":
+ return ClassificationResult{Kind: KindAccept}, true
+ case "sys_exit_accept":
+ return ClassificationResult{Kind: KindAccept}, true
+ case "sys_enter_accept4":
+ return ClassificationResult{Kind: KindAccept}, true
+ case "sys_exit_accept4":
+ return ClassificationResult{Kind: KindAccept}, true
+ case "sys_enter_bind":
+ return ClassificationResult{Kind: KindFd}, true
+ case "sys_enter_connect":
+ return ClassificationResult{Kind: KindFd}, true
+ case "sys_enter_listen":
+ return ClassificationResult{Kind: KindFd}, true
+ case "sys_enter_shutdown":
+ return ClassificationResult{Kind: KindFd}, true
}
if strings.HasPrefix(name, "sys_enter_io_") {
return ClassificationResult{Kind: KindNull}, true
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go
index e6353b5..07cfe49 100644
--- a/internal/generate/classify_test.go
+++ b/internal/generate/classify_test.go
@@ -237,10 +237,49 @@ func TestClassifyPathnameExecve(t *testing.T) {
}
}
-func TestClassifyFdAccept(t *testing.T) {
+func TestClassifyAccept(t *testing.T) {
r := classifyFromData(t, FormatAccept)
- if r.Kind != KindFd {
- t.Errorf("accept: got kind %d, want KindFd", r.Kind)
+ if r.Kind != KindAccept {
+ t.Errorf("accept: got kind %d, want KindAccept", r.Kind)
+ }
+}
+
+func TestClassifyAccept4(t *testing.T) {
+ r := classifyFromData(t, FormatAccept4)
+ if r.Kind != KindAccept {
+ t.Errorf("accept4: got kind %d, want KindAccept", r.Kind)
+ }
+}
+
+func TestClassifyExitAccept(t *testing.T) {
+ r := classifyFromData(t, FormatExitAccept)
+ if r.Kind != KindAccept {
+ t.Errorf("exit_accept: got kind %d, want KindAccept", r.Kind)
+ }
+}
+
+func TestClassifyExitAccept4(t *testing.T) {
+ r := classifyFromData(t, FormatExitAccept4)
+ if r.Kind != KindAccept {
+ t.Errorf("exit_accept4: got kind %d, want KindAccept", r.Kind)
+ }
+}
+
+func TestClassifySocketLifecycleFdSyscallsByName(t *testing.T) {
+ tests := []string{"bind", "connect", "listen", "shutdown"}
+ for _, name := range tests {
+ t.Run(name, func(t *testing.T) {
+ r := ClassifyFormat(&Format{
+ Name: "sys_enter_" + name,
+ ExternalFields: []Field{
+ {Type: "long", Name: "__syscall_nr"},
+ {Type: "int", Name: "sockfd"},
+ },
+ })
+ if r.Kind != KindFd {
+ t.Errorf("%s: got kind %d, want KindFd", name, r.Kind)
+ }
+ })
}
}
@@ -301,7 +340,8 @@ func TestClassifySyscallPairAccepted(t *testing.T) {
{"symlink", FormatSymlink, FormatExitSymlink, KindName},
{"mknod", FormatMknod, FormatExitMknod, KindPathname},
{"execve", FormatExecve, FormatExitExecve, KindPathname},
- {"accept", FormatAccept, FormatExitAccept, KindFd},
+ {"accept", FormatAccept, FormatExitAccept, KindAccept},
+ {"accept4", FormatAccept4, FormatExitAccept4, KindAccept},
{"socket", FormatSocket, FormatExitSocket, KindSocket},
{"socketpair", FormatSocketpair, FormatExitSocketpair, KindSocketpair},
{"kill", FormatKill, FormatExitKill, KindNull},
@@ -328,6 +368,7 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) {
{"mknod", FormatMknod, FormatExitMknod, FamilyFS},
{"execve", FormatExecve, FormatExitExecve, FamilyProcess},
{"accept", FormatAccept, FormatExitAccept, FamilyNetwork},
+ {"accept4", FormatAccept4, FormatExitAccept4, FamilyNetwork},
{"socket", FormatSocket, FormatExitSocket, FamilyNetwork},
{"socketpair", FormatSocketpair, FormatExitSocketpair, FamilyNetwork},
{"kill", FormatKill, FormatExitKill, FamilySignals},
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go
index 3e29612..d8130c8 100644
--- a/internal/generate/codegen_test.go
+++ b/internal/generate/codegen_test.go
@@ -212,6 +212,19 @@ func TestGenerateSocketpairHandler(t *testing.T) {
requireContains(t, output, "ev->family = pending.family;")
}
+func TestGenerateAcceptHandler(t *testing.T) {
+ output := generateFromPair(t, FormatAccept, FormatExitAccept)
+
+ requireContains(t, output, "struct accept_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_ACCEPT_EVENT;")
+ requireContains(t, output, "ev->fd = (__s32)ctx->args[0];")
+ requireContains(t, output, "ev->ret = -1;")
+ requireContains(t, output, "SEC(\"tracepoint/syscalls/sys_exit_accept\")")
+ requireContains(t, output, "ev->event_type = EXIT_ACCEPT_EVENT;")
+ requireContains(t, output, "ev->fd = -1;")
+ requireContains(t, output, "ev->ret = ctx->ret;")
+}
+
func TestGenerateNameToHandleAtHandler(t *testing.T) {
output := generateFromPair(t, FormatNameToHandleAt, FormatExitNameToHandleAt)
@@ -321,6 +334,7 @@ func TestGenerateAllEventTypes(t *testing.T) {
{KindOpenByHandleAt, "ENTER_OPEN_BY_HANDLE_AT_EVENT", "EXIT_OPEN_BY_HANDLE_AT_EVENT"},
{KindSocket, "ENTER_SOCKET_EVENT", "EXIT_SOCKET_EVENT"},
{KindSocketpair, "ENTER_SOCKETPAIR_EVENT", "EXIT_SOCKETPAIR_EVENT"},
+ {KindAccept, "ENTER_ACCEPT_EVENT", "EXIT_ACCEPT_EVENT"},
}
for _, tt := range tests {
@@ -349,6 +363,7 @@ func TestEventStructNames(t *testing.T) {
{KindOpenByHandleAt, "open_by_handle_at_event"},
{KindSocket, "socket_event"},
{KindSocketpair, "socketpair_event"},
+ {KindAccept, "accept_event"},
}
for _, tt := range tests {
@@ -367,7 +382,7 @@ func TestEnterReject(t *testing.T) {
t.Error("KindNone should be enter-rejected")
}
- accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair}
+ accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept}
for _, k := range accepted {
if isEnterRejected(k) {
t.Errorf("kind %d should NOT be enter-rejected", k)
diff --git a/internal/generate/kindregistry.go b/internal/generate/kindregistry.go
index f716489..186dbbc 100644
--- a/internal/generate/kindregistry.go
+++ b/internal/generate/kindregistry.go
@@ -27,6 +27,7 @@ var kindRegistry = map[TracepointKind]kindMeta{
KindOpenByHandleAt: {structName: "open_by_handle_at_event", enterAccepted: true},
KindSocket: {structName: "socket_event", enterAccepted: true},
KindSocketpair: {structName: "socketpair_event", enterAccepted: true},
+ KindAccept: {structName: "accept_event", enterAccepted: true},
// KindNone is intentionally absent: it represents "unclassified" and is
// never enter-accepted. lookupKind returns the zero kindMeta (enterAccepted=false)
// for any unregistered kind, so KindNone is implicitly rejected.
diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go
index e363a68..090619b 100644
--- a/internal/generate/testdata.go
+++ b/internal/generate/testdata.go
@@ -793,6 +793,37 @@ format:
print fmt: "0x%lx", REC->ret
`
+const FormatAccept4 = `name: sys_enter_accept4
+ID: 1810
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:int fd; offset:16; size:8; signed:0;
+ field:struct sockaddr * upeer_sockaddr; offset:24; size:8; signed:0;
+ field:int * upeer_addrlen; offset:32; size:8; signed:0;
+ field:int flags; offset:40; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx, upeer_sockaddr: 0x%08lx, upeer_addrlen: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->upeer_sockaddr)), ((unsigned long)(REC->upeer_addrlen)), ((unsigned long)(REC->flags))
+`
+
+const FormatExitAccept4 = `name: sys_exit_accept4
+ID: 1809
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
const FormatSocket = `name: sys_enter_socket
ID: 1818
format:
diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go
index 0967c02..e8689da 100644
--- a/internal/types/fastdecode.go
+++ b/internal/types/fastdecode.go
@@ -15,6 +15,8 @@ const (
socketEventSize = 36
socketpairEventSize = 56
socketpairEventSizeV1 = 52
+ acceptEventSize = 40
+ acceptEventSizeV1 = 36
)
func NewOpenEventFast(raw []byte) *OpenEvent {
@@ -220,3 +222,25 @@ func NewSocketpairEventFast(raw []byte) *SocketpairEvent {
s.Ret = int64(binary.LittleEndian.Uint64(raw[retOffset : retOffset+8]))
return s
}
+
+func NewAcceptEventFast(raw []byte) *AcceptEvent {
+ if len(raw) < acceptEventSizeV1 {
+ return nil
+ }
+ if len(raw) != acceptEventSize && len(raw) != acceptEventSizeV1 {
+ return NewAcceptEvent(raw)
+ }
+ a := poolOfAcceptEvents.Get().(*AcceptEvent)
+ a.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4]))
+ a.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8]))
+ a.Time = binary.LittleEndian.Uint64(raw[8:16])
+ a.Pid = binary.LittleEndian.Uint32(raw[16:20])
+ a.Tid = binary.LittleEndian.Uint32(raw[20:24])
+ a.Fd = int32(binary.LittleEndian.Uint32(raw[24:28]))
+ retOffset := 28
+ if len(raw) == acceptEventSize {
+ retOffset = 32
+ }
+ a.Ret = int64(binary.LittleEndian.Uint64(raw[retOffset : retOffset+8]))
+ return a
+}
diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go
index f15cc9e..d806b45 100644
--- a/internal/types/fastdecode_test.go
+++ b/internal/types/fastdecode_test.go
@@ -153,6 +153,19 @@ func TestFastDecodersMatchGeneratedDecoders(t *testing.T) {
t.Fatalf("socketpair decode mismatch")
}
})
+
+ t.Run("AcceptEvent", func(t *testing.T) {
+ ev := &AcceptEvent{EventType: ENTER_ACCEPT_EVENT, TraceId: SYS_ENTER_ACCEPT4, Time: 1, Pid: 2, Tid: 3, Fd: 4, Ret: -1}
+ raw, _ := ev.Bytes()
+
+ slow := NewAcceptEvent(raw)
+ fast := NewAcceptEventFast(raw)
+ defer slow.Recycle()
+ defer fast.Recycle()
+ if !slow.Equals(fast) {
+ t.Fatalf("accept decode mismatch")
+ }
+ })
}
func TestNewSocketpairEventFastKernelLayout(t *testing.T) {
@@ -190,6 +203,33 @@ func TestNewSocketpairEventFastKernelLayout(t *testing.T) {
}
}
+func TestNewAcceptEventFastKernelLayout(t *testing.T) {
+ raw := make([]byte, acceptEventSize)
+ binary.LittleEndian.PutUint32(raw[0:4], uint32(EXIT_ACCEPT_EVENT))
+ binary.LittleEndian.PutUint32(raw[4:8], uint32(SYS_EXIT_ACCEPT4))
+ binary.LittleEndian.PutUint64(raw[8:16], 1)
+ binary.LittleEndian.PutUint32(raw[16:20], 2)
+ binary.LittleEndian.PutUint32(raw[20:24], 3)
+ binary.LittleEndian.PutUint32(raw[24:28], uint32(10))
+ binary.LittleEndian.PutUint64(raw[32:40], uint64(42))
+
+ fast := NewAcceptEventFast(raw)
+ if fast == nil {
+ t.Fatalf("expected decoded accept event for kernel layout payload")
+ }
+ defer fast.Recycle()
+
+ if fast.EventType != EXIT_ACCEPT_EVENT ||
+ fast.TraceId != SYS_EXIT_ACCEPT4 ||
+ fast.Time != 1 ||
+ fast.Pid != 2 ||
+ fast.Tid != 3 ||
+ fast.Fd != 10 ||
+ fast.Ret != 42 {
+ t.Fatalf("unexpected accept decode: %#v", fast)
+ }
+}
+
func TestFastDecodersReturnNilOnShortPayload(t *testing.T) {
cases := []struct {
name string
@@ -206,6 +246,7 @@ func TestFastDecodersReturnNilOnShortPayload(t *testing.T) {
{name: "OpenByHandleAtEvent", decode: func(raw []byte) bool { return NewOpenByHandleAtEventFast(raw) == nil }},
{name: "SocketEvent", decode: func(raw []byte) bool { return NewSocketEventFast(raw) == nil }},
{name: "SocketpairEvent", decode: func(raw []byte) bool { return NewSocketpairEventFast(raw) == nil }},
+ {name: "AcceptEvent", decode: func(raw []byte) bool { return NewAcceptEventFast(raw) == nil }},
}
for _, tc := range cases {
diff --git a/internal/types/generated_types.go b/internal/types/generated_types.go
index bdfce73..05c869a 100644
--- a/internal/types/generated_types.go
+++ b/internal/types/generated_types.go
@@ -90,6 +90,8 @@ const ENTER_SOCKET_EVENT = 19
const EXIT_SOCKET_EVENT = 20
const ENTER_SOCKETPAIR_EVENT = 21
const EXIT_SOCKETPAIR_EVENT = 22
+const ENTER_ACCEPT_EVENT = 23
+const EXIT_ACCEPT_EVENT = 24
const UNCLASSIFIED = 0
const READ_CLASSIFIED = 1
const WRITE_CLASSIFIED = 2
@@ -1589,3 +1591,72 @@ func (s *SocketpairEvent) Bytes() ([]byte, error) {
func (s *SocketpairEvent) Recycle() {
poolOfSocketpairEvents.Put(s)
}
+
+type AcceptEvent struct {
+ EventType EventType
+ TraceId TraceId
+ Time uint64
+ Pid uint32
+ Tid uint32
+ Fd int32
+ Ret int64
+}
+
+func (a AcceptEvent) String() string {
+ return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Fd:%v Ret:%v", a.EventType, a.TraceId, a.Time, a.Pid, a.Tid, a.Fd, a.Ret)
+}
+
+func (a AcceptEvent) Equals(other any) bool {
+ otherConcrete, ok := other.(*AcceptEvent)
+ if !ok {
+ return false
+ }
+ return a.EventType == otherConcrete.EventType && a.TraceId == otherConcrete.TraceId && a.Time == otherConcrete.Time && a.Pid == otherConcrete.Pid && a.Tid == otherConcrete.Tid && a.Fd == otherConcrete.Fd && a.Ret == otherConcrete.Ret
+}
+
+func (a *AcceptEvent) GetEventType() EventType {
+ return a.EventType
+}
+
+func (a *AcceptEvent) GetTraceId() TraceId {
+ return a.TraceId
+}
+
+func (a *AcceptEvent) GetPid() uint32 {
+ return a.Pid
+}
+
+func (a *AcceptEvent) GetTid() uint32 {
+ return a.Tid
+}
+
+func (a *AcceptEvent) GetTime() uint64 {
+ return a.Time
+}
+
+var poolOfAcceptEvents = sync.Pool{
+ New: func() any { return &AcceptEvent{} },
+}
+
+func NewAcceptEvent(raw []byte) *AcceptEvent {
+ a := poolOfAcceptEvents.Get().(*AcceptEvent)
+ if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, a); err != nil {
+ *a = AcceptEvent{}
+ poolOfAcceptEvents.Put(a)
+ return nil
+ }
+ return a
+}
+
+func (a *AcceptEvent) Bytes() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ err := binary.Write(buf, binary.LittleEndian, a)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func (a *AcceptEvent) Recycle() {
+ poolOfAcceptEvents.Put(a)
+}
diff --git a/internal/types/types_test.go b/internal/types/types_test.go
index 40b5090..d47609a 100644
--- a/internal/types/types_test.go
+++ b/internal/types/types_test.go
@@ -190,6 +190,31 @@ func TestSocketpairEventSerialization(t *testing.T) {
assertEquals(t, socketpairEv1.Ret, socketpairEv2.Ret)
}
+func TestAcceptEventSerialization(t *testing.T) {
+ acceptEv1 := AcceptEvent{
+ EventType: ENTER_ACCEPT_EVENT,
+ TraceId: SYS_ENTER_ACCEPT4,
+ Time: 3456,
+ Pid: 34,
+ Tid: 35,
+ Fd: 9,
+ Ret: -1,
+ }
+ bytes, err := acceptEv1.Bytes()
+ if err != nil {
+ t.Error(err)
+ }
+ acceptEv2 := NewAcceptEvent(bytes)
+
+ assertEquals(t, acceptEv1.EventType, acceptEv2.EventType)
+ assertEquals(t, acceptEv1.TraceId, acceptEv2.TraceId)
+ assertEquals(t, acceptEv1.Time, acceptEv2.Time)
+ assertEquals(t, acceptEv1.Pid, acceptEv2.Pid)
+ assertEquals(t, acceptEv1.Tid, acceptEv2.Tid)
+ assertEquals(t, acceptEv1.Fd, acceptEv2.Fd)
+ assertEquals(t, acceptEv1.Ret, acceptEv2.Ret)
+}
+
func TestEqualsDifferentTypes(t *testing.T) {
openEv := OpenEvent{EventType: ENTER_OPEN_EVENT, TraceId: SYS_ENTER_OPENAT, Time: 1, Pid: 1, Tid: 1}
nullEv := NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 1, Pid: 1, Tid: 1}