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