diff --git a/CMakeLists.txt b/CMakeLists.txt index b9b3c4e611..256d38022c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.15) project(collector) +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + enable_testing() add_subdirectory(collector) diff --git a/cmake/FindBpfObject.cmake b/cmake/FindBpfObject.cmake new file mode 100644 index 0000000000..362d23853b --- /dev/null +++ b/cmake/FindBpfObject.cmake @@ -0,0 +1,190 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#[=======================================================================[.rst: +FindBpfObject +-------- + +Find BpfObject + +This module finds if all the dependencies for eBPF Compile-Once-Run-Everywhere +programs are available and where all the components are located. + +The caller may set the following variables to disable automatic +search/processing for the associated component: + + ``BPFOBJECT_BPFTOOL_EXE`` + Path to ``bpftool`` binary + + ``BPFOBJECT_CLANG_EXE`` + Path to ``clang`` binary + + ``LIBBPF_INCLUDE_DIRS`` + Path to ``libbpf`` development headers + + ``LIBBPF_LIBRARIES`` + Path to `libbpf` library + + ``BPFOBJECT_VMLINUX_H`` + Path to ``vmlinux.h`` generated by ``bpftool``. If unset, this module will + attempt to automatically generate a copy. + +This module sets the following result variables: + +:: + + BpfObject_FOUND = TRUE if all components are found + + +This module also provides the ``bpf_object()`` macro. This macro generates a +cmake interface library for the BPF object's generated skeleton as well +as the associated dependencies. + +.. code-block:: cmake + + bpf_object( [
...]) + +Given an abstract ```` for a BPF object and the associated ```` +file, generates an interface library target, ``_skel``, that may be +linked against by other cmake targets. Additional headers may be provided to +the macro to ensure that the generated skeleton is up-to-date. + +Example Usage: + +:: + + find_package(BpfObject REQUIRED) + bpf_object(myobject myobject.bpf.c myobject.h) + add_executable(myapp myapp.c) + target_link_libraries(myapp myobject_skel) + +#]=======================================================================] + +if(NOT BPFOBJECT_BPFTOOL_EXE) + find_program(BPFOBJECT_BPFTOOL_EXE NAMES bpftool DOC "Path to bpftool executable") +endif() + +if(NOT BPFOBJECT_CLANG_EXE) + find_program(BPFOBJECT_CLANG_EXE NAMES clang DOC "Path to clang executable") + + execute_process(COMMAND ${BPFOBJECT_CLANG_EXE} --version + OUTPUT_VARIABLE CLANG_version_output + ERROR_VARIABLE CLANG_version_error + RESULT_VARIABLE CLANG_version_result + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # Check that clang is new enough + if(${CLANG_version_result} EQUAL 0) + if("${CLANG_version_output}" MATCHES "clang version ([^\n]+)\n") + # Transform X.Y.Z into X;Y;Z which can then be interpreted as a list + set(CLANG_VERSION "${CMAKE_MATCH_1}") + string(REPLACE "." ";" CLANG_VERSION_LIST ${CLANG_VERSION}) + list(GET CLANG_VERSION_LIST 0 CLANG_VERSION_MAJOR) + + # Anything older than clang 10 doesn't really work + string(COMPARE LESS ${CLANG_VERSION_MAJOR} 10 CLANG_VERSION_MAJOR_LT10) + if(${CLANG_VERSION_MAJOR_LT10}) + message(FATAL_ERROR "clang ${CLANG_VERSION} is too old for BPF CO-RE") + endif() + + message(STATUS "Found clang version: ${CLANG_VERSION}") + else() + message(FATAL_ERROR "Failed to parse clang version string: ${CLANG_version_output}") + endif() + else() + message(FATAL_ERROR "Command \"${BPFOBJECT_CLANG_EXE} --version\" failed with output:\n${CLANG_version_error}") + endif() +endif() + +if(NOT LIBBPF_INCLUDE_DIRS OR NOT LIBBPF_LIBRARIES) + find_package(LibBpf) +endif() + +if(BPFOBJECT_VMLINUX_H) + get_filename_component(GENERATED_VMLINUX_DIR ${BPFOBJECT_VMLINUX_H} DIRECTORY) +elseif(BPFOBJECT_BPFTOOL_EXE) + # Generate vmlinux.h + set(GENERATED_VMLINUX_DIR ${CMAKE_CURRENT_BINARY_DIR}) + set(BPFOBJECT_VMLINUX_H ${GENERATED_VMLINUX_DIR}/vmlinux.h) + execute_process(COMMAND ${BPFOBJECT_BPFTOOL_EXE} btf dump file /sys/kernel/btf/vmlinux format c + OUTPUT_FILE ${BPFOBJECT_VMLINUX_H} + ERROR_VARIABLE VMLINUX_error + RESULT_VARIABLE VMLINUX_result) + if(${VMLINUX_result} EQUAL 0) + set(VMLINUX ${BPFOBJECT_VMLINUX_H}) + else() + message(FATAL_ERROR "Failed to dump vmlinux.h from BTF: ${VMLINUX_error}") + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(BpfObject + REQUIRED_VARS + BPFOBJECT_BPFTOOL_EXE + BPFOBJECT_CLANG_EXE + LIBBPF_INCLUDE_DIRS + LIBBPF_LIBRARIES + GENERATED_VMLINUX_DIR) + +# Get clang bpf system includes +execute_process( + COMMAND bash -c "${BPFOBJECT_CLANG_EXE} -v -E - < /dev/null 2>&1 | + sed -n '/<...> search starts here:/,/End of search list./{ s| \\(/.*\\)|-idirafter \\1|p }'" + OUTPUT_VARIABLE CLANG_SYSTEM_INCLUDES_output + ERROR_VARIABLE CLANG_SYSTEM_INCLUDES_error + RESULT_VARIABLE CLANG_SYSTEM_INCLUDES_result + OUTPUT_STRIP_TRAILING_WHITESPACE) +if(${CLANG_SYSTEM_INCLUDES_result} EQUAL 0) + separate_arguments(CLANG_SYSTEM_INCLUDES UNIX_COMMAND ${CLANG_SYSTEM_INCLUDES_output}) + message(STATUS "BPF system include flags: ${CLANG_SYSTEM_INCLUDES}") +else() + message(FATAL_ERROR "Failed to determine BPF system includes: ${CLANG_SYSTEM_INCLUDES_error}") +endif() + +# Get target arch +execute_process(COMMAND uname -m + COMMAND sed -e "s/x86_64/x86/" -e "s/aarch64/arm64/" -e "s/ppc64le/powerpc/" -e "s/mips.*/mips/" -e "s/riscv64/riscv/" + OUTPUT_VARIABLE ARCH_output + ERROR_VARIABLE ARCH_error + RESULT_VARIABLE ARCH_result + OUTPUT_STRIP_TRAILING_WHITESPACE) +if(${ARCH_result} EQUAL 0) + set(ARCH ${ARCH_output}) + message(STATUS "BPF target arch: ${ARCH}") +else() + message(FATAL_ERROR "Failed to determine target architecture: ${ARCH_error}") +endif() + +# Public macro +macro(bpf_object name input) + set(BPF_C_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${input}) + foreach(arg ${ARGN}) + list(APPEND BPF_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/${arg}) + endforeach() + set(BPF_O_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.bpf.o) + set(BPF_SKEL_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.skel.h) + set(OUTPUT_TARGET ${name}_skel) + + # Build BPF object file + add_custom_command(OUTPUT ${BPF_O_FILE} + COMMAND ${BPFOBJECT_CLANG_EXE} -g -O2 -target bpf -D__TARGET_ARCH_${ARCH} + ${CLANG_SYSTEM_INCLUDES} -I${GENERATED_VMLINUX_DIR} + -isystem ${LIBBPF_INCLUDE_DIRS} -c ${BPF_C_FILE} -o ${BPF_O_FILE} + COMMAND_EXPAND_LISTS + VERBATIM + DEPENDS ${BPF_C_FILE} ${BPF_H_FILES} + COMMENT "[clang] Building BPF object: ${name}") + + # Build BPF skeleton header + add_custom_command(OUTPUT ${BPF_SKEL_FILE} + COMMAND bash -c "${BPFOBJECT_BPFTOOL_EXE} gen skeleton ${BPF_O_FILE} > ${BPF_SKEL_FILE}" + VERBATIM + DEPENDS ${BPF_O_FILE} + COMMENT "[skel] Building BPF skeleton: ${name}") + + add_library(${OUTPUT_TARGET} INTERFACE) + target_sources(${OUTPUT_TARGET} INTERFACE ${BPF_SKEL_FILE}) + target_include_directories(${OUTPUT_TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) + target_include_directories(${OUTPUT_TARGET} SYSTEM INTERFACE ${LIBBPF_INCLUDE_DIRS}) + target_link_libraries(${OUTPUT_TARGET} INTERFACE ${LIBBPF_LIBRARIES} -lelf -lz) +endmacro() + diff --git a/cmake/FindLibBpf.cmake b/cmake/FindLibBpf.cmake new file mode 100644 index 0000000000..cd558f5544 --- /dev/null +++ b/cmake/FindLibBpf.cmake @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +find_path(LIBBPF_INCLUDE_DIRS + NAMES + bpf/bpf.h + bpf/btf.h + bpf/libbpf.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ENV CPATH) + +find_library(LIBBPF_LIBRARIES + NAMES + bpf + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ENV LIBRARY_PATH + ENV LD_LIBRARY_PATH) + +include (FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibBpf "Please install the libbpf development package" + LIBBPF_LIBRARIES + LIBBPF_INCLUDE_DIRS) + +mark_as_advanced(LIBBPF_INCLUDE_DIRS LIBBPF_LIBRARIES) diff --git a/collector/lib/CMakeLists.txt b/collector/lib/CMakeLists.txt index 4eafcf9bd8..17d79094d5 100644 --- a/collector/lib/CMakeLists.txt +++ b/collector/lib/CMakeLists.txt @@ -1,10 +1,14 @@ +find_package(BpfObject REQUIRED) + +add_subdirectory(sources) + file(GLOB COLLECTOR_LIB_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/system-inspector/*.cpp ) add_library(collector_lib ${DRIVER_HEADERS} ${COLLECTOR_LIB_SRC_FILES}) -add_dependencies(collector_lib sinsp) +add_dependencies(collector_lib sinsp collector_scrapers) target_link_libraries(collector_lib sinsp) target_link_libraries(collector_lib stdc++fs) # This is needed for GCC-8 to link against the filesystem library target_link_libraries(collector_lib cap-ng) @@ -14,6 +18,7 @@ target_link_libraries(collector_lib civetweb::civetweb-cpp) target_link_libraries(collector_lib yaml-cpp::yaml-cpp) target_link_libraries(collector_lib rox-proto) +target_link_libraries(collector_lib collector_scrapers) if(NOT DISABLE_PROFILING) find_library(GPERFTOOLS_PROFILER profiler) diff --git a/collector/lib/CollectorService.cpp b/collector/lib/CollectorService.cpp index 78f66b9a1e..09f4324ea5 100644 --- a/collector/lib/CollectorService.cpp +++ b/collector/lib/CollectorService.cpp @@ -119,6 +119,8 @@ void CollectorService::RunForever() { server.addHandler(collector_config_inspector->kBaseRoute, collector_config_inspector.get()); } + bpf_programs_.Load(); + system_inspector_.Init(config_, conn_tracker); system_inspector_.Start(); diff --git a/collector/lib/CollectorService.h b/collector/lib/CollectorService.h index 5cfcab2afd..d56cb4f1a9 100644 --- a/collector/lib/CollectorService.h +++ b/collector/lib/CollectorService.h @@ -3,6 +3,7 @@ #include "CollectorConfig.h" #include "Control.h" +#include "sources/bpf-scraper/BPFProgramIterator.h" #include "system-inspector/Service.h" namespace collector { @@ -24,6 +25,7 @@ class CollectorService { const std::atomic& signum_; system_inspector::Service system_inspector_; + sources::BPFProgramIterator bpf_programs_; }; bool SetupKernelDriver(CollectorService& collector, const CollectorConfig& config); diff --git a/collector/lib/sources/CMakeLists.txt b/collector/lib/sources/CMakeLists.txt new file mode 100644 index 0000000000..159f6bb8ed --- /dev/null +++ b/collector/lib/sources/CMakeLists.txt @@ -0,0 +1,2 @@ + +add_subdirectory(bpf-scraper) diff --git a/collector/lib/sources/bpf-scraper/BPFProgramIterator.cpp b/collector/lib/sources/bpf-scraper/BPFProgramIterator.cpp new file mode 100644 index 0000000000..91f8cfe55a --- /dev/null +++ b/collector/lib/sources/bpf-scraper/BPFProgramIterator.cpp @@ -0,0 +1,65 @@ +#include "BPFProgramIterator.h" + +#include + +#include +#include + +#include "bpf/common.h" +#include "program_iter.skel.h" + +namespace collector::sources { + +static struct program_iter_bpf* skeleton_; + +bool BPFProgramIterator::Load() { + skeleton_ = program_iter_bpf__open_and_load(); + if (!skeleton_) { + return false; + } + + if (program_iter_bpf__attach(skeleton_) != 0) { + return false; + } + + return true; +} + +void BPFProgramIterator::Unload() { + program_iter_bpf__destroy(skeleton_); +} + +std::vector BPFProgramIterator::LoadedPrograms() { + int iter_fd = bpf_iter_create(bpf_link__fd(skeleton_->links.dump_bpf_prog)); + if (iter_fd < 0) { + return {}; + } + + std::vector messages; + struct bpf_prog_result result; + + while (true) { + int ret = read(iter_fd, &result, sizeof(struct bpf_prog_result)); + if (ret < 0) { + if (errno == EAGAIN) { + continue; + } + break; + } + + if (ret == 0) { + break; + } + + auto message = formatter_.ToProtoMessage(&result); + if (message == nullptr) { + continue; + } + + messages.push_back(message); + } + + return messages; +} + +} // namespace collector::sources diff --git a/collector/lib/sources/bpf-scraper/BPFProgramIterator.h b/collector/lib/sources/bpf-scraper/BPFProgramIterator.h new file mode 100644 index 0000000000..3837816e8e --- /dev/null +++ b/collector/lib/sources/bpf-scraper/BPFProgramIterator.h @@ -0,0 +1,23 @@ +#ifndef _COLLECTOR_BPF_PROGRAM_ITERATOR_ +#define _COLLECTOR_BPF_PROGRAM_ITERATOR_ + +#include + +#include "internalapi/sensor/signal_iservice.pb.h" + +#include "BPFSignalFormatter.h" + +namespace collector::sources { +class BPFProgramIterator { + public: + bool Load(); + void Unload(); + + std::vector LoadedPrograms(); + + private: + BPFSignalFormatter formatter_; +}; +}; // namespace collector::sources + +#endif diff --git a/collector/lib/sources/bpf-scraper/BPFSignalFormatter.h b/collector/lib/sources/bpf-scraper/BPFSignalFormatter.h new file mode 100644 index 0000000000..95675251c4 --- /dev/null +++ b/collector/lib/sources/bpf-scraper/BPFSignalFormatter.h @@ -0,0 +1,51 @@ +#include +#include + +#include + +#include "api/v1/signal.pb.h" +#include "internalapi/sensor/signal_iservice.pb.h" +#include "storage/bpf.pb.h" + +#include "ProtoAllocator.h" +#include "Utility.h" +#include "bpf/common.h" + +namespace collector::sources { + +class BPFSignalFormatter : protected ProtoAllocator { + using Signal = v1::Signal; + using BpfSignal = storage::BpfSignal; + + public: + sensor::SignalStreamMessage* ToProtoMessage(struct bpf_prog_result* result) { + BpfSignal* bpf_signal = CreateBpfSignal(result); + if (!bpf_signal) { + return nullptr; + } + + Signal* signal = Allocate(); + signal->set_allocated_bpf_signal(bpf_signal); + + sensor::SignalStreamMessage* signal_stream_message = AllocateRoot(); + signal_stream_message->clear_collector_register_request(); + signal_stream_message->set_allocated_signal(signal); + return signal_stream_message; + } + + private: + BpfSignal* CreateBpfSignal(struct bpf_prog_result* result) { + BpfSignal* signal = Allocate(); + + // signal->set_id(UUIDStr()); + const std::string name(result->name); + const std::string attach_point(result->attached); + + signal->set_name(name); + signal->set_attach_point(attach_point); + + return signal; + } +}; + +} // namespace collector::sources diff --git a/collector/lib/sources/bpf-scraper/BPFSignalHandler.h b/collector/lib/sources/bpf-scraper/BPFSignalHandler.h new file mode 100644 index 0000000000..9c3c49937a --- /dev/null +++ b/collector/lib/sources/bpf-scraper/BPFSignalHandler.h @@ -0,0 +1,4 @@ + +namespace collector::sources { + +} // namespace collector::sources diff --git a/collector/lib/sources/bpf-scraper/CMakeLists.txt b/collector/lib/sources/bpf-scraper/CMakeLists.txt new file mode 100644 index 0000000000..4f7f55c2c6 --- /dev/null +++ b/collector/lib/sources/bpf-scraper/CMakeLists.txt @@ -0,0 +1,18 @@ + +bpf_object(program_iter bpf/programs.bpf.c) + +get_target_property(SKEL_SOURCES program_iter_skel INTERFACE_SOURCES) + +add_library(collector_scrapers + BPFProgramIterator.cpp + BPFProgramIterator.h + BPFSignalFormatter.h + ${SKEL_SOURCES} +) + +target_include_directories(collector_scrapers PRIVATE $) +target_include_directories(collector_scrapers PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + +add_executable(bpf_prog_scraper standalone.cpp ${SKEL_SOURCES}) +target_link_libraries(bpf_prog_scraper collector_scrapers) +target_include_directories(bpf_prog_scraper PRIVATE $) diff --git a/collector/lib/sources/bpf-scraper/bpf/common.h b/collector/lib/sources/bpf-scraper/bpf/common.h new file mode 100644 index 0000000000..2438b02e2e --- /dev/null +++ b/collector/lib/sources/bpf-scraper/bpf/common.h @@ -0,0 +1,12 @@ +#ifndef __SCRAPER_BPF_ITER_H +#define __SCRAPER_BPF_ITER_H + +#define BPF_STR_MAX 32 + +struct bpf_prog_result { + unsigned int id; + char name[BPF_STR_MAX]; + char attached[BPF_STR_MAX]; +}; + +#endif diff --git a/collector/lib/sources/bpf-scraper/bpf/programs.bpf.c b/collector/lib/sources/bpf-scraper/bpf/programs.bpf.c new file mode 100644 index 0000000000..01d1c13774 --- /dev/null +++ b/collector/lib/sources/bpf-scraper/bpf/programs.bpf.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Red Hat, Inc. */ +#include + +#include +#include + +#include "common.h" + +char _license[] SEC("license") = "GPL"; + +static const char* get_name(struct btf* btf, long btf_id, const char* fallback) { + struct btf_type **types, *t; + unsigned int name_off; + const char* str; + + if (!btf) { + return fallback; + } + str = btf->strings; + types = btf->types; + bpf_probe_read_kernel(&t, sizeof(t), types + btf_id); + name_off = BPF_CORE_READ(t, name_off); + if (name_off >= btf->hdr.str_len) { + return fallback; + } + return str + name_off; +} + +SEC("iter/bpf_link") +int dump_bpf_link(struct bpf_iter__bpf_link* ctx) { + struct seq_file* seq = ctx->meta->seq; + struct bpf_link* link = ctx->link; + int link_id; + + if (!link) { + return 0; + } + + link_id = link->id; + BPF_SEQ_PRINTF(seq, "%d\n", link_id); + return 0; +} + +SEC("iter/bpf_prog") +int dump_bpf_prog(struct bpf_iter__bpf_prog* ctx) { + struct seq_file* seq = ctx->meta->seq; + __u64 seq_num = ctx->meta->seq_num; + struct bpf_prog* prog = ctx->prog; + struct bpf_prog_aux* aux; + + if (!prog) { + return 0; + } + + struct bpf_prog_result result = {0}; + + aux = prog->aux; + + result.id = aux->id; + bpf_core_read_str(result.name, BPF_STR_MAX, get_name(aux->btf, aux->func_info[0].type_id, aux->name)); + bpf_core_read_str(result.attached, BPF_STR_MAX, aux->attach_func_name); + + bpf_seq_write(seq, &result, sizeof(result)); + + return 0; +} diff --git a/collector/lib/sources/bpf-scraper/standalone.cpp b/collector/lib/sources/bpf-scraper/standalone.cpp new file mode 100644 index 0000000000..e9b96fb298 --- /dev/null +++ b/collector/lib/sources/bpf-scraper/standalone.cpp @@ -0,0 +1,24 @@ + +#include + +#include "BPFProgramIterator.h" + +using namespace collector::sources; + +int main(int argc, char** argv) { + BPFProgramIterator iterator; + + if (!iterator.Load()) { + std::cerr << "Failed to load BPF Program Iterator" << std::endl; + return -1; + } + + auto programs = iterator.LoadedPrograms(); + + for (auto& prog : programs) { + std::cout << prog << std::endl; + } + + iterator.Unload(); + return 0; +} diff --git a/collector/proto/CMakeLists.txt b/collector/proto/CMakeLists.txt index 279d64c0dd..4f53449899 100644 --- a/collector/proto/CMakeLists.txt +++ b/collector/proto/CMakeLists.txt @@ -16,6 +16,7 @@ set(ROX_PROTO_FILES internalapi/sensor/signal_iservice.proto storage/network_flow.proto storage/process_indicator.proto + storage/bpf.proto ) add_library(rox-proto ${ROX_PROTO_FILES}) diff --git a/collector/proto/third_party/stackrox b/collector/proto/third_party/stackrox index ef2e2cc853..f74eda5d25 160000 --- a/collector/proto/third_party/stackrox +++ b/collector/proto/third_party/stackrox @@ -1 +1 @@ -Subproject commit ef2e2cc85370988de7f0d01c56338c0d14804e79 +Subproject commit f74eda5d250c4e393465cf09b58937aa55c28a55