From c8cfe759d05a6a33523737b0d463252fc93cf88c Mon Sep 17 00:00:00 2001 From: Christiano Haesbaert Date: Mon, 1 Jul 2024 19:06:10 +0200 Subject: [PATCH 1/2] Basic ringbuf event statistics This adds a new structure `ebpf_event_stats` where we accumulate stats, right now it only counts lost events vs non-lost events, but we can grow this in the future, like adding per type events if it's interesting enough. The print in EventsTrace is quite primitive as I believe this will only be used for debugging there, so heh. Choosing a name for `ebpf_ringbuf_write` was a bit tricky, I wanted to keep the same parameters but it had to be "different enough" that we could spot it with a naked eye. While here, adjust the enum of ebpf events to not skip that precious first bit. --- GPL/Events/EbpfEventProto.h | 47 ++++++++++++++---------- GPL/Events/File/Probe.bpf.c | 10 ++--- GPL/Events/Helpers.h | 21 +++++++++++ GPL/Events/Process/Probe.bpf.c | 12 +++--- GPL/Events/Varlen.h | 2 +- non-GPL/Events/EventsTrace/EventsTrace.c | 15 +++++++- non-GPL/Events/Lib/EbpfEvents.c | 21 +++++++++++ non-GPL/Events/Lib/EbpfEvents.h | 6 +++ 8 files changed, 101 insertions(+), 33 deletions(-) diff --git a/GPL/Events/EbpfEventProto.h b/GPL/Events/EbpfEventProto.h index d858832c..c3919c9e 100644 --- a/GPL/Events/EbpfEventProto.h +++ b/GPL/Events/EbpfEventProto.h @@ -19,26 +19,27 @@ #endif enum ebpf_event_type { - EBPF_EVENT_PROCESS_FORK = (1 << 1), - EBPF_EVENT_PROCESS_EXEC = (1 << 2), - EBPF_EVENT_PROCESS_EXIT = (1 << 3), - EBPF_EVENT_PROCESS_SETSID = (1 << 4), - EBPF_EVENT_PROCESS_SETUID = (1 << 5), - EBPF_EVENT_PROCESS_SETGID = (1 << 6), - EBPF_EVENT_PROCESS_TTY_WRITE = (1 << 7), - EBPF_EVENT_FILE_DELETE = (1 << 8), - EBPF_EVENT_FILE_CREATE = (1 << 9), - EBPF_EVENT_FILE_RENAME = (1 << 10), - EBPF_EVENT_FILE_MODIFY = (1 << 11), - EBPF_EVENT_FILE_MEMFD_OPEN = (1 << 12), - EBPF_EVENT_FILE_SHMEM_OPEN = (1 << 13), - EBPF_EVENT_NETWORK_CONNECTION_ACCEPTED = (1 << 14), - EBPF_EVENT_NETWORK_CONNECTION_ATTEMPTED = (1 << 15), - EBPF_EVENT_NETWORK_CONNECTION_CLOSED = (1 << 16), - EBPF_EVENT_PROCESS_MEMFD_CREATE = (1 << 17), - EBPF_EVENT_PROCESS_SHMGET = (1 << 18), - EBPF_EVENT_PROCESS_PTRACE = (1 << 19), - EBPF_EVENT_PROCESS_LOAD_MODULE = (1 << 20), + EBPF_EVENT_PROCESS_INVALID = 0, + EBPF_EVENT_PROCESS_FORK = (1 << 0), + EBPF_EVENT_PROCESS_EXEC = (1 << 1), + EBPF_EVENT_PROCESS_EXIT = (1 << 2), + EBPF_EVENT_PROCESS_SETSID = (1 << 3), + EBPF_EVENT_PROCESS_SETUID = (1 << 4), + EBPF_EVENT_PROCESS_SETGID = (1 << 5), + EBPF_EVENT_PROCESS_TTY_WRITE = (1 << 6), + EBPF_EVENT_FILE_DELETE = (1 << 7), + EBPF_EVENT_FILE_CREATE = (1 << 8), + EBPF_EVENT_FILE_RENAME = (1 << 9), + EBPF_EVENT_FILE_MODIFY = (1 << 10), + EBPF_EVENT_FILE_MEMFD_OPEN = (1 << 11), + EBPF_EVENT_FILE_SHMEM_OPEN = (1 << 12), + EBPF_EVENT_NETWORK_CONNECTION_ACCEPTED = (1 << 13), + EBPF_EVENT_NETWORK_CONNECTION_ATTEMPTED = (1 << 14), + EBPF_EVENT_NETWORK_CONNECTION_CLOSED = (1 << 15), + EBPF_EVENT_PROCESS_MEMFD_CREATE = (1 << 16), + EBPF_EVENT_PROCESS_SHMGET = (1 << 17), + EBPF_EVENT_PROCESS_PTRACE = (1 << 18), + EBPF_EVENT_PROCESS_LOAD_MODULE = (1 << 19), }; struct ebpf_event_header { @@ -378,4 +379,10 @@ struct ebpf_net_event { char comm[TASK_COMM_LEN]; } __attribute__((packed)); +// Basic event statistics +struct ebpf_event_stats { + uint64_t lost; // lost events due to a full ringbuffer + uint64_t sent; // events sent through the ringbuffer +}; + #endif // EBPF_EVENTPROBE_EBPFEVENTPROTO_H diff --git a/GPL/Events/File/Probe.bpf.c b/GPL/Events/File/Probe.bpf.c index fa140847..7beb4d66 100644 --- a/GPL/Events/File/Probe.bpf.c +++ b/GPL/Events/File/Probe.bpf.c @@ -155,7 +155,7 @@ static int vfs_unlink__exit(int ret) size = ebpf_resolve_pids_ss_cgroup_path_to_string(field->data, task); ebpf_vl_field__set_size(&event->vl_fields, field, size); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); // Certain filesystems (eg. overlayfs) call vfs_unlink twice during the same // execution context. @@ -263,10 +263,10 @@ static void prepare_and_send_file_event(struct file *f, if (path_prefix) { if ((path_prefix_len > 0) && (size >= path_prefix_len)) { if (is_equal_prefix(field->data, path_prefix, path_prefix_len)) - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); } } else { - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); } } @@ -516,7 +516,7 @@ static int vfs_rename__exit(int ret) size = ebpf_resolve_pids_ss_cgroup_path_to_string(field->data, task); ebpf_vl_field__set_size(&event->vl_fields, field, size); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); // Certain filesystems (eg. overlayfs) call vfs_rename twice during the same // execution context. @@ -588,7 +588,7 @@ static void file_modify_event__emit(enum ebpf_file_change_type typ, struct path size = ebpf_resolve_pids_ss_cgroup_path_to_string(field->data, task); ebpf_vl_field__set_size(&event->vl_fields, field, size); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); out: return; diff --git a/GPL/Events/Helpers.h b/GPL/Events/Helpers.h index d5e7bcd6..92e5df5b 100644 --- a/GPL/Events/Helpers.h +++ b/GPL/Events/Helpers.h @@ -295,6 +295,27 @@ static void ebpf_comm__fill(char *comm, size_t len, const struct task_struct *ta read_kernel_str_or_empty_str(comm, len, BPF_CORE_READ(task, comm)); } +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, u32); + __type(value, struct ebpf_event_stats); + __uint(max_entries, 1); +} ringbuf_stats SEC(".maps"); + +static long ebpf_ringbuf_write(void *ringbuf, void *data, u64 size, u64 flags) +{ + long r; + struct ebpf_event_stats *ees; + u32 zero = 0; + + r = bpf_ringbuf_output(ringbuf, data, size, flags); + ees = bpf_map_lookup_elem(&ringbuf_stats, &zero); + if (ees != NULL) + r == 0 ? ees->sent++ : ees->lost++; + + return (r); +} + static bool is_kernel_thread(const struct task_struct *task) { // All kernel threads are children of kthreadd, which always has pid 2 diff --git a/GPL/Events/Process/Probe.bpf.c b/GPL/Events/Process/Probe.bpf.c index 00b5cb08..8970943f 100644 --- a/GPL/Events/Process/Probe.bpf.c +++ b/GPL/Events/Process/Probe.bpf.c @@ -75,7 +75,7 @@ int BPF_PROG(sched_process_fork, const struct task_struct *parent, const struct size = ebpf_resolve_path_to_string(field->data, &child->fs->pwd, child); ebpf_vl_field__set_size(&event->vl_fields, field, size); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); out: return 0; @@ -165,7 +165,7 @@ int BPF_PROG(sched_process_exec, size = read_kernel_str_or_empty_str(field->data, PATH_MAX, binprm->filename); ebpf_vl_field__set_size(&event->vl_fields, field, size); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); out: return 0; @@ -219,7 +219,7 @@ static int taskstats_exit__enter(const struct task_struct *task, int group_dead) size = ebpf_resolve_pids_ss_cgroup_path_to_string(field->data, task); ebpf_vl_field__set_size(&event->vl_fields, field, size); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); out: return 0; @@ -315,7 +315,7 @@ int BPF_PROG(module_load, struct module *mod) size = read_kernel_str_or_empty_str(field->data, PATH_MAX, mod->srcversion); ebpf_vl_field__set_size(&event->vl_fields, field, size); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); out: return 0; @@ -448,7 +448,7 @@ int tracepoint_syscalls_sys_enter_memfd_create(struct trace_event_raw_sys_enter goto out; ebpf_vl_field__set_size(&event->vl_fields, field, size); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); out: return 0; @@ -562,7 +562,7 @@ static int output_tty_event(struct ebpf_tty_dev *slave, const void *base, size_t } ebpf_vl_field__set_size(&event->vl_fields, field, len_cap); - bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); + ebpf_ringbuf_write(&ringbuf, event, EVENT_SIZE(event), 0); out: return ret; } diff --git a/GPL/Events/Varlen.h b/GPL/Events/Varlen.h index 56dddeac..f30de8bc 100644 --- a/GPL/Events/Varlen.h +++ b/GPL/Events/Varlen.h @@ -17,7 +17,7 @@ // We can't use the ringbuf reserve/commit API if we want to output an event // with variable length fields as we won't know the event size in advance, so // we create events on the event_buffer_map if this is the case and output them -// with bpf_ringbuf_output. +// with ebpf_ringbuf_write. // // If the event has no variable length parameters (i.e. is always a fixed // size). bpf_ringbuf_reserve/bpf_ringbuf_submit should be used instead to diff --git a/non-GPL/Events/EventsTrace/EventsTrace.c b/non-GPL/Events/EventsTrace/EventsTrace.c index 393302a7..9ff12d05 100644 --- a/non-GPL/Events/EventsTrace/EventsTrace.c +++ b/non-GPL/Events/EventsTrace/EventsTrace.c @@ -35,7 +35,7 @@ const char argp_program_doc[] = "[--process-setgid] [--process-tty-write] [--process-memfd_create] [--process-shmget] " "[--process-ptrace] [--process-load_module]\n" "[--net-conn-accept] [--net-conn-attempt] [--net-conn-closed]\n" - "[--print-features-on-init] [--unbuffer-stdout] [--libbpf-verbose]\n"; + "[--print-features-on-init] [--stats|-s] [--unbuffer-stdout] [--libbpf-verbose]\n"; // Somewhat kludgy way of ensuring argp doesn't print the EBPF_* constants that // happen to be valid ASCII values as short options. We pass these enum values @@ -120,6 +120,7 @@ static const struct argp_option opts[] = { "Print network connection closed events", 0}, {"print-features-on-init", 'i', NULL, false, "Print a message with feature information when probes have been successfully loaded", 1}, + {"stats", 's', NULL, false, "Print event statistics", 0}, {"unbuffer-stdout", 'u', NULL, false, "Disable userspace stdout buffering", 2}, {"libbpf-verbose", 'v', NULL, false, "Log verbose libbpf logs to stderr", 2}, {}, @@ -132,6 +133,7 @@ bool g_print_features_init = 0; bool g_features_printed = 0; bool g_unbuffer_stdout = 0; bool g_libbpf_verbose = 0; +bool g_stats = 0; static error_t parse_arg(int key, char *arg, struct argp_state *state) { @@ -148,6 +150,9 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) case 'a': g_events_env = UINT64_MAX; break; + case 's': + g_stats = 1; + break; case FILE_DELETE: case FILE_CREATE: case FILE_RENAME: @@ -1179,6 +1184,14 @@ int main(int argc, char **argv) fprintf(stderr, "Failed to poll event context %d: %s\n", err, strerror(-err)); break; } + if (g_stats) { + struct ebpf_event_stats ees; + + if (ebpf_event_ctx__read_stats(ctx, &ees) == 0) + printf("sent %lu lost %lu\n", ees.sent, ees.lost); + else + fprintf(stderr, "Failed to read stats: %s\n", strerror(errno)); + } } ebpf_event_ctx__destroy(&ctx); diff --git a/non-GPL/Events/Lib/EbpfEvents.c b/non-GPL/Events/Lib/EbpfEvents.c index 4b5e7607..332a48a1 100644 --- a/non-GPL/Events/Lib/EbpfEvents.c +++ b/non-GPL/Events/Lib/EbpfEvents.c @@ -797,6 +797,27 @@ int ebpf_event_ctx__poll(struct ebpf_event_ctx *ctx, int timeout) return ring_buffer__poll(ctx->ringbuf, timeout); } +int ebpf_event_ctx__read_stats(struct ebpf_event_ctx *ctx, struct ebpf_event_stats *ees) +{ + struct ebpf_event_stats pcpu_ees[libbpf_num_possible_cpus()]; + uint32_t zero = 0; + int i; + + if (!ctx || !ees) + return -1; + if (bpf_map__lookup_elem(ctx->probe->maps.ringbuf_stats, &zero, sizeof(zero), pcpu_ees, + sizeof(pcpu_ees), 0) != 0) + return -1; + + memset(ees, 0, sizeof(*ees)); + for (i = 0; i < libbpf_num_possible_cpus(); i++) { + ees->lost += pcpu_ees[i].lost; + ees->sent += pcpu_ees[i].sent; + } + + return 0; +} + int ebpf_event_ctx__consume(struct ebpf_event_ctx *ctx) { if (!ctx) diff --git a/non-GPL/Events/Lib/EbpfEvents.h b/non-GPL/Events/Lib/EbpfEvents.h index 71a120bf..d2176333 100644 --- a/non-GPL/Events/Lib/EbpfEvents.h +++ b/non-GPL/Events/Lib/EbpfEvents.h @@ -54,6 +54,12 @@ int ebpf_event_ctx__next(struct ebpf_event_ctx *ctx, int timeout); */ int ebpf_event_ctx__poll(struct ebpf_event_ctx *ctx, int timeout); +/* Read event statistics into ees. + * + * returns 0 on success or less than 0 on failure. + */ +int ebpf_event_ctx__read_stats(struct ebpf_event_ctx *ctx, struct ebpf_event_stats *ees); + /* Consumes as many events as possible from the event context and returns the * number consumed. Does not poll. This is good if you are polling outside * this library. From 2550cdd9431444e19887f0c8437f32a5c56ca140 Mon Sep 17 00:00:00 2001 From: Christiano Haesbaert Date: Tue, 2 Jul 2024 09:50:17 +0200 Subject: [PATCH 2/2] The formatter makes this look like crap, so add needless braces --- non-GPL/Events/Lib/EbpfEvents.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/non-GPL/Events/Lib/EbpfEvents.c b/non-GPL/Events/Lib/EbpfEvents.c index 332a48a1..ebd1fe4b 100644 --- a/non-GPL/Events/Lib/EbpfEvents.c +++ b/non-GPL/Events/Lib/EbpfEvents.c @@ -806,8 +806,9 @@ int ebpf_event_ctx__read_stats(struct ebpf_event_ctx *ctx, struct ebpf_event_sta if (!ctx || !ees) return -1; if (bpf_map__lookup_elem(ctx->probe->maps.ringbuf_stats, &zero, sizeof(zero), pcpu_ees, - sizeof(pcpu_ees), 0) != 0) + sizeof(pcpu_ees), 0) != 0) { return -1; + } memset(ees, 0, sizeof(*ees)); for (i = 0; i < libbpf_num_possible_cpus(); i++) {