From 81efa39f35df392b3fbb3ad43dfeab680b5956eb Mon Sep 17 00:00:00 2001 From: Jorge Niedbalski Date: Fri, 20 Sep 2024 17:39:09 +0200 Subject: [PATCH] in_ebpf: initial version of the plugin This is an initial proposal of a POC of an ebpf ingestor plugin. This adds capabilities to load and attach to an existing ebpf program and consume events from a fixed-sized ring buffer, subsequently those events are ingested in the log ingestion buffer. Events types are known and defined in the fluent-bit codebase and those has to be implemented by the ebpf program to follow when submitted into the ring buffer, this in the future must be serialized and be an extensible part of the project as we possibly make progress towards compability with other ebpf collectors. Also, i've implemented a fallback to allow strings to be passed as the payload of the event, without following a specific event type. Signed-off-by: Jorge Niedbalski --- .github/workflows/unit-tests.yaml | 6 +- CMakeLists.txt | 30 +++ cmake/plugins_options.cmake | 1 + cmake/windows-setup.cmake | 1 + dockerfiles/Dockerfile.centos7 | 1 + plugins/CMakeLists.txt | 1 + plugins/in_ebpf/CMakeLists.txt | 6 + plugins/in_ebpf/in_ebpf.c | 397 ++++++++++++++++++++++++++++++ plugins/in_ebpf/in_ebpf.h | 92 +++++++ tests/runtime/CMakeLists.txt | 59 +++-- tests/runtime/in_ebpf.c | 245 ++++++++++++++++++ 11 files changed, 815 insertions(+), 24 deletions(-) create mode 100644 plugins/in_ebpf/CMakeLists.txt create mode 100644 plugins/in_ebpf/in_ebpf.c create mode 100644 plugins/in_ebpf/in_ebpf.h create mode 100644 tests/runtime/in_ebpf.c diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 0fa50f11890..55c3fae3183 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -54,7 +54,7 @@ jobs: - name: Setup environment run: | sudo apt-get update - sudo apt-get install -y gcc-7 g++-7 clang-6.0 libsystemd-dev gcovr libyaml-dev + sudo apt-get install -y gcc-7 g++-7 clang-6.0 libsystemd-dev gcovr libyaml-dev libbpf-dev sudo ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer || true - uses: actions/checkout@v4 @@ -140,7 +140,7 @@ jobs: - name: Setup environment run: | sudo apt-get update - sudo apt-get install -y gcc-9 g++-9 clang-12 cmake flex bison libsystemd-dev gcovr libyaml-dev + sudo apt-get install -y gcc-9 g++-9 clang-12 cmake flex bison libsystemd-dev gcovr libyaml-dev libbpf-dev sudo ln -s /usr/bin/llvm-symbolizer-12 /usr/bin/llvm-symbolizer || true - name: Build and test with actuated runners @@ -193,7 +193,7 @@ jobs: --volume "/etc/machine-id:/etc/machine-id" install: | apt-get update - apt-get install -y gcc-7 g++-7 clang-6.0 libyaml-dev cmake flex bison libssl-dev #libsystemd-dev + apt-get install -y gcc-7 g++-7 clang-6.0 libyaml-dev cmake flex bison libssl-dev libbpf-dev #libsystemd-dev ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer || true update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e42d4faffc..e53def56eb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1032,6 +1032,36 @@ else() set(FLB_ARROW OFF) endif() +# EBPF Support +# ============ +if (FLB_IN_EBPF) + find_package(PkgConfig) + + # Check for libbpf with pkg-config + pkg_check_modules(LIBBPF libbpf>=0.5.0) + + if (LIBBPF_FOUND) + message(STATUS "libbpf found: ${LIBBPF_LIBRARIES}") + include_directories(${LIBBPF_INCLUDE_DIRS}) + list(APPEND EXTRA_LIBS ${LIBBPF_LIBRARIES}) + else() + # Manually find the library if pkg-config fails + find_library(LIBBPF_LIBRARY NAMES bpf REQUIRED) + if (LIBBPF_LIBRARY) + message(STATUS "Found libbpf: ${LIBBPF_LIBRARY}") + list(APPEND EXTRA_LIBS ${LIBBPF_LIBRARY}) + else() + if (FLB_SYSTEM_LINUX) + message(FATAL_ERROR "libbpf is required on Linux. Please install libbpf or ensure it is in your library path.") + else() + message(STATUS "libbpf is not found. Disabling eBPF support.") + set(FLB_IN_EBPF OFF) + endif() + endif() + endif() + +endif() + # Pthread Local Storage # ===================== # By default we expect the compiler already support thread local storage diff --git a/cmake/plugins_options.cmake b/cmake/plugins_options.cmake index 5a63bbc7d68..7d8462de465 100644 --- a/cmake/plugins_options.cmake +++ b/cmake/plugins_options.cmake @@ -61,6 +61,7 @@ DEFINE_OPTION(FLB_IN_WINLOG "Enable Windows Log input plugin" DEFINE_OPTION(FLB_IN_WINDOWS_EXPORTER_METRICS "Enable windows exporter metrics input plugin" ON) DEFINE_OPTION(FLB_IN_WINEVTLOG "Enable Windows EvtLog input plugin" OFF) DEFINE_OPTION(FLB_IN_WINSTAT "Enable Windows Stat input plugin" OFF) +DEFINE_OPTION(FLB_IN_EBPF "Enable Linux eBPF input plugin" OFF) # Processors # ========== diff --git a/cmake/windows-setup.cmake b/cmake/windows-setup.cmake index b947d6cbfa0..60230408b2e 100644 --- a/cmake/windows-setup.cmake +++ b/cmake/windows-setup.cmake @@ -53,6 +53,7 @@ if(FLB_WINDOWS_DEFAULTS) set(FLB_IN_STORAGE_BACKLOG Yes) set(FLB_IN_EMITTER Yes) set(FLB_IN_PODMAN_METRICS No) + set(FLB_IN_EBPF No) set(FLB_IN_ELASTICSEARCH Yes) set(FLB_IN_SPLUNK Yes) set(FLB_IN_PROMETHEUS_REMOTE_WRITE Yes) diff --git a/dockerfiles/Dockerfile.centos7 b/dockerfiles/Dockerfile.centos7 index 9ff9ee88ad9..d9230f75bc0 100644 --- a/dockerfiles/Dockerfile.centos7 +++ b/dockerfiles/Dockerfile.centos7 @@ -24,6 +24,7 @@ RUN cmake3 -DCMAKE_INSTALL_PREFIX=/opt/fluent-bit/ -DCMAKE_INSTALL_SYSCONFDIR=/e -DFLB_OUT_KAFKA=On \ -DFLB_JEMALLOC=On \ -DFLB_CHUNK_TRACE=On \ + -DFLB_IN_EBPF=Off \ -DFLB_OUT_PGSQL=On ../ RUN make -j "$(getconf _NPROCESSORS_ONLN)" diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index ce8cae64d97..ae9bac57af8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -207,6 +207,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") REGISTER_IN_PLUGIN("in_docker_events") REGISTER_IN_PLUGIN("in_podman_metrics") REGISTER_IN_PLUGIN("in_process_exporter_metrics") + REGISTER_IN_PLUGIN("in_ebpf") endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") diff --git a/plugins/in_ebpf/CMakeLists.txt b/plugins/in_ebpf/CMakeLists.txt new file mode 100644 index 00000000000..fc2a7a4a7c1 --- /dev/null +++ b/plugins/in_ebpf/CMakeLists.txt @@ -0,0 +1,6 @@ +set(src + in_ebpf.c +) + +FLB_PLUGIN(in_ebpf "${src}" "") +target_link_libraries(flb-plugin-in_ebpf -lbpf -lelf -lz) \ No newline at end of file diff --git a/plugins/in_ebpf/in_ebpf.c b/plugins/in_ebpf/in_ebpf.c new file mode 100644 index 00000000000..141dcb37e4d --- /dev/null +++ b/plugins/in_ebpf/in_ebpf.c @@ -0,0 +1,397 @@ +/* Fluent Bit + * ========== + * Copyright (C) 2015-2024 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "in_ebpf.h" + +/* Encodes log event */ +int encode_log_event(struct flb_input_instance *ins, + struct flb_log_event_encoder *log_encoder, + const char *event_type_str, + __u32 pid, + const char *data, size_t data_len) +{ + int ret; + + flb_plg_trace(ins, "encoding log event"); + ret = flb_log_event_encoder_begin_record(log_encoder); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_plg_error(ins, "failed to begin log event record"); + return -1; + } + + flb_plg_trace(ins, "setting current timestamp for log event"); + ret = flb_log_event_encoder_set_current_timestamp(log_encoder); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_log_event_encoder_rollback_record(log_encoder); + flb_plg_error(ins, "failed to set timestamp"); + return -1; + } + + if (pid > 0) { + flb_plg_trace(ins, "appending pid: %u", pid); + ret = flb_log_event_encoder_append_body_cstring(log_encoder, "pid"); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_log_event_encoder_rollback_record(log_encoder); + flb_plg_error(ins, "failed to append pid key"); + return -1; + } + ret = flb_log_event_encoder_append_body_uint32(log_encoder, pid); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_log_event_encoder_rollback_record(log_encoder); + flb_plg_error(ins, "failed to append pid value"); + return -1; + } + } + + if (event_type_str) { + flb_plg_trace(ins, "appending event type: %s", event_type_str); + ret = flb_log_event_encoder_append_body_cstring(log_encoder, "event_type"); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_log_event_encoder_rollback_record(log_encoder); + flb_plg_error(ins, "failed to append event type key"); + return -1; + } + ret = flb_log_event_encoder_append_body_string(log_encoder, event_type_str, strlen(event_type_str)); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_log_event_encoder_rollback_record(log_encoder); + flb_plg_error(ins, "failed to append event type value"); + return -1; + } + } + + if (data_len > 0) { + flb_plg_trace(ins, "appending event data of length: %zu", data_len); + ret = flb_log_event_encoder_append_body_cstring(log_encoder, "event_data"); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_log_event_encoder_rollback_record(log_encoder); + flb_plg_error(ins, "failed to append event data key"); + return -1; + } + ret = flb_log_event_encoder_append_body_string(log_encoder, data, data_len); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_log_event_encoder_rollback_record(log_encoder); + flb_plg_error(ins, "failed to append event data value"); + return -1; + } + } else { + flb_plg_trace(ins, "no event data to append (data_len = 0)"); + } + + /* Commit the record */ + ret = flb_log_event_encoder_commit_record(log_encoder); + if (ret != FLB_EVENT_ENCODER_SUCCESS) { + flb_plg_error(ins, "failed to commit log event record"); + return -1; + } + + return 0; +} + +/* Handles the event data */ +int handle_ebpf_event(void *instance, void *data, size_t data_sz) +{ + struct flb_input_instance *ins = (struct flb_input_instance *)instance; + struct flb_in_ebpf_config *ctx = (struct flb_in_ebpf_config *)ins->context; + struct flb_log_event_encoder *log_encoder = ctx->log_encoder; + const char *event_type_str; + __u32 pid; + char *event_data; + size_t event_data_len; + int ret; + + ret = extract_event_data(data, data_sz, &event_type_str, &pid, &event_data, &event_data_len); + if (ret != 0) { + flb_plg_warn(ins, "invalid event data received"); + return ret; + } + + /* Encode the log event */ + ret = encode_log_event(ins, log_encoder, event_type_str, pid, event_data, event_data_len); + if (ret != 0) { + flb_plg_error(ins, "failed to encode log event"); + return ret; + } + + /* Append the encoded log event to Fluent Bit */ + if (log_encoder->output_length > 0) { + flb_plg_trace(ins, "appending log event of length: %zu", log_encoder->output_length); + ret = flb_input_log_append(ins, NULL, 0, + log_encoder->output_buffer, + log_encoder->output_length); + if (ret == -1) { + flb_plg_error(ins, "failed to append log data"); + return -1; + } + flb_log_event_encoder_reset(log_encoder); + } + + return 0; +} + +/* Extracts event data from input */ +int extract_event_data(void *data, size_t data_sz, const char **event_type_str, + __u32 *pid, char **event_data, size_t *event_data_len) +{ + if (data_sz == sizeof(struct flb_in_ebpf_event)) { + struct flb_in_ebpf_event *event = (struct flb_in_ebpf_event *)data; + *event_type_str = get_event_type_str(event->event_type); + *pid = event->pid; + *event_data = event->data; + *event_data_len = strlen(event->data); + } else if (data_sz <= MAX_EVENT_LEN) { + *event_type_str = FLB_IN_EBPF_EVENT_TYPE_UNKNOWN; + *pid = 0; + *event_data = (char *)data; + *event_data_len = strlen(*event_data); + } else { + return -1; + } + + return 0; +} + +/* Collect function for reading the ring buffer */ +static int in_ebpf_collect(struct flb_input_instance *ins, + struct flb_config *config, void *in_context) +{ + struct flb_in_ebpf_config *ctx = (struct flb_in_ebpf_config *)in_context; + int err; + + flb_plg_trace(ins, "polling on ring buffer '%s'", ctx->ringbuf_map_name); + + err = ring_buffer__consume(ctx->rb); + if (err < 0) { + flb_plg_error(ins, "error polling the ring buffer: %d", err); + return -1; + } + + return 0; +} + +/* Pause function */ +static void in_ebpf_pause(void *data, struct flb_config *config) +{ + struct flb_in_ebpf_config *ctx = (struct flb_in_ebpf_config *)data; + + flb_input_collector_pause(ctx->coll_fd, ctx->ins); +} + +/* Resume function */ +static void in_ebpf_resume(void *data, struct flb_config *config) +{ + struct flb_in_ebpf_config *ctx = (struct flb_in_ebpf_config *)data; + + flb_input_collector_resume(ctx->coll_fd, ctx->ins); +} + +/* Cleanup function */ +static int in_ebpf_exit(void *in_context, struct flb_config *config) +{ + struct flb_in_ebpf_config *ctx = (struct flb_in_ebpf_config *)in_context; + + if (ctx->rb) { + ring_buffer__free(ctx->rb); + } + + if (ctx->obj) { + bpf_object__close(ctx->obj); + } + + if (ctx->log_encoder) { + flb_log_event_encoder_destroy(ctx->log_encoder); + } + + flb_free(ctx); + return 0; +} + +/* Initialization function */ +static int in_ebpf_init(struct flb_input_instance *ins, + struct flb_config *config, void *data) +{ + struct flb_in_ebpf_config *ctx; + const char *bpf_obj_file; + const char *bpf_prog_name; + struct bpf_map *map; + struct bpf_program *prog; + struct bpf_link *link; + int err; + int ret; + int poll_seconds; + int poll_nanoseconds; + + ctx = flb_calloc(1, sizeof(struct flb_in_ebpf_config)); + if (!ctx) { + flb_plg_error(ins, "could not allocate memory for the context"); + return -1; + } + + ctx->ins = ins; + ctx->log_encoder = flb_log_event_encoder_create(FLB_LOG_EVENT_FORMAT_DEFAULT); + if (!ctx->log_encoder) { + flb_plg_error(ins, "could not create log event encoder"); + flb_free(ctx); + return -1; + } + + flb_input_set_context(ins, ctx); + ret = flb_input_config_map_set(ins, (void *)ctx); + if (ret == -1) { + flb_plg_error(ins, "failed to load config map"); + flb_free(ctx); + return -1; + } + + bpf_obj_file = ctx->bpf_object_file; + if (!bpf_obj_file) { + flb_plg_error(ins, "no eBPF object file specified"); + flb_free(ctx); + return -1; + } + + bpf_prog_name = ctx->bpf_program_name; + if (!bpf_prog_name) { + flb_plg_error(ins, "no eBPF program name specified"); + flb_free(ctx); + return -1; + } + + ctx->obj = bpf_object__open_file(bpf_obj_file, NULL); + if (!ctx->obj) { + flb_plg_error(ins, "failed to open eBPF object file: %s", bpf_obj_file); + flb_free(ctx); + return -1; + } + + err = bpf_object__load(ctx->obj); + if (err) { + flb_plg_error(ins, "failed to load eBPF object: %d", err); + bpf_object__close(ctx->obj); + flb_free(ctx); + return -1; + } + + prog = bpf_object__find_program_by_name(ctx->obj, bpf_prog_name); + if (!prog) { + flb_plg_error(ins, "failed to find eBPF program: %s", bpf_prog_name); + bpf_object__close(ctx->obj); + flb_free(ctx); + return -1; + } + + link = bpf_program__attach(prog); + if (!link) { + flb_plg_error(ins, "failed to attach eBPF program"); + bpf_object__close(ctx->obj); + flb_free(ctx); + return -1; + } + + map = bpf_object__find_map_by_name(ctx->obj, ctx->ringbuf_map_name); + if (!map) { + flb_plg_error(ins, "failed to find the '%s' map in eBPF object", ctx->ringbuf_map_name); + bpf_link__destroy(link); + bpf_object__close(ctx->obj); + flb_free(ctx); + return -1; + } + + ctx->map_fd = bpf_map__fd(map); + if (ctx->map_fd < 0) { + flb_plg_error(ins, "failed to get file descriptor for '%s' map", ctx->ringbuf_map_name); + bpf_link__destroy(link); + bpf_object__close(ctx->obj); + flb_free(ctx); + return -1; + } + + ctx->rb = ring_buffer__new(ctx->map_fd, handle_ebpf_event, ins, NULL); + if (!ctx->rb) { + flb_plg_error(ins, "failed to create ring buffer"); + bpf_link__destroy(link); + bpf_object__close(ctx->obj); + flb_free(ctx); + return -1; + } + + poll_seconds = ctx->poll_ms / 1000; + poll_nanoseconds = (ctx->poll_ms % 1000) * 1000000; + + ctx->coll_fd = flb_input_set_collector_time(ins, in_ebpf_collect, + poll_seconds, poll_nanoseconds, + config); + if (ctx->coll_fd < 0) { + flb_plg_error(ins, "failed to set up collector"); + ring_buffer__free(ctx->rb); + bpf_link__destroy(link); + bpf_object__close(ctx->obj); + flb_free(ctx); + return -1; + } + + flb_plg_info(ins, "eBPF program '%s' loaded successfully from object file '%s' with ring buffer '%s'", + bpf_prog_name, bpf_obj_file, ctx->ringbuf_map_name); + + return 0; +} + +/* Configuration map for the plugin */ +static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_STR, "bpf_object_file", NULL, + 0, FLB_TRUE, offsetof(struct flb_in_ebpf_config, bpf_object_file), + "path to the eBPF program object file." + }, + { + FLB_CONFIG_MAP_STR, "bpf_program_name", NULL, + 0, FLB_TRUE, offsetof(struct flb_in_ebpf_config, bpf_program_name), + "name of the eBPF program to attach." + }, + { + FLB_CONFIG_MAP_STR, "ringbuf_map_name", FLB_IN_EBPF_DEFAULT_RINGBUF_MAP_NAME, + 0, FLB_TRUE, offsetof(struct flb_in_ebpf_config, ringbuf_map_name), + "name of the ring buffer map in the eBPF program." + }, + { + FLB_CONFIG_MAP_INT, "poll_ms", FLB_IN_EBPF_DEFAULT_POLL_MS, + 0, FLB_TRUE, offsetof(struct flb_in_ebpf_config, poll_ms), + "poll timeout in milliseconds (-1 for infinite)." + }, + {0} +}; + +/* Plugin registration */ +struct flb_input_plugin in_ebpf_plugin = { + .name = "ebpf", + .description = "eBPF input plugin", + .cb_init = in_ebpf_init, + .cb_pre_run = NULL, + .cb_collect = in_ebpf_collect, + .cb_flush_buf = NULL, + .cb_pause = in_ebpf_pause, + .cb_resume = in_ebpf_resume, + .cb_exit = in_ebpf_exit, + .config_map = config_map, +}; diff --git a/plugins/in_ebpf/in_ebpf.h b/plugins/in_ebpf/in_ebpf.h new file mode 100644 index 00000000000..68daf6273a2 --- /dev/null +++ b/plugins/in_ebpf/in_ebpf.h @@ -0,0 +1,92 @@ +/* Fluent Bit + * ========== + * Copyright (C) 2015-2024 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FLB_IN_EBPF_H +#define FLB_IN_EBPF_H + +#include +#include +#include +#include + +/* Define default values */ +#define FLB_IN_EBPF_DEFAULT_RINGBUF_MAP_NAME "events" +#define FLB_IN_EBPF_DEFAULT_POLL_MS "1000" // 1 second default poll timeout +#define FLB_IN_EBPF_DEFAULT_ATTRIBUTE_NAME "payload" +#define FLB_IN_EBPF_DEFAULT_RINGBUF_SIZE "8192" // Default ring buffer size in bytes + +#define MAX_EVENT_LEN 128 + +/* Configuration structure for eBPF plugin */ +struct flb_in_ebpf_config { + struct ring_buffer *rb; + struct bpf_object *obj; + struct flb_log_event_encoder *log_encoder; // Log encoder + int map_fd; + size_t ringbuf_size; + size_t ringbuf_consume_count; /* events to consume from ring buffer on each poll */ + char *ringbuf_map_name; + int poll_ms; /* Poll timeout in milliseconds */ + const char *bpf_object_file; /* Path to the eBPF object file */ + const char *bpf_program_name; /* Name of the eBPF program to attach */ + char *attribute_name; /* Configurable attribute name */ + int coll_fd; /* Collector file descriptor */ + struct flb_input_instance *ins; /* Pointer to the input instance */ +}; + +/* Event types enum in UPPERCASE */ +enum FLB_IN_EBPF_EVENT_TYPE { + FLB_IN_EBPF_EVENT_FILESYSTEM = 0, + FLB_IN_EBPF_EVENT_NETWORK = 1, + FLB_IN_EBPF_EVENT_PROCESS = 2 +}; + +/* Event structure sent by eBPF */ +struct flb_in_ebpf_event { + __u32 pid; + __u32 event_type; // Event type as an enum + char data[MAX_EVENT_LEN]; // Event-specific data (filename, network info, etc.) +}; + +/* Define constant strings for event types */ +#define FLB_IN_EBPF_EVENT_TYPE_FILESYSTEM "filesystem" +#define FLB_IN_EBPF_EVENT_TYPE_NETWORK "network" +#define FLB_IN_EBPF_EVENT_TYPE_PROCESS "process" +#define FLB_IN_EBPF_EVENT_TYPE_UNKNOWN "unknown" + +/* Function to map enum values to strings */ +static inline const char *get_event_type_str(int event_type) { + switch (event_type) { + case FLB_IN_EBPF_EVENT_FILESYSTEM: + return FLB_IN_EBPF_EVENT_TYPE_FILESYSTEM; + case FLB_IN_EBPF_EVENT_NETWORK: + return FLB_IN_EBPF_EVENT_TYPE_NETWORK; + case FLB_IN_EBPF_EVENT_PROCESS: + return FLB_IN_EBPF_EVENT_TYPE_PROCESS; + default: + return FLB_IN_EBPF_EVENT_TYPE_UNKNOWN; + } +} + +int handle_ebpf_event(void *instance, void *data, size_t data_sz); +int encode_log_event(struct flb_input_instance *ins, + struct flb_log_event_encoder *log_encoder, + const char *event_type_str, + __u32 pid, + const char *data, size_t data_len); +int extract_event_data(void *data, size_t data_sz, const char **event_type_str, + __u32 *pid, char **event_data, size_t *event_data_len); +#endif /* FLB_IN_EBPF_H */ diff --git a/tests/runtime/CMakeLists.txt b/tests/runtime/CMakeLists.txt index e902f7892ff..0fa2e194ac6 100644 --- a/tests/runtime/CMakeLists.txt +++ b/tests/runtime/CMakeLists.txt @@ -32,7 +32,7 @@ FLB_RT_TEST(FLB_CHUNK_TRACE "core_chunk_trace.c") FLB_RT_TEST(FLB_IN_EVENT_TEST "in_event_test.c") if(FLB_OUT_LIB) - # These plugins works only on Linux + # These plugins work only on Linux if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") FLB_RT_TEST(FLB_IN_CPU "in_cpu.c") FLB_RT_TEST(FLB_IN_DISK "in_disk.c") @@ -60,6 +60,25 @@ if(FLB_OUT_LIB) FLB_RT_TEST(FLB_IN_KUBERNETES_EVENTS "in_kubernetes_events.c") endif() +# Add executable for in_ebpf test and link necessary sources +if (FLB_IN_EBPF) + add_executable( + flb-rt-in_ebpf + in_ebpf.c + ../../plugins/in_ebpf/in_ebpf.c # Ensure this is linked with the executable + ) + target_link_libraries(flb-rt-in_ebpf + fluent-bit-static + ${CMAKE_THREAD_LIBS_INIT} + ${SYSTEMD_LIB} + -lbpf + ) + add_test(NAME flb-rt-in_ebpf + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/flb-rt-in_ebpf + WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY}/build) + set_tests_properties(flb-rt-in_ebpf PROPERTIES LABELS "runtime") +endif() + # Filter Plugins if(FLB_IN_LIB AND FLB_OUT_LIB) FLB_RT_TEST(FLB_FILTER_AWS "filter_aws.c") @@ -69,24 +88,23 @@ if(FLB_IN_LIB AND FLB_OUT_LIB) FLB_RT_TEST(FLB_FILTER_GREP "filter_grep.c") FLB_RT_TEST(FLB_FILTER_THROTTLE "filter_throttle.c") FLB_RT_TEST(FLB_FILTER_THROTTLE_SIZE "filter_throttle_size.c") - FLB_RT_TEST(FLB_FILTER_NEST "filter_nest.c") - FLB_RT_TEST(FLB_FILTER_REWRITE_TAG "filter_rewrite_tag.c") - FLB_RT_TEST(FLB_FILTER_KUBERNETES "filter_kubernetes.c") - FLB_RT_TEST(FLB_FILTER_PARSER "filter_parser.c") - FLB_RT_TEST(FLB_FILTER_MODIFY "filter_modify.c") - FLB_RT_TEST(FLB_FILTER_LUA "filter_lua.c") - FLB_RT_TEST(FLB_FILTER_TYPE_CONVERTER "filter_type_converter.c") + FLB_RT_TEST(FLB_FILTER_NEST "filter_nest.c") + FLB_RT_TEST(FLB_FILTER_REWRITE_TAG "filter_rewrite_tag.c") + FLB_RT_TEST(FLB_FILTER_KUBERNETES "filter_kubernetes.c") + FLB_RT_TEST(FLB_FILTER_PARSER "filter_parser.c") + FLB_RT_TEST(FLB_FILTER_MODIFY "filter_modify.c") + FLB_RT_TEST(FLB_FILTER_LUA "filter_lua.c") + FLB_RT_TEST(FLB_FILTER_TYPE_CONVERTER "filter_type_converter.c") FLB_RT_TEST(FLB_FILTER_RECORD_MODIFIER "filter_record_modifier.c") FLB_RT_TEST(FLB_FILTER_MULTILINE "filter_multiline.c") FLB_RT_TEST(FLB_FILTER_SYSINFO "filter_sysinfo.c") if (FLB_FILTER_WASM) - FLB_RT_TEST(FLB_FILTER_WASM "filter_wasm.c") + FLB_RT_TEST(FLB_FILTER_WASM "filter_wasm.c") endif () FLB_RT_TEST(FLB_FILTER_ECS "filter_ecs.c") - FLB_RT_TEST(FLB_FILTER_LOG_TO_METRICS "filter_log_to_metrics.c") + FLB_RT_TEST(FLB_FILTER_LOG_TO_METRICS "filter_log_to_metrics.c") endif() - # Output Plugins if(FLB_IN_LIB) FLB_RT_TEST(FLB_OUT_LIB "core_engine.c") @@ -118,14 +136,13 @@ if(FLB_IN_LIB) FLB_RT_TEST(FLB_OUT_CLOUDWATCH_LOGS "out_cloudwatch.c") FLB_RT_TEST(FLB_OUT_KINESIS_FIREHOSE "out_firehose.c") FLB_RT_TEST(FLB_OUT_KINESIS_STREAMS "out_kinesis.c") - # These plugins work only on Linux + # These plugins work only on Linux if(NOT FLB_SYSTEM_WINDOWS) FLB_RT_TEST(FLB_OUT_FILE "out_file.c") endif() FLB_RT_TEST(FLB_OUT_S3 "out_s3.c") FLB_RT_TEST(FLB_OUT_TD "out_td.c") FLB_RT_TEST(FLB_OUT_INFLUXDB "out_influxdb.c") - endif() if (FLB_CUSTOM_CALYPTIA) @@ -154,7 +171,7 @@ set(FLB_TESTS_DATA_PATH ${CMAKE_CURRENT_SOURCE_DIR}) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/flb_tests_runtime.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/flb_tests_runtime.h" - ) +) foreach(source_file ${CHECK_PROGRAMS}) get_filename_component(o_source_file_we ${source_file} NAME_WE) @@ -165,19 +182,19 @@ foreach(source_file ${CHECK_PROGRAMS}) add_executable( ${source_file_we} ${source_file} - ) + ) add_sanitizers(${source_file_we}) target_link_libraries(${source_file_we} fluent-bit-static ${CMAKE_THREAD_LIBS_INIT} ${SYSTEMD_LIB} - ) - if(FLB_AVRO_ENCODER) - target_link_libraries(${source_file_we} avro-static jansson) - endif() + ) + if(FLB_AVRO_ENCODER) + target_link_libraries(${source_file_we} avro-static jansson) + endif() add_test(NAME ${source_file_we} - COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${source_file_we} - WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY}/build) + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${source_file_we} + WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY}/build) set_tests_properties(${source_file_we} PROPERTIES LABELS "runtime") set_property(TARGET ${source_file_we} APPEND_STRING PROPERTY COMPILE_FLAGS "-D${o_source_file_we}") endif() diff --git a/tests/runtime/in_ebpf.c b/tests/runtime/in_ebpf.c new file mode 100644 index 00000000000..f06a6ebc5da --- /dev/null +++ b/tests/runtime/in_ebpf.c @@ -0,0 +1,245 @@ +/* Fluent Bit + * ========== + * Copyright (C) 2015-2024 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include + +#include "../../plugins/in_ebpf/in_ebpf.h" +#include "flb_tests_runtime.h" + +/* Mock initialization for input instance */ +struct flb_input_instance *init_mock_instance(struct flb_in_ebpf_config *ctx) +{ + struct flb_input_instance *mock_instance; + + mock_instance = flb_calloc(1, sizeof(struct flb_input_instance)); + if (!mock_instance) { + printf("Failed to allocate memory for mock_instance\n"); + return NULL; + } + + mock_instance->context = ctx; + + ctx->log_encoder = flb_log_event_encoder_create(FLB_LOG_EVENT_FORMAT_DEFAULT); + if (!ctx->log_encoder) { + printf("Failed to create log event encoder\n"); + flb_free(mock_instance); + return NULL; + } + + return mock_instance; +} + +/* Cleanup function for the mock input instance */ +void cleanup_mock_instance(struct flb_input_instance *mock_instance) +{ + struct flb_in_ebpf_config *ctx; + + if (mock_instance) { + ctx = mock_instance->context; + if (ctx && ctx->log_encoder) { + flb_log_event_encoder_destroy(ctx->log_encoder); + } + flb_free(mock_instance); + } +} + +/* Test 1: Normal encoding with valid data */ +void test_encode_log_event_valid() +{ + int ret; + struct flb_in_ebpf_config ctx; + struct flb_input_instance *mock_instance = init_mock_instance(&ctx); + const char *event_type = "test_event"; + __u32 pid = 1234; + const char *data = "valid_data"; + size_t data_len = strlen(data); + + if (!mock_instance) { + return; /* Exit if initialization failed */ + } + + ret = encode_log_event(mock_instance, ctx.log_encoder, event_type, pid, data, data_len); + TEST_CHECK(ret == 0); + TEST_CHECK(ctx.log_encoder->output_length > 0); + printf("test_encode_log_event_valid passed\n"); + + cleanup_mock_instance(mock_instance); +} + +/* Test 2: Encoding with NULL event type */ +void test_encode_log_event_null_event_type() +{ + int ret; + struct flb_in_ebpf_config ctx; + struct flb_input_instance *mock_instance = init_mock_instance(&ctx); + __u32 pid = 1234; + const char *data = "valid_data"; + size_t data_len = strlen(data); + + if (!mock_instance) { + return; /* Exit if initialization failed */ + } + + ret = encode_log_event(mock_instance, ctx.log_encoder, NULL, pid, data, data_len); + TEST_CHECK(ret == 0); + printf("test_encode_log_event_null_event_type passed\n"); + + cleanup_mock_instance(mock_instance); +} + +/* Test 3: Encoding with zero-length data */ +void test_encode_log_event_zero_length_data() +{ + int ret; + struct flb_in_ebpf_config ctx; + struct flb_input_instance *mock_instance = init_mock_instance(&ctx); + const char *event_type = "test_event"; + __u32 pid = 1234; + const char *data = ""; + size_t data_len = 0; + + if (!mock_instance) { + return; /* Exit if initialization failed */ + } + + ret = encode_log_event(mock_instance, ctx.log_encoder, event_type, pid, data, data_len); + TEST_CHECK(ret == 0); + printf("test_encode_log_event_zero_length_data passed\n"); + + cleanup_mock_instance(mock_instance); +} + +/* Test 4: Extract event data with structured input */ +void test_extract_event_data_structured() +{ + int ret; + struct flb_in_ebpf_event event; + strncpy(event.data, "structured_event_data", sizeof(event.data) - 1); + event.event_type = FLB_IN_EBPF_EVENT_PROCESS; + event.pid = 5678; + + const char *event_type_str; + __u32 pid; + char *event_data; + size_t event_data_len; + + ret = extract_event_data(&event, sizeof(event), &event_type_str, &pid, &event_data, &event_data_len); + TEST_CHECK(ret == 0); + TEST_CHECK(strcmp(event_type_str, FLB_IN_EBPF_EVENT_TYPE_PROCESS) == 0); + TEST_CHECK(pid == 5678); + TEST_CHECK(strcmp(event_data, "structured_event_data") == 0); + printf("test_extract_event_data_structured passed\n"); +} + +/* Test 5: Extract event data with raw string input */ +void test_extract_event_data_raw() +{ + int ret; + const char *raw_data = "raw_event_data"; + size_t data_sz = strlen(raw_data) + 1; + + const char *event_type_str; + __u32 pid; + char *event_data; + size_t event_data_len; + + ret = extract_event_data((void *)raw_data, data_sz, &event_type_str, &pid, &event_data, &event_data_len); + TEST_CHECK(ret == 0); + TEST_CHECK(strcmp(event_type_str, FLB_IN_EBPF_EVENT_TYPE_UNKNOWN) == 0); + TEST_CHECK(pid == 0); + TEST_CHECK(strcmp(event_data, "raw_event_data") == 0); + printf("test_extract_event_data_raw passed\n"); +} + +/* Test 8: Extract event data with invalid size */ +void test_extract_event_data_invalid_size() +{ + int ret; + struct flb_in_ebpf_event event; + strncpy(event.data, "invalid_size_event", sizeof(event.data) - 1); + event.event_type = FLB_IN_EBPF_EVENT_PROCESS; + event.pid = 1234; + + const char *event_type_str; + __u32 pid; + char *event_data; + size_t event_data_len; + + ret = extract_event_data(&event, sizeof(event) - 1, &event_type_str, &pid, &event_data, &event_data_len); + TEST_CHECK(ret != 0); /* Expect failure */ + printf("test_extract_event_data_invalid_size passed\n"); +} + +/* Test 9: Encoding with NULL data */ +void test_encode_log_event_null_data() +{ + int ret; + struct flb_in_ebpf_config ctx; + struct flb_input_instance *mock_instance = init_mock_instance(&ctx); + const char *event_type = "test_event"; + __u32 pid = 1234; + const char *data = NULL; + size_t data_len = 0; + + if (!mock_instance) { + return; /* Exit if initialization failed */ + } + + ret = encode_log_event(mock_instance, ctx.log_encoder, event_type, pid, data, data_len); + TEST_CHECK(ret == 0); + printf("test_encode_log_event_null_data passed\n"); + + cleanup_mock_instance(mock_instance); +} + +/* Test 10: Encoding with extremely large data */ +void test_encode_log_event_large_data() +{ + int ret; + struct flb_in_ebpf_config ctx; + struct flb_input_instance *mock_instance = init_mock_instance(&ctx); + const char *event_type = "large_event"; + __u32 pid = 4321; + char large_data[10000]; + memset(large_data, 'A', sizeof(large_data)); + size_t data_len = sizeof(large_data); + + if (!mock_instance) { + return; /* Exit if initialization failed */ + } + + ret = encode_log_event(mock_instance, ctx.log_encoder, event_type, pid, large_data, data_len); + TEST_CHECK(ret == 0); + printf("test_encode_log_event_large_data passed\n"); + + cleanup_mock_instance(mock_instance); +} + +TEST_LIST = { + {"encode_log_event_valid", test_encode_log_event_valid}, + {"encode_log_event_null_event_type", test_encode_log_event_null_event_type}, + {"encode_log_event_zero_length_data", test_encode_log_event_zero_length_data}, + {"extract_event_data_structured", test_extract_event_data_structured}, + {"extract_event_data_raw", test_extract_event_data_raw}, + {"extract_event_data_invalid_size", test_extract_event_data_invalid_size}, + {"encode_log_event_null_data", test_encode_log_event_null_data}, + {"encode_log_event_large_data", test_encode_log_event_large_data}, + {NULL, NULL} +}; \ No newline at end of file