diff --git a/.github/workflows/devshell.yml b/.github/workflows/devshell.yml index 5feac2d8b1..f4b709d892 100644 --- a/.github/workflows/devshell.yml +++ b/.github/workflows/devshell.yml @@ -46,6 +46,7 @@ jobs: --enable-all-modules --disable-java --disable-java-modules + --enable-ebpf --with-python=3 `[ $CC = clang ] && echo '--enable-force-gnu99' || true` " @@ -58,6 +59,10 @@ jobs: gh_export COREFILES_DIR PYTHONUSERBASE CC SYSLOG_NG_INSTALL_DIR CONFIGURE_FLAGS CMAKE_FLAGS gh_path "${PYTHONUSERBASE}" + - name: Install BPF tools + run: | + apt-get install libbpf-dev bpftool clang + - name: autogen.sh if: matrix.build-tool == 'autotools' run: ./autogen.sh diff --git a/cmake/Modules/FindLIBBPF.cmake b/cmake/Modules/FindLIBBPF.cmake new file mode 100644 index 0000000000..caf0c619fa --- /dev/null +++ b/cmake/Modules/FindLIBBPF.cmake @@ -0,0 +1,38 @@ +############################################################################# +# Copyright (c) 2023 Attila Szakacs +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# As an additional exemption you are allowed to compile & link against the +# OpenSSL libraries as published by the OpenSSL project. See the file +# COPYING for details. +# +############################################################################# + +find_package(PkgConfig) +pkg_check_modules(LIBBPF QUIET libbpf>=1.1.0) + +if (LIBBPF_FOUND) + find_program(BPF_CC clang) + find_program(BPFTOOL bpftool) + + if ((NOT BPF_CC) OR (NOT BPFTOOL)) + set(LIBBPF_FOUND FALSE) + else () + execute_process(COMMAND ${BPF_CC} -print-file-name=include OUTPUT_VARIABLE CLANG_INCLUDES OUTPUT_STRIP_TRAILING_WHITESPACE) + pkg_get_variable(LIBBPF_INCLUDE_DIRS_RAW libbpf includedir) + set(BPF_CFLAGS -nostdinc -isystem ${CLANG_INCLUDES} -target bpf -I${CMAKE_CURRENT_BINARY_DIR} -I${LIBBPF_INCLUDE_DIRS_RAW} -fPIC -O2 -g) + endif () +endif () diff --git a/configure.ac b/configure.ac index 147b00d1ca..90d26d667f 100644 --- a/configure.ac +++ b/configure.ac @@ -247,6 +247,10 @@ AC_ARG_ENABLE(linux-caps, [ --enable-linux-caps Enable support for managing Linux capabilities (default: auto)] ,,enable_linux_caps="auto") +AC_ARG_ENABLE(ebpf, + [ --enable-ebpf Enable support for loading of eBPF programs (default: no)] + ,,enable_ebpf="no") + AC_ARG_ENABLE(gcov, [ --enable-gcov Enable coverage profiling (default: no)] ,,enable_gcov="no") @@ -1760,6 +1764,28 @@ if test "x$enable_systemd" = "xyes"; then fi fi +if test "x$enable_ebpf" = "xyes"; then + AC_PATH_PROG(BPFTOOL, bpftool) + AC_SUBST(BPFTOOL) + + AC_PATH_PROG(BPF_CC, [clang]) + AC_SUBST(BPF_CC) + + if test "x$BPFTOOL" = "x"; then + AC_MSG_ERROR([Detecting bpftool for eBPF support failed]) + elif test "x$BPF_CC" = "x"; then + AC_MSG_ERROR([Detecting clang for eBPF support failed]) + fi + + PKG_CHECK_MODULES(LIBBPF, libbpf >= "1.1.0") + + # we are using a distinct C compiler for compiling BPF code and + # pkg-config removes builtin paths from its output. Reference it + # directly. + + LIBBPF_INCLUDE=`$PKG_CONFIG --variable includedir libbpf` +fi + dnl *************************************************************************** dnl check if we have timezone variable in dnl *************************************************************************** @@ -1943,6 +1969,7 @@ AC_DEFINE_UNQUOTED(ENABLE_SPOOF_SOURCE, `enable_value $enable_spoof_source`, [En AC_DEFINE_UNQUOTED(ENABLE_IPV6, `enable_value $enable_ipv6`, [Enable IPv6 support]) AC_DEFINE_UNQUOTED(ENABLE_TCP_WRAPPER, `enable_value $enable_tcp_wrapper`, [Enable TCP wrapper support]) AC_DEFINE_UNQUOTED(ENABLE_LINUX_CAPS, `enable_value $enable_linux_caps`, [Enable Linux capability management support]) +AC_DEFINE_UNQUOTED(ENABLE_EBPF, `enable_value $enable_ebpf`, [Enable Linux eBPF support]) AC_DEFINE_UNQUOTED(ENABLE_ENV_WRAPPER, `enable_value $enable_env_wrapper`, [Enable environment wrapper support]) AC_DEFINE_UNQUOTED(ENABLE_SYSTEMD, `enable_value $enable_systemd`, [Enable systemd support]) AC_DEFINE_UNQUOTED(ENABLE_KAFKA, `enable_value $enable_kafka`, [Enable kafka support]) @@ -1956,6 +1983,7 @@ AM_CONDITIONAL(ENABLE_SYSTEMD_UNIT_INSTALL, [test "$systemdsystemunitdir" != ""] AM_CONDITIONAL(ENABLE_SQL, [test "$enable_sql" = "yes"]) AM_CONDITIONAL(ENABLE_SUN_STREAMS, [test "$enable_sun_streams" = "yes"]) AM_CONDITIONAL(ENABLE_OPENBSD_SYSTEM_SOURCE, [test "$enable_openbsd_system_source" = "yes"]) +AM_CONDITIONAL(ENABLE_EBPF, [test "$enable_ebpf" = "yes"]) AM_CONDITIONAL(ENABLE_PACCT, [test "$enable_pacct" = "yes"]) AM_CONDITIONAL(ENABLE_MONGODB, [test "$enable_mongodb" = "yes"]) AM_CONDITIONAL(ENABLE_SMTP, [test "$enable_smtp" = "yes"]) @@ -2042,6 +2070,7 @@ AC_SUBST(JSON_CFLAGS) AC_SUBST(JSON_DEPENDENCY) AC_SUBST(IVYKIS_SUBDIRS) AC_SUBST(RESOLV_LIBS) +AC_SUBST(LIBBPF_INCLUDE) AC_SUBST(CURRDATE) AC_SUBST(RELEASE_TAG) @@ -2122,4 +2151,5 @@ echo " Python modules : ${enable_python_modules:=no}" echo " Java : ${enable_java:=no}" echo " Java modules : ${enable_java_modules:=no}" echo " afsnmp module : ${enable_afsnmp:=no}" +echo " eBPF module : ${enable_ebpf:=no}" echo " native bindings : ${enable_native:=no}" diff --git a/dbld/packages.manifest b/dbld/packages.manifest index 6457f7ff5d..de49a3ead7 100644 --- a/dbld/packages.manifest +++ b/dbld/packages.manifest @@ -40,6 +40,13 @@ make [centos, fedora] rpm-build [centos, fedora] which [fedora] +############################################################################# +# eBPF related tools +############################################################################# +clang [tarball] +libbpf-dev [tarball] +bpftool [tarball] + ############################################################################# # Tarball related tools ############################################################################# diff --git a/lib/signal-slot-connector/signal-slot-connector.c b/lib/signal-slot-connector/signal-slot-connector.c index a16e3430a0..f148a09759 100644 --- a/lib/signal-slot-connector/signal-slot-connector.c +++ b/lib/signal-slot-connector/signal-slot-connector.c @@ -113,7 +113,7 @@ signal_slot_connect(SignalSlotConnector *self, Signal signal, Slot slot, gpointe if (_slot_lookup(slots, slot, object)) { - msg_debug("SignalSlotConnector::connect", + msg_trace("SignalSlotConnector::connect", evt_tag_printf("already_connected", "connect(connector=%p,signal=%s,slot=%p, object=%p)", self, signal, slot, object)); @@ -127,7 +127,7 @@ signal_slot_connect(SignalSlotConnector *self, Signal signal, Slot slot, gpointe g_hash_table_insert(self->connections, (gpointer)signal, new_slots); } - msg_debug("SignalSlotConnector::connect", + msg_trace("SignalSlotConnector::connect", evt_tag_printf("new connection registered", "connect(connector=%p,signal=%s,slot=%p,object=%p)", self, signal, slot, object)); @@ -156,7 +156,7 @@ signal_slot_disconnect(SignalSlotConnector *self, Signal signal, Slot slot, gpoi if (!slots) goto exit_; - msg_debug("SignalSlotConnector::disconnect", + msg_trace("SignalSlotConnector::disconnect", evt_tag_printf("connector", "%p", self), evt_tag_str("signal", signal), evt_tag_printf("slot", "%p", slot), @@ -171,7 +171,7 @@ signal_slot_disconnect(SignalSlotConnector *self, Signal signal, Slot slot, gpoi GList *slotfunctor_node = g_list_find_custom(slots, &slotfunctor, _slot_functor_cmp); if (!slotfunctor_node) { - msg_debug("SignalSlotConnector::disconnect slot object not found", + msg_trace("SignalSlotConnector::disconnect slot object not found", evt_tag_printf("connector", "%p", self), evt_tag_str("signal", signal), evt_tag_printf("slot", "%p", slot), @@ -184,7 +184,7 @@ signal_slot_disconnect(SignalSlotConnector *self, Signal signal, Slot slot, gpoi if (!new_slots) { g_hash_table_remove(self->connections, signal); - msg_debug("SignalSlotConnector::disconnect last slot is disconnected, unregister signal", + msg_trace("SignalSlotConnector::disconnect last slot is disconnected, unregister signal", evt_tag_printf("connector", "%p", self), evt_tag_str("signal", signal), evt_tag_printf("slot", "%p", slot), @@ -217,7 +217,7 @@ signal_slot_emit(SignalSlotConnector *self, Signal signal, gpointer user_data) { g_assert(signal != NULL); - msg_debug("SignalSlotConnector::emit", + msg_trace("SignalSlotConnector::emit", evt_tag_printf("connector", "%p", self), evt_tag_str("signal", signal), evt_tag_printf("user_data", "%p", user_data)); @@ -226,7 +226,7 @@ signal_slot_emit(SignalSlotConnector *self, Signal signal, gpointer user_data) if (!slots) { - msg_debug("SignalSlotConnector: unregistered signal emitted", + msg_trace("SignalSlotConnector: unregistered signal emitted", evt_tag_printf("connector", "%p", self), evt_tag_str("signal", signal)); return; diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index ee0592803a..37e405d624 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -19,6 +19,7 @@ add_subdirectory(csvparser) add_subdirectory(correlation) add_subdirectory(diskq) add_subdirectory(examples) +add_subdirectory(ebpf) add_subdirectory(geoip2) add_subdirectory(getent) add_subdirectory(graphite) diff --git a/modules/Makefile.am b/modules/Makefile.am index 0e3b6d4f6a..3d1a630d35 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -20,6 +20,7 @@ include modules/timestamp/Makefile.am include modules/correlation/Makefile.am include modules/diskq/Makefile.am include modules/examples/Makefile.am +include modules/ebpf/Makefile.am include modules/geoip2/Makefile.am include modules/getent/Makefile.am include modules/graphite/Makefile.am diff --git a/modules/afsocket/CMakeLists.txt b/modules/afsocket/CMakeLists.txt index 7b7653edea..f66cf50b62 100644 --- a/modules/afsocket/CMakeLists.txt +++ b/modules/afsocket/CMakeLists.txt @@ -5,6 +5,7 @@ set(AFSOCKET_SOURCES afsocket-source.h afsocket-dest.c afsocket-dest.h + afsocket-signals.h socket-options.c socket-options.h transport-mapper.c diff --git a/modules/afsocket/Makefile.am b/modules/afsocket/Makefile.am index 6ca568f33e..a5b5b6ae0c 100644 --- a/modules/afsocket/Makefile.am +++ b/modules/afsocket/Makefile.am @@ -5,6 +5,7 @@ modules_afsocket_libafsocket_la_SOURCES = \ modules/afsocket/afsocket-source.h \ modules/afsocket/afsocket-dest.c \ modules/afsocket/afsocket-dest.h \ + modules/afsocket/afsocket-signals.h \ modules/afsocket/socket-options.c \ modules/afsocket/socket-options.h \ modules/afsocket/transport-mapper.c \ diff --git a/modules/afsocket/afsocket-signals.h b/modules/afsocket/afsocket-signals.h new file mode 100644 index 0000000000..6aa862c49f --- /dev/null +++ b/modules/afsocket/afsocket-signals.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Balazs Scheidler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#ifndef AFSOCKET_SIGNALS_H_INCLUDED +#define AFSOCKET_SIGNALS_H_INCLUDED + +#include "signal-slot-connector/signal-slot-connector.h" + +typedef struct _AFSocketSetupSocketSignalData +{ + gint sock; + /* initialized to FALSE by the caller, must be set to TRUE in order to + * fail the initialization */ + gboolean failure; +} AFSocketSetupSocketSignalData; + +#define signal_afsocket_setup_socket SIGNAL(afsocket, setup_socket, AFSocketSetupSocketSignalData *) + +#endif diff --git a/modules/afsocket/afsocket-source.c b/modules/afsocket/afsocket-source.c index 6ba4227569..82a666d417 100644 --- a/modules/afsocket/afsocket-source.c +++ b/modules/afsocket/afsocket-source.c @@ -30,6 +30,7 @@ #include "mainloop.h" #include "poll-fd-events.h" #include "timeutils/misc.h" +#include "afsocket-signals.h" #include #include @@ -838,6 +839,20 @@ _finalize_init(gpointer arg) return TRUE; } +static gboolean +afsocket_sd_open_socket(AFSocketSourceDriver *self, gint *sock) +{ + if (!transport_mapper_open_socket(self->transport_mapper, self->socket_options, self->bind_addr, + self->bind_addr, AFSOCKET_DIR_RECV, sock)) + return FALSE; + + AFSocketSetupSocketSignalData signal_data = {0}; + + signal_data.sock = *sock; + EMIT(self->super.super.super.signal_slot_connector, signal_afsocket_setup_socket, &signal_data); + return !signal_data.failure; +} + static gboolean _sd_open_stream(AFSocketSourceDriver *self) { @@ -856,9 +871,7 @@ _sd_open_stream(AFSocketSourceDriver *self) { if (!afsocket_sd_acquire_socket(self, &sock)) return self->super.super.optional; - if (sock == -1 - && !transport_mapper_open_socket(self->transport_mapper, self->socket_options, self->bind_addr, - self->bind_addr, AFSOCKET_DIR_RECV, &sock)) + if (sock == -1 && !afsocket_sd_open_socket(self, &sock)) return self->super.super.optional; } self->fd = sock; @@ -873,9 +886,7 @@ _sd_open_dgram(AFSocketSourceDriver *self) { if (!afsocket_sd_acquire_socket(self, &sock)) return self->super.super.optional; - if (sock == -1 - && !transport_mapper_open_socket(self->transport_mapper, self->socket_options, self->bind_addr, - self->bind_addr, AFSOCKET_DIR_RECV, &sock)) + if (sock == -1 && !afsocket_sd_open_socket(self, &sock)) return self->super.super.optional; } self->fd = -1; diff --git a/modules/ebpf/CMakeLists.txt b/modules/ebpf/CMakeLists.txt new file mode 100644 index 0000000000..b26c0fcba2 --- /dev/null +++ b/modules/ebpf/CMakeLists.txt @@ -0,0 +1,36 @@ +find_package(LIBBPF) + +module_switch(ENABLE_EBPF "Enable ebpf module (requires ebpf toolchain)" LIBBPF_FOUND) + +if (NOT ENABLE_EBPF) + return() +endif() + +add_custom_command(OUTPUT vmlinux.h + COMMAND ${BPFTOOL} btf dump file /sys/kernel/btf/vmlinux format c >vmlinux.h) + +add_custom_command(OUTPUT random.skel.c + COMMAND ${BPFTOOL} gen skeleton random.kern.o > random.skel.c + DEPENDS random.kern.o) + +add_custom_command(OUTPUT random.kern.o + COMMAND ${BPF_CC} ${BPF_CFLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/random.kern.c -o random.kern.o + DEPENDS random.kern.c vmlinux.h) + +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/random.skel.c PROPERTIES COMPILE_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-function") + +set(EBPF_SOURCES + ebpf-parser.h + ebpf-reuseport.h + ebpf-reuseport.c + ebpf-plugin.c + ebpf-parser.c + random.skel.c +) + +add_module( + TARGET ebpf + GRAMMAR ebpf-grammar + INCLUDES ${PROJECT_SOURCE_DIR} + SOURCES ${EBPF_SOURCES} +) diff --git a/modules/ebpf/Makefile.am b/modules/ebpf/Makefile.am new file mode 100644 index 0000000000..e8be7552f0 --- /dev/null +++ b/modules/ebpf/Makefile.am @@ -0,0 +1,52 @@ +if ENABLE_EBPF + +module_LTLIBRARIES += modules/ebpf/libebpf.la +modules_ebpf_libebpf_la_SOURCES = \ + modules/ebpf/ebpf-grammar.y \ + modules/ebpf/ebpf-parser.c \ + modules/ebpf/ebpf-parser.h \ + modules/ebpf/ebpf-plugin.c \ + modules/ebpf/ebpf-reuseport.c \ + modules/ebpf/ebpf-reuseport.h + +modules_ebpf_libebpf_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/modules/ebpf -I$(top_builddir)/modules/ebpf +modules_ebpf_libebpf_la_LIBADD = $(MODULE_DEPS_LIBS) $(LIBBPF_LIBS) +modules_ebpf_libebpf_la_LDFLAGS = $(MODULE_LDFLAGS) +modules_ebpf_libebpf_la_DEPENDENCIES = $(MODULE_DEPS_LIBS) + +modules/ebpf modules/ebpf/ mod-ebpf: modules/ebpf/libebpf.la + +BPF_CFLAGS=-nostdinc -isystem `$(BPF_CC) -print-file-name=include` \ + -target bpf \ + -Imodules/ebpf -I$(LIBBPF_INCLUDE) \ + -fPIC -O2 -g + +%.kern.o: %.kern.c modules/ebpf/vmlinux.h + $(BPF_CC) $(BPF_CFLAGS) -c $< -o $@ + +%.skel.c: %.kern.o + $(BPFTOOL) gen skeleton $< > $@ + +modules/ebpf/vmlinux.h: + $(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c >$@ + +CLEANFILES += modules/ebpf/random.skel.c modules/ebpf/vmlinux.h + +BUILT_SOURCES += modules/ebpf/random.skel.c + + +endif + +BUILT_SOURCES += \ + modules/ebpf/ebpf-grammar.y \ + modules/ebpf/ebpf-grammar.c \ + modules/ebpf/ebpf-grammar.h + +EXTRA_DIST += \ + modules/ebpf/ebpf-grammar.ym \ + modules/ebpf/CMakeLists.txt \ + modules/ebpf/random.kern.c + + + +.PHONY: modules/ebpf/ mod-ebpf diff --git a/modules/ebpf/ebpf-grammar.ym b/modules/ebpf/ebpf-grammar.ym new file mode 100644 index 0000000000..32b652fa91 --- /dev/null +++ b/modules/ebpf/ebpf-grammar.ym @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 Balazs Scheidler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +%code top { +#include "driver.h" +#include "cfg-lexer.h" +#include "ebpf-parser.h" +} + +%code { + +#pragma GCC diagnostic ignored "-Wswitch-default" + +#include "ebpf-reuseport.h" + +LogDriverPlugin *last_reuseport; + +} + +%define api.prefix {ebpf_} + +/* this parameter is needed in order to instruct bison to use a complete + * argument list for yylex/yyerror */ + +%lex-param {CfgLexer *lexer} +%parse-param {CfgLexer *lexer} +%parse-param {LogDriverPlugin **instance} +%parse-param {gpointer arg} + +/* INCLUDE_DECLS */ + +%token KW_EBPF +%token KW_REUSEPORT +%token KW_SOCKETS + +%type ebpf_program + +%% + +start + : LL_CONTEXT_INNER_SRC KW_EBPF '(' ebpf_program ')' { *instance = $4; YYACCEPT; } + ; + +ebpf_program + : KW_REUSEPORT + { last_reuseport = ebpf_reuseport_new(); } + '(' ebpf_reuseport_options ')' { $$ = last_reuseport; } + ; + +ebpf_reuseport_options + : ebpf_reuseport_option ebpf_reuseport_options + | + ; + +ebpf_reuseport_option + : KW_SOCKETS '(' positive_integer ')' { ebpf_reuseport_set_sockets(last_reuseport, $3); } + ; + +/* INCLUDE_RULES */ + +%% diff --git a/modules/ebpf/ebpf-parser.c b/modules/ebpf/ebpf-parser.c new file mode 100644 index 0000000000..71529ec975 --- /dev/null +++ b/modules/ebpf/ebpf-parser.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Balazs Scheidler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "cfg-parser.h" +#include "driver.h" +#include "ebpf-grammar.h" + +extern int ebpf_debug; + +int ebpf_parse(CfgLexer *lexer, LogDriverPlugin **instance, gpointer arg); + +static CfgLexerKeyword ebpf_keywords[] = +{ + { "ebpf", KW_EBPF }, + { "reuseport", KW_REUSEPORT }, + { "sockets", KW_SOCKETS }, + { NULL } +}; + +CfgParser ebpf_parser = +{ +#if SYSLOG_NG_ENABLE_DEBUG + .debug_flag = &ebpf_debug, +#endif + .name = "ebpf", + .keywords = ebpf_keywords, + .parse = (gint (*)(CfgLexer *, gpointer *, gpointer)) ebpf_parse, + .cleanup = (void (*)(gpointer)) log_driver_plugin_free, +}; + +CFG_PARSER_IMPLEMENT_LEXER_BINDING(ebpf_, EBPFPROGS_, LogDriverPlugin **) diff --git a/modules/ebpf/ebpf-parser.h b/modules/ebpf/ebpf-parser.h new file mode 100644 index 0000000000..a013a5fa7e --- /dev/null +++ b/modules/ebpf/ebpf-parser.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 Balazs Scheidler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#ifndef EBPF_PROGRAMS_PARSER_H_INCLUDED +#define EBPF_PROGRAMS_PARSER_H_INCLUDED + +#include "cfg-parser.h" +#include "driver.h" + +extern CfgParser ebpf_parser; + +CFG_PARSER_DECLARE_LEXER_BINDING(ebpf_, EBPFPROGS_, LogDriverPlugin **); + +#endif diff --git a/modules/ebpf/ebpf-plugin.c b/modules/ebpf/ebpf-plugin.c new file mode 100644 index 0000000000..d50a210866 --- /dev/null +++ b/modules/ebpf/ebpf-plugin.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 Balazs Scheidler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "cfg-parser.h" +#include "plugin.h" +#include "plugin-types.h" + +extern CfgParser ebpf_parser; + +static Plugin ebpf_plugins[] = +{ + { + .type = LL_CONTEXT_INNER_SRC, + .name = "ebpf", + .parser = &ebpf_parser, + }, +}; + +gboolean +ebpf_module_init(PluginContext *context, CfgArgs *args) +{ + plugin_register(context, ebpf_plugins, G_N_ELEMENTS(ebpf_plugins)); + return TRUE; +} + +const ModuleInfo module_info = +{ + .canonical_name = "ebpf", + .version = SYSLOG_NG_VERSION, + .description = "EBPF Programs plugin", + .core_revision = SYSLOG_NG_SOURCE_REVISION, + .plugins = ebpf_plugins, + .plugins_len = G_N_ELEMENTS(ebpf_plugins), +}; diff --git a/modules/ebpf/ebpf-reuseport.c b/modules/ebpf/ebpf-reuseport.c new file mode 100644 index 0000000000..0b6d7a6242 --- /dev/null +++ b/modules/ebpf/ebpf-reuseport.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2023 Balazs Scheidler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ +#include "ebpf-reuseport.h" +#include "modules/afsocket/afsocket-signals.h" + +typedef struct _EBPFReusePort +{ + LogDriverPlugin super; + struct random_kern *random; + gint number_of_sockets; +} EBPFReusePort; + +#include "random.skel.c" + +void +ebpf_reuseport_set_sockets(LogDriverPlugin *s, gint number_of_sockets) +{ + EBPFReusePort *self = (EBPFReusePort *) s; + self->number_of_sockets = number_of_sockets; +} + +static void +_slot_setup_socket(EBPFReusePort *self, AFSocketSetupSocketSignalData *data) +{ + int bpf_fd = bpf_program__fd(self->random->progs.random_choice); + if (bpf_fd < 0) + { + msg_error("ebpf-reuseport(): setsockopt(SO_ATTACH_REUSEPORT_EBPF) returned error", + evt_tag_errno("error", errno)); + goto error; + } + + if (setsockopt(data->sock, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &bpf_fd, sizeof(bpf_fd)) < 0) + { + msg_error("ebpf-reuseport(): setsockopt(SO_ATTACH_REUSEPORT_EBPF) returned error", + evt_tag_errno("error", errno)); + goto error; + } + + msg_debug("ebpf-reuseport(): eBPF reuseport group randomizer applied", + evt_tag_int("sock", data->sock)); + return; +error: + data->failure = TRUE; +} + +static gboolean +_attach(LogDriverPlugin *s, LogDriver *driver) +{ + EBPFReusePort *self = (EBPFReusePort *)s; + + self->random = random_kern__open_and_load(); + if (!self->random) + { + msg_error("ebpf-reuseport(): Unable to load eBPF program to the kernel"); + return FALSE; + } + self->random->bss->number_of_sockets = self->number_of_sockets; + + SignalSlotConnector *ssc = driver->super.signal_slot_connector; + CONNECT(ssc, signal_afsocket_setup_socket, _slot_setup_socket, self); + + return TRUE; +} + +static void +_free(LogDriverPlugin *s) +{ + EBPFReusePort *self = (EBPFReusePort *) s; + + if (self->random) + random_kern__destroy(self->random); + log_driver_plugin_free_method(s); +} + +LogDriverPlugin * +ebpf_reuseport_new(void) +{ + EBPFReusePort *self = g_new0(EBPFReusePort, 1); + log_driver_plugin_init_instance(&self->super, "ebpf-reuseport"); + + self->super.attach = _attach; + self->super.free_fn = _free; + self->number_of_sockets = 0; + + return &self->super; +} diff --git a/modules/ebpf/ebpf-reuseport.h b/modules/ebpf/ebpf-reuseport.h new file mode 100644 index 0000000000..6c114ba18b --- /dev/null +++ b/modules/ebpf/ebpf-reuseport.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Balazs Scheidler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#ifndef EBPF_REUSEPORT_H_INCLUDED +#define EBPF_REUSEPORT_H_INCLUDED + +#include "driver.h" + +void ebpf_reuseport_set_sockets(LogDriverPlugin *s, gint number_of_sockets); +LogDriverPlugin *ebpf_reuseport_new(void); + +#endif diff --git a/modules/ebpf/random.kern.c b/modules/ebpf/random.kern.c new file mode 100644 index 0000000000..fdcb12e781 --- /dev/null +++ b/modules/ebpf/random.kern.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Balazs Scheidler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "vmlinux.h" +#include + +int number_of_sockets; + +SEC("socket") +int random_choice(struct __sk_buff *skb) +{ + if (number_of_sockets == 0) + return -1; + + return bpf_get_prandom_u32() % number_of_sockets; +} diff --git a/news/feature-4365.md b/news/feature-4365.md new file mode 100644 index 0000000000..bd2286411e --- /dev/null +++ b/news/feature-4365.md @@ -0,0 +1,23 @@ +`ebpf()` plugin and udp packet balancer: a new ebpf() plugin was added which +leverages the kernel's eBPF infrastructure to improve performance and +scalability of syslog-ng. The first ebpf based solution improves +performance when a single (or very few) senders generate most of the inbound +UDP traffic that syslog-ng needs to process. Normally, the kernel +distributes load between so-reuseport sockets by keeping each flow (e.g. +same source/dest ip/port) in its dedicated receiver. This fails to balance +the sockets properly if only a few senders are responsible for most of the +load. ebpf(reuseport()) will replace the original kernel algorithm with an +alternative that changes the algorithm, so individual packets will be +assigned to one of the sockets randomly, thereby producing a more uniform +load. + +Example: + +source s_udp { + udp(so-reuseport(1) port(2000) persist-name("udp1") + ebpf(reuseport(sockets(4))) + ); + udp(so-reuseport(1) port(2000) persist-name("udp2")); + udp(so-reuseport(1) port(2000) persist-name("udp3")); + udp(so-reuseport(1) port(2000) persist-name("udp4")); +}; diff --git a/tests/copyright/policy b/tests/copyright/policy index 48aa1dbfbc..1a26ac8d95 100644 --- a/tests/copyright/policy +++ b/tests/copyright/policy @@ -67,6 +67,7 @@ modules/python-modules/requirements\.lock ########################################################################### LGPLv2.1+_SSL,non-balabit +cmake/Modules/FindLIBBPF\.cmake lib/timeutils/zonecache.[ch]$ modules/secure-logging modules/secure-logging/tests @@ -91,6 +92,7 @@ libtest/mock-logpipe\.[ch] lib/generic-number\.[ch] lib/tests/test_generic_number\.c syslog-ng-ctl/commands/log-level.[ch] +modules/afsocket/afsocket-signals.h modules/python-modules/syslogng/confgen\.py modules/python-modules/syslogng/dest\.py modules/python-modules/syslogng/logger\.py @@ -161,6 +163,7 @@ scl/cee/.*\.conf$ scl/mariadb/.*\.conf$ scl/python/python-modules\.conf$ modules/python-modules/syslogng/modules/kubernetes/.* +modules/ebpf/.* modules/python-modules/syslogng/modules/example/.* modules/python-modules/setup\.py scripts/build-python-venv\.sh