diff --git a/CMakeLists.txt b/CMakeLists.txt index fa1ac5e9..24e43a38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,9 @@ endif() if(NOT TARGET fmt) find_package(fmt REQUIRED) endif(NOT TARGET fmt) +if(NOT TARGET spdlog) + find_package(spdlog REQUIRED) +endif(NOT TARGET spdlog) find_package(Protobuf REQUIRED) @@ -101,11 +104,12 @@ set(LIBBPF_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/thirdparty/libbpf) set(LIBBPF_LIBRARIES ${CMAKE_BINARY_DIR}/thirdparty/libbpf/libbpf.a) add_subdirectory(libbpfload) -add_subdirectory(libebpfdiscoveryproto) add_subdirectory(libebpfdiscovery) +add_subdirectory(libebpfdiscoveryproto) add_subdirectory(libebpfdiscoveryshared) add_subdirectory(libebpfdiscoveryskel) add_subdirectory(libhttpparser) +add_subdirectory(liblogging) include(GNUInstallDirs) add_subdirectory(ebpfdiscoverysrv) diff --git a/ebpfdiscoverysrv/CMakeLists.txt b/ebpfdiscoverysrv/CMakeLists.txt index 37b5d55c..4b9eb0f1 100644 --- a/ebpfdiscoverysrv/CMakeLists.txt +++ b/ebpfdiscoverysrv/CMakeLists.txt @@ -5,4 +5,5 @@ add_executable(${TARGET} ${SOURCES}) target_link_libraries(${TARGET} Boost::program_options) target_link_libraries(${TARGET} ebpfdiscovery) +target_link_libraries(${TARGET} logging) target_compile_definitions(${TARGET} PUBLIC PROJECT_VERSION="${PROJECT_VERSION}") diff --git a/ebpfdiscoverysrv/src/main.cpp b/ebpfdiscoverysrv/src/main.cpp index 473ef25f..56cc0a0b 100644 --- a/ebpfdiscoverysrv/src/main.cpp +++ b/ebpfdiscoverysrv/src/main.cpp @@ -1,62 +1,186 @@ // SPDX-License-Identifier: GPL-2.0 - #include "ebpfdiscovery/Discovery.h" +#include "ebpfdiscovery/DiscoveryBpf.h" +#include "ebpfdiscovery/DiscoveryBpfLoader.h" +#include "logging/Logger.h" -#include +#include +#include +#include #include #include #include +#include #include -static ebpfdiscovery::Discovery discoveryInstance; +namespace po = boost::program_options; +using logging::Logger; +using logging::LogLevel; -std::atomic isShuttingDown; +enum class ProgramStatus { + Running, + UnixShutdownSignalReceived, +} programStatus; -static void handleUnixExitSignal(int signo) { - if (isShuttingDown) { - return; - } - isShuttingDown = true; +std::condition_variable programStatusCV; +std::mutex programStatusMutex; + +/* + * CLI options + */ + +static po::options_description getProgramOptions() { + po::options_description desc{"Options"}; + + // clang-format off + desc.add_options() + ("log-level", po::value()->default_value(logging::LogLevel::Err, "error"), "Set log level {trace,debug,info,warning,error,critical,off}") + ("help,h", "Display available options") + ("log-dir", po::value()->default_value(""), "Log files directory") + ("log-no-stdout", po::value()->default_value(false), "Disable logging to stdout") + ("version", "Display program version") + ; + // clang-format on - discoveryInstance.stopRun(); + return desc; } -void setupUnixSignalHandlers() { - struct sigaction action {}; - action.sa_handler = handleUnixExitSignal; - action.sa_flags = 0; - sigaction(SIGINT, &action, nullptr); - sigaction(SIGTERM, &action, nullptr); - sigaction(SIGPIPE, &action, nullptr); +/* + * Logging setup + */ + +static void setupLogging(logging::LogLevel logLevel, bool enableStdout, const std::filesystem::path& logDir) { + Logger::getInstance().setup("eBPF-Discovery", enableStdout, logDir); + Logger::getInstance().setLevel(logLevel); + LOG_TRACE("Logging has been set up. (logDir: {})", logDir.string()); } +/* + * Unix signals setup + */ + +static sigset_t getSigset() { + sigset_t sigset; + sigfillset(&sigset); + return sigset; +} + +static void runUnixSignalHandlerLoop() { + while (true) { + sigset_t sigset{getSigset()}; + LOG_TRACE("Waiting for unix signals."); + const auto signo{sigwaitinfo(&sigset, nullptr)}; + if (signo == -1) { + LOG_CRITICAL("Failed to wait for unix signals: {}", std::strerror(errno)); + std::abort(); + } + LOG_DEBUG("Received unix signal. (signo: {})", signo); + if (signo == SIGINT || signo == SIGPIPE || signo == SIGTERM) { + std::lock_guard lock(programStatusMutex); + programStatus = ProgramStatus::UnixShutdownSignalReceived; + LOG_TRACE("Unix signal handler is notifying for shutdown."); + programStatusCV.notify_all(); + break; + } + } +} + +/* + * Libbpf setup + */ + static int libbpfPrintFn(enum libbpf_print_level level, const char* format, va_list args) { -#ifdef DEBUG - return vfprintf(stderr, format, args); -#else + switch (level) { + case LIBBPF_WARN: + Logger::getInstance().vlogf(logging::LogLevel::Warn, format, args); + return 0; + case LIBBPF_INFO: + Logger::getInstance().vlogf(logging::LogLevel::Info, format, args); + return 0; + case LIBBPF_DEBUG: + Logger::getInstance().vlogf(logging::LogLevel::Debug, format, args); + return 0; + } return 0; -#endif } -void setupLibbpf() { +static void setupLibbpf() { libbpf_set_print(libbpfPrintFn); } int main(int argc, char** argv) { - setupLibbpf(); - setupUnixSignalHandlers(); + po::options_description desc{getProgramOptions()}; + po::variables_map vm; + + try { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + } catch (const po::error& e) { + std::cout << e.what() << '\n'; + return EXIT_FAILURE; + } + + if (vm.count("help")) { + std::cout << desc; + return EXIT_SUCCESS; + } + + if (vm.count("version")) { + std::cout << "eBPF-Discovery " << PROJECT_VERSION << '\n'; + return EXIT_SUCCESS; + } + + logging::LogLevel logLevel{vm["log-level"].as()}; + bool isStdoutLogDisabled{vm["log-no-stdout"].as()}; + std::filesystem::path logDir{vm["log-dir"].as()}; try { - discoveryInstance.load(); + setupLogging(logLevel, !isStdoutLogDisabled, logDir); } catch (const std::runtime_error& e) { - std::cerr << "Couldn't load BPF program: " << e.what() << std::endl; + std::cerr << "Couldn't setup logging: " << e.what() << '\n'; return EXIT_FAILURE; } - if (discoveryInstance.run() != 0) { + LOG_DEBUG("Starting the program."); + + { + LOG_TRACE("Setting up unix signals handling."); + sigset_t sigset = getSigset(); + if (sigprocmask(SIG_BLOCK, &sigset, nullptr) == -1) { + LOG_CRITICAL("Failed to block unix signals: {}", std::strerror(errno)); + return EXIT_FAILURE; + } + } + + setupLibbpf(); + ebpfdiscovery::DiscoveryBpfLoader loader; + try { + loader.load(); + } catch (const std::runtime_error& e) { + LOG_CRITICAL("Couldn't load BPF program. ({})", e.what()); return EXIT_FAILURE; } + ebpfdiscovery::Discovery instance(loader.get()); + try { + instance.start(); + } catch (const std::runtime_error& e) { + LOG_CRITICAL("Couldn't start Discovery: {}", e.what()); + } + + std::thread unixSignalThread(runUnixSignalHandlerLoop); + { + std::unique_lock programStatusLock(programStatusMutex); + programStatusCV.wait(programStatusLock, []() { return programStatus != ProgramStatus::Running; }); + } + + LOG_DEBUG("Exiting the program."); + if (unixSignalThread.joinable()) { + unixSignalThread.join(); + } + instance.stop(); + instance.wait(); + return EXIT_SUCCESS; } diff --git a/libebpfdiscovery/CMakeLists.txt b/libebpfdiscovery/CMakeLists.txt index 6f5c0ff0..d93171f2 100644 --- a/libebpfdiscovery/CMakeLists.txt +++ b/libebpfdiscovery/CMakeLists.txt @@ -3,6 +3,8 @@ list( SOURCES src/Config.cpp src/Discovery.cpp + src/DiscoveryBpf.cpp + src/DiscoveryBpfLoader.cpp src/IpAddressChecker.cpp src/NetlinkCalls.cpp src/Session.cpp @@ -15,11 +17,13 @@ add_library(${TARGET} STATIC ${SOURCES}) target_include_directories(${TARGET} PRIVATE src PUBLIC headers) target_link_libraries(${TARGET} bpfload) +target_link_libraries(${TARGET} ebpfdiscoveryproto) target_link_libraries(${TARGET} ebpfdiscoveryshared) target_link_libraries(${TARGET} ebpfdiscoveryskel) target_link_libraries(${TARGET} fmt::fmt) target_link_libraries(${TARGET} httpparser) -target_link_libraries(${TARGET} ebpfdiscoveryproto) +target_link_libraries(${TARGET} logging) +target_link_libraries(${TARGET} spdlog::spdlog) if(BUILD_TESTS) list(APPEND TEST_SOURCES test/StringFunctionsTest.cpp test/LRUCacheTest.cpp test/IpAddressCheckerTest.cpp) diff --git a/libebpfdiscovery/headers/ebpfdiscovery/Discovery.h b/libebpfdiscovery/headers/ebpfdiscovery/Discovery.h index 427c6f16..c4e44180 100644 --- a/libebpfdiscovery/headers/ebpfdiscovery/Discovery.h +++ b/libebpfdiscovery/headers/ebpfdiscovery/Discovery.h @@ -2,17 +2,19 @@ #pragma once #include "ebpfdiscovery/Config.h" +#include "ebpfdiscovery/DiscoveryBpf.h" #include "ebpfdiscovery/LRUCache.h" #include "ebpfdiscovery/Session.h" #include "ebpfdiscoveryshared/Types.h" #include "httpparser/HttpRequestParser.h" -#include "discovery.skel.h" - #include #include +#include +#include #include #include +#include #include namespace ebpfdiscovery { @@ -21,23 +23,23 @@ using httpparser::HttpRequestParser; class Discovery { public: - Discovery(); - Discovery(const DiscoveryConfig config); - ~Discovery(); - - bool isLoaded() noexcept; - void load(); - void unload() noexcept; - - // Blocks current thread until stopRun() is called - int run(); - - // Thread safe operation - void stopRun(); + Discovery(DiscoveryBpf discoveryBpf); + Discovery(DiscoveryBpf discoveryBpf, const DiscoveryConfig config); + Discovery(const Discovery&) = delete; + Discovery& operator=(const Discovery&) = delete; + Discovery(Discovery&&) = default; + Discovery& operator=(Discovery&&) = default; + ~Discovery() = default; + + void start(); + void stop(); + void wait(); private: typedef LRUCache SavedSessionsCacheType; + void run(); + void fetchEvents(); void saveSession(const DiscoverySavedSessionKey& session_key, const Session& session); @@ -47,6 +49,7 @@ class Discovery { void handleBufferLookupSuccess(DiscoverySavedBuffer& savedBuffer, DiscoveryEvent& event); void handleExistingSession(SavedSessionsCacheType::iterator it, std::string_view& bufferView, DiscoveryEvent& event); void handleNewSession(std::string_view& bufferView, DiscoveryEvent& event); + void handleNewRequest(const Session& session, const DiscoverySessionMeta& meta); void handleCloseEvent(DiscoveryEvent& event); void handleSuccessfulParse(const Session& session, const DiscoverySessionMeta& sessionMeta); @@ -54,13 +57,19 @@ class Discovery { int bpfDiscoveryResumeCollecting(); int bpfDiscoveryDeleteSession(const DiscoveryTrackedSessionKey& trackedSessionKey); - DiscoveryConfig config; + constexpr auto discoverySkel() const { + return discoveryBpf.skel; + } - std::atomic running; - std::atomic loaded; - discovery_bpf* discoverySkel; - bpf_object_open_opts discoverySkelOpenOpts; + DiscoveryConfig config; + DiscoveryBpf discoveryBpf; SavedSessionsCacheType savedSessions; + + std::atomic running{false}; + bool stopReceived{false}; + std::condition_variable stopReceivedCV; + std::mutex stopReceivedMutex; + std::thread workerThread; }; } // namespace ebpfdiscovery diff --git a/libebpfdiscovery/headers/ebpfdiscovery/DiscoveryBpf.h b/libebpfdiscovery/headers/ebpfdiscovery/DiscoveryBpf.h new file mode 100644 index 00000000..1ca27af1 --- /dev/null +++ b/libebpfdiscovery/headers/ebpfdiscovery/DiscoveryBpf.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include "discovery.skel.h" + +namespace ebpfdiscovery { + +class DiscoveryBpf { +public: + DiscoveryBpf(discovery_bpf* skel); + DiscoveryBpf(const DiscoveryBpf&) = default; + DiscoveryBpf& operator=(const DiscoveryBpf&) = default; + DiscoveryBpf(DiscoveryBpf&&) = default; + DiscoveryBpf& operator=(DiscoveryBpf&&) = default; + ~DiscoveryBpf() = default; + + discovery_bpf* skel; +}; + +} // namespace ebpfdiscovery diff --git a/libebpfdiscovery/headers/ebpfdiscovery/DiscoveryBpfLoader.h b/libebpfdiscovery/headers/ebpfdiscovery/DiscoveryBpfLoader.h new file mode 100644 index 00000000..b5d38aa8 --- /dev/null +++ b/libebpfdiscovery/headers/ebpfdiscovery/DiscoveryBpfLoader.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include "ebpfdiscovery/DiscoveryBpf.h" + +#include "discovery.skel.h" + +#include + +namespace ebpfdiscovery { +class DiscoveryBpfLoader { +public: + DiscoveryBpfLoader(); + DiscoveryBpfLoader(const DiscoveryBpfLoader&) = delete; + DiscoveryBpfLoader& operator=(const DiscoveryBpfLoader&) = delete; + DiscoveryBpfLoader(DiscoveryBpfLoader&&) = default; + DiscoveryBpfLoader& operator=(DiscoveryBpfLoader&&) = default; + ~DiscoveryBpfLoader(); + + void load(); + void unload() noexcept; + bool isLoaded() noexcept; + + DiscoveryBpf get(); + +private: + std::atomic loaded; + bpf_object_open_opts openOpts; + discovery_bpf* skel; +}; + +} // namespace ebpfdiscovery diff --git a/libebpfdiscovery/src/Discovery.cpp b/libebpfdiscovery/src/Discovery.cpp index 294907f4..1040f3bb 100644 --- a/libebpfdiscovery/src/Discovery.cpp +++ b/libebpfdiscovery/src/Discovery.cpp @@ -1,15 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 - #include "ebpfdiscovery/Discovery.h" #include "StringFunctions.h" #include "ebpfdiscovery/Session.h" - -extern "C" { -#include "bpfload/btf_helpers.h" -} - -#include "discovery.skel.h" +#include "logging/Logger.h" #include #include @@ -24,49 +18,54 @@ extern "C" { namespace ebpfdiscovery { -static void printSession(const Session& session, const DiscoverySessionMeta& meta) { - const auto& request{session.parser.result}; - std::cout << request.method << " " << request.host << request.url; - if (const auto& xForwardedFor{request.xForwardedFor}; !xForwardedFor.empty()) { - std::cout << " X-Forwarded-For: " << '"' << xForwardedFor << '"'; - } else if (discoverySessionFlagsIsIPv4(meta.flags)) { - if (auto srcIpv4{ipv4ToString(meta.sourceIPData)}; !srcIpv4.empty()) - std::cout << " srcIpv4: " << '"' << srcIpv4 << '"'; - } else if (discoverySessionFlagsIsIPv6(meta.flags)) { - if (auto srcIpv6{ipv6ToString(meta.sourceIPData)}; !srcIpv6.empty()) - std::cout << " srcIpv6: " << '"' << srcIpv6 << '"'; - } - std::cout << " pid: " << meta.pid << '\n'; -} - -Discovery::Discovery() : Discovery(DiscoveryConfig{}) { +Discovery::Discovery(DiscoveryBpf discoveryBpf) : Discovery(discoveryBpf, DiscoveryConfig{}) { } -Discovery::Discovery(const DiscoveryConfig config) : savedSessions(DISCOVERY_MAX_SESSIONS) { +Discovery::Discovery(DiscoveryBpf discoveryBpf, const DiscoveryConfig config) + : discoveryBpf(discoveryBpf), savedSessions(DISCOVERY_MAX_SESSIONS) { } -Discovery::~Discovery() { - unload(); -} +void Discovery::start() { + if (running) { + return; + } + running = true; -int Discovery::run() { - if (!isLoaded()) { - return -1; + if (int ret{bpfDiscoveryResumeCollecting()}; ret != 0) { + running = false; + throw std::runtime_error("Could not initialize BPF program configuration: " + std::to_string(ret)); } - running = true; - while (running) { + workerThread = std::thread([&]() { run(); }); +} + +void Discovery::run() { + LOG_TRACE("Discovery is starting the BPF event handler loop."); + std::unique_lock lock(stopReceivedMutex); + while (!stopReceived) { fetchEvents(); bpfDiscoveryResumeCollecting(); - std::this_thread::sleep_for(config.eventQueuePollInterval); + stopReceivedCV.wait_for(lock, config.eventQueuePollInterval); } - return 0; + return; +} + +void Discovery::stop() { + std::lock_guard lock(stopReceivedMutex); + stopReceived = true; + stopReceivedCV.notify_all(); +} + +void Discovery::wait() { + if (workerThread.joinable()) { + workerThread.join(); + } } void Discovery::fetchEvents() { DiscoveryEvent event; - while (bpf_map__lookup_and_delete_elem(discoverySkel->maps.eventsToUserspaceQueueMap, NULL, 0, &event, sizeof(event), BPF_ANY) == 0) { + while (bpf_map__lookup_and_delete_elem(discoverySkel()->maps.eventsToUserspaceQueueMap, NULL, 0, &event, sizeof(event), BPF_ANY) == 0) { handleNewEvent(std::move(event)); } } @@ -83,7 +82,7 @@ void Discovery::handleNewEvent(DiscoveryEvent event) { void Discovery::handleNewDataEvent(DiscoveryEvent& event) { DiscoverySavedBuffer savedBuffer; auto lookup_result{bpf_map__lookup_elem( - discoverySkel->maps.savedBuffersMap, + discoverySkel()->maps.savedBuffersMap, &event.dataKey, sizeof(DiscoverySavedBufferKey), &savedBuffer, @@ -98,7 +97,7 @@ void Discovery::handleNewDataEvent(DiscoveryEvent& event) { void Discovery::handleBufferLookupSuccess(DiscoverySavedBuffer& savedBuffer, DiscoveryEvent& event) { std::string_view bufferView(savedBuffer.data, savedBuffer.length); - bpf_map__delete_elem(discoverySkel->maps.savedBuffersMap, &event.dataKey, sizeof(DiscoverySavedBufferKey), BPF_ANY); + bpf_map__delete_elem(discoverySkel()->maps.savedBuffersMap, &event.dataKey, sizeof(DiscoverySavedBufferKey), BPF_ANY); auto it{savedSessions.find(event.dataKey)}; if (it != savedSessions.end()) { @@ -146,6 +145,37 @@ void Discovery::handleNewSession(std::string_view& bufferView, DiscoveryEvent& e handleSuccessfulParse(session, event.sessionMeta); } +void Discovery::handleNewRequest(const Session& session, const DiscoverySessionMeta& meta) { + const auto& request{session.parser.result}; + if (discoverySessionFlagsIsIPv4(meta.flags)) { + LOG_DEBUG( + "Handling new request. (method:'{}', host:'{}', url:'{}', X-Forwarded-For:'{}', sourceIPv4:'{}', pid:{})", + request.method, + request.host, + request.url, + request.xForwardedFor, + ipv4ToString(meta.sourceIPData), + meta.pid); + } else if (discoverySessionFlagsIsIPv6(meta.flags)) { + LOG_DEBUG( + "Handling new request. (method:'{}', host:'{}', url:'{}', X-Forwarded-For:'{}', sourceIPv6:'{}', pid:{})", + request.method, + request.host, + request.url, + request.xForwardedFor, + ipv6ToString(meta.sourceIPData), + meta.pid); + } else { + LOG_DEBUG( + "Handling new request. (method:'{}', host:'{}', url:'{}', X-Forwarded-For:'{}', pid:{})", + request.method, + request.host, + request.url, + request.xForwardedFor, + meta.pid); + } +} + void Discovery::handleCloseEvent(DiscoveryEvent& event) { if (auto it{savedSessions.find(event.dataKey)}; it != savedSessions.end()) { savedSessions.erase(it); @@ -156,60 +186,15 @@ int Discovery::bpfDiscoveryResumeCollecting() { static uint32_t zero{0}; DiscoveryGlobalState discoveryGlobalState{}; return bpf_map__update_elem( - discoverySkel->maps.globalStateMap, &zero, sizeof(zero), &discoveryGlobalState, sizeof(discoveryGlobalState), BPF_EXIST); + discoverySkel()->maps.globalStateMap, &zero, sizeof(zero), &discoveryGlobalState, sizeof(discoveryGlobalState), BPF_EXIST); } int Discovery::bpfDiscoveryResetConfig() { return bpfDiscoveryResumeCollecting(); } -bool Discovery::isLoaded() noexcept { - return discoverySkel != nullptr && loaded; -} - -void Discovery::load() { - LIBBPF_OPTS(bpf_object_open_opts, openOpts); - discoverySkelOpenOpts = openOpts; - - if (int res{ensure_core_btf(&openOpts)}) { - throw std::runtime_error("Failed to fetch necessary BTF for CO-RE: " + std::string(strerror(-res))); - } - - discoverySkel = discovery_bpf__open_opts(&openOpts); - if (discoverySkel == nullptr) { - throw std::runtime_error("Failed to open BPF object."); - } - - if (int res{discovery_bpf__load(discoverySkel)}) { - throw std::runtime_error("Failed to load BPF object: " + std::to_string(res)); - } - - if (int res{discovery_bpf__attach(discoverySkel)}) { - throw std::runtime_error("Failed to attach BPF object: " + std::to_string(res)); - } - - if (int res{bpfDiscoveryResumeCollecting()}) { - throw std::runtime_error("Failed to set config of BPF program: " + std::to_string(res)); - } - - loaded = true; -} - -void Discovery::unload() noexcept { - stopRun(); - loaded = false; - if (discoverySkel != nullptr) { - discovery_bpf__destroy(discoverySkel); - } - cleanup_core_btf(&discoverySkelOpenOpts); -} - -void Discovery::stopRun() { - running = false; -} - void Discovery::handleSuccessfulParse(const Session& session, const DiscoverySessionMeta& meta) { - printSession(session, meta); + handleNewRequest(session, meta); } void Discovery::saveSession(const DiscoverySavedSessionKey& sessionKey, const Session& session) { @@ -217,7 +202,7 @@ void Discovery::saveSession(const DiscoverySavedSessionKey& sessionKey, const Se } int Discovery::bpfDiscoveryDeleteSession(const DiscoveryTrackedSessionKey& trackedSessionKey) { - return bpf_map__delete_elem(discoverySkel->maps.trackedSessionsMap, &trackedSessionKey, sizeof(trackedSessionKey), BPF_ANY); + return bpf_map__delete_elem(discoverySkel()->maps.trackedSessionsMap, &trackedSessionKey, sizeof(trackedSessionKey), BPF_ANY); } } // namespace ebpfdiscovery diff --git a/libebpfdiscovery/src/DiscoveryBpf.cpp b/libebpfdiscovery/src/DiscoveryBpf.cpp new file mode 100644 index 00000000..285c9ca6 --- /dev/null +++ b/libebpfdiscovery/src/DiscoveryBpf.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "ebpfdiscovery/DiscoveryBpf.h" + +namespace ebpfdiscovery { + +DiscoveryBpf::DiscoveryBpf(discovery_bpf* skel) : skel(skel) { +} + +} // namespace ebpfdiscovery diff --git a/libebpfdiscovery/src/DiscoveryBpfLoader.cpp b/libebpfdiscovery/src/DiscoveryBpfLoader.cpp new file mode 100644 index 00000000..6950d79e --- /dev/null +++ b/libebpfdiscovery/src/DiscoveryBpfLoader.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "ebpfdiscovery/DiscoveryBpfLoader.h" + +#include "logging/Logger.h" + +extern "C" { +#include "bpfload/btf_helpers.h" +} + +namespace ebpfdiscovery { + +DiscoveryBpfLoader::DiscoveryBpfLoader() { +} + +DiscoveryBpfLoader::~DiscoveryBpfLoader() { + unload(); +} + +void DiscoveryBpfLoader::load() { + LOG_DEBUG("Loading BPF program."); + LIBBPF_OPTS(bpf_object_open_opts, newOpenOpts); + openOpts = newOpenOpts; + + if (const auto res{ensure_core_btf(&openOpts)}) { + throw std::runtime_error("Failed to fetch necessary BTF for CO-RE: " + std::string(strerror(-res))); + } + + skel = discovery_bpf__open_opts(&openOpts); + if (skel == nullptr) { + throw std::runtime_error("Failed to open BPF object."); + } + + if (const auto res{discovery_bpf__load(skel)}) { + throw std::runtime_error("Failed to load BPF object: " + std::to_string(res)); + } + + if (const auto res{discovery_bpf__attach(skel)}) { + throw std::runtime_error("Failed to attach BPF object: " + std::to_string(res)); + } + + loaded = true; +} + +void DiscoveryBpfLoader::unload() noexcept { + loaded = false; + if (skel != nullptr) { + discovery_bpf__destroy(skel); + } + cleanup_core_btf(&openOpts); +} + +bool DiscoveryBpfLoader::isLoaded() noexcept { + return skel != nullptr && loaded; +} + +DiscoveryBpf DiscoveryBpfLoader::get() { + return DiscoveryBpf(skel); +} + +} // namespace ebpfdiscovery diff --git a/libebpfdiscoveryskel/src/Config.h b/libebpfdiscoveryskel/src/Config.h new file mode 100644 index 00000000..ff08a924 --- /dev/null +++ b/libebpfdiscoveryskel/src/Config.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 +#pragma once + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, struct DiscoveryConfig); + __uint(max_entries, 1); +} discoveryConfigMap SEC(".maps"); + +__attribute__((always_inline)) inline static struct DiscoveryConfig* getDiscoveryConfig() { + __u32 zero = 0; + return (struct DiscoveryConfig*)bpf_map_lookup_elem(&discoveryConfigMap, &zero); +} diff --git a/liblogging/CMakeLists.txt b/liblogging/CMakeLists.txt new file mode 100644 index 00000000..9b114895 --- /dev/null +++ b/liblogging/CMakeLists.txt @@ -0,0 +1,6 @@ +list(APPEND SOURCES src/Logger.cpp) +set(TARGET logging) + +add_library(${TARGET} SHARED ${SOURCES}) +target_include_directories(${TARGET} PRIVATE src PUBLIC headers) +target_link_libraries(${TARGET} spdlog::spdlog) diff --git a/liblogging/headers/logging/Logger.h b/liblogging/headers/logging/Logger.h new file mode 100644 index 00000000..774a0677 --- /dev/null +++ b/liblogging/headers/logging/Logger.h @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace logging { + +enum LogLevel { + Trace = static_cast(spdlog::level::trace), + Debug = static_cast(spdlog::level::debug), + Info = static_cast(spdlog::level::info), + Warn = static_cast(spdlog::level::warn), + Err = static_cast(spdlog::level::err), + Critical = static_cast(spdlog::level::critical), + Off = static_cast(spdlog::level::off) +}; + +std::istream& operator>>(std::istream& in, LogLevel& level); + +class Logger { +private: + static constexpr std::size_t max_size = 102400; + static constexpr std::size_t max_files = 3; + +public: + Logger(); + ~Logger() = default; + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + + static Logger& getInstance(); + + void setLevel(enum LogLevel level); + void setup(std::string name, bool logToStdout = true, std::filesystem::path logDir = {}); + + template + void log(enum LogLevel level, const char* fmt, const Args&... args) { + spdLogger.log(static_cast(level), fmt, args...); + } + + void vlogf(enum LogLevel level, const char* format, va_list args); + + template + void trace(const char* fmt, const Args&... args) { + spdLogger.trace(fmt, args...); + } + + template + void debug(const char* fmt, const Args&... args) { + spdLogger.debug(fmt, args...); + } + + template + void info(const char* fmt, const Args&... args) { + spdLogger.info(fmt, args...); + } + + template + void warn(const char* fmt, const Args&... args) { + spdLogger.warn(fmt, args...); + } + + template + void error(const char* fmt, const Args&... args) { + spdLogger.error(fmt, args...); + } + + template + void critical(const char* fmt, const Args&... args) { + spdLogger.critical(fmt, args...); + } + + template + void trace(const char* str) { + spdLogger.trace(str); + } + + template + void debug(const char* str) { + spdLogger.debug(str); + } + + template + void info(const char* str) { + spdLogger.info(str); + } + + template + void warn(const char* str) { + spdLogger.warn(str); + } + + template + void error(const char* str) { + spdLogger.error(str); + } + + template + void critical(const char* str) { + spdLogger.critical(str); + } + +private: + void loggerSetDefaults(); + void setLogger(spdlog::logger logger); + void logLine(enum LogLevel level, const char* str, size_t len); + + spdlog::logger spdLogger; +}; + +} // namespace logging + +#define LOG_TRACE(...) logging::Logger::getInstance().trace(__VA_ARGS__) +#define LOG_DEBUG(...) logging::Logger::getInstance().debug(__VA_ARGS__) +#define LOG_INFO(...) logging::Logger::getInstance().info(__VA_ARGS__) +#define LOG_WARN(...) logging::Logger::getInstance().warn(__VA_ARGS__) +#define LOG_ERROR(...) logging::Logger::getInstance().error(__VA_ARGS__) +#define LOG_CRITICAL(...) logging::Logger::getInstance().critical(__VA_ARGS__) diff --git a/liblogging/src/Logger.cpp b/liblogging/src/Logger.cpp new file mode 100644 index 00000000..64b7bc68 --- /dev/null +++ b/liblogging/src/Logger.cpp @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "logging/Logger.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace logging { + +std::istream& operator>>(std::istream& in, LogLevel& level) { + std::string token; + in >> token; + std::transform(token.begin(), token.end(), token.begin(), [](unsigned char c) { return std::tolower(c); }); + if (token == "trace") { + level = LogLevel::Trace; + } else if (token == "debug") { + level = LogLevel::Debug; + } else if (token == "info") { + level = LogLevel::Info; + } else if (token == "warning" || token == "warn") { + level = LogLevel::Warn; + } else if (token == "error" || token == "err") { + level = LogLevel::Err; + } else if (token == "critical" || token == "crit") { + level = LogLevel::Critical; + } else if (token == "off") { + level = LogLevel::Off; + } else { + in.setstate(std::ios::failbit); + } + return in; +} + +Logger::Logger() : spdLogger("") { + loggerSetDefaults(); +} + +Logger& Logger::getInstance() { + static Logger instance; + return instance; +} + +void Logger::setLevel(enum LogLevel level) { + spdLogger.set_level(static_cast(level)); +} + +void Logger::setup(std::string name, bool logToStdout, std::filesystem::path logDir) { + namespace fs = std::filesystem; + + std::vector sinks; + if (logToStdout) { + sinks.push_back(std::make_shared()); + } + + if (!logDir.empty()) { + if (!fs::exists(logDir)) { + throw std::runtime_error("Log directory doesn't exist"); + } + + if (!fs::is_directory(logDir)) { + throw std::runtime_error("Log directory is not a directory"); + } + + if (access(logDir.c_str(), R_OK | W_OK) != 0) { + throw std::runtime_error("Couldn't access log directory for reading and writing"); + } + + fs::path logFile{logDir / (name + ".log")}; + sinks.push_back(std::make_shared(logFile, max_size, max_files)); + } + + setLogger(spdlog::logger(name, sinks.begin(), sinks.end())); +} + +void Logger::vlogf(enum LogLevel level, const char* format, va_list args) { + if (!spdLogger.should_log(static_cast(level))) { + return; + } + + const int staticBufSize{512}; + static char staticBuf[staticBufSize]{}; + + static va_list argsCopy; + va_copy(argsCopy, args); + + int resultSize{vsnprintf(staticBuf, staticBufSize, format, args)}; + if (resultSize <= staticBufSize) { + logLine(level, staticBuf, resultSize); + return; + } + + char buf[resultSize]{}; + vsnprintf(buf, resultSize, format, argsCopy); + + logLine(level, buf, resultSize); +} + +void Logger::setLogger(spdlog::logger logger) { + spdLogger = std::move(logger); + loggerSetDefaults(); +} + +void Logger::loggerSetDefaults() { + spdLogger.set_level(spdlog::level::off); + spdLogger.set_pattern("%Y-%m-%d %H:%M:%S.%e [%t] %^%l%$ [%n] %v"); +} + +void Logger::logLine(enum LogLevel level, const char* buf, size_t len) { + if (buf == nullptr || len == 0) { + return; + } + + std::string str; + if (len > 1 && buf[len - 1] == '\n') { + str.assign(buf, len - 1); + } else { + str.assign(buf, len); + } + + log(level, "{}", str); +} + +} // namespace logging