From 1a6a9b61390ce05c72afa19ba8ffda9d8752bfb6 Mon Sep 17 00:00:00 2001 From: fruffy Date: Thu, 19 Dec 2024 16:10:52 +0100 Subject: [PATCH] Use bpftool instead of iproute2 to load eBPF and XDP maps. Signed-off-by: fruffy --- backends/ebpf/CMakeLists.txt | 8 +- backends/ebpf/build_libbpf | 27 ++++--- backends/ebpf/ebpfProgram.cpp | 2 +- backends/ebpf/run-ebpf-test.py | 4 +- backends/ebpf/runtime/.gitignore | 1 + backends/ebpf/runtime/contrib/.gitignore | 1 + backends/ebpf/runtime/ebpf_kernel.h | 55 ++++++------- backends/ebpf/runtime/kernel.mk | 2 +- backends/ebpf/targets/ebpfenv.py | 6 +- backends/ebpf/targets/kernel_target.py | 77 +++++++++++++++---- cmake/Linters.cmake | 2 +- .../extern_modules/extern-conntrack-ebpf.c | 2 +- 12 files changed, 119 insertions(+), 68 deletions(-) create mode 100644 backends/ebpf/runtime/.gitignore diff --git a/backends/ebpf/CMakeLists.txt b/backends/ebpf/CMakeLists.txt index 3d26c338707..4d48a3c4c6e 100644 --- a/backends/ebpf/CMakeLists.txt +++ b/backends/ebpf/CMakeLists.txt @@ -21,9 +21,9 @@ if(NOT APPLE) set(FETCHCONTENT_QUIET OFF) fetchcontent_declare( bpfrepo - URL https://github.com/libbpf/libbpf/archive/refs/tags/v1.4.1.tar.gz - URL_HASH SHA256=cc01a3a05d25e5978c20be7656f14eb8b6fcb120bb1c7e8041e497814fc273cb - SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/runtime/contrib/libbpf + URL https://github.com/libbpf/bpftool/releases/download/v7.5.0/bpftool-libbpf-v7.5.0-sources.tar.gz + # URL_HASH SHA256=cc01a3a05d25e5978c20be7656f14eb8b6fcb120bb1c7e8041e497814fc273cb + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/runtime/contrib/bpftool USES_TERMINAL_DOWNLOAD TRUE GIT_PROGRESS TRUE DOWNLOAD_EXTRACT_TIMESTAMP TRUE @@ -31,7 +31,7 @@ if(NOT APPLE) fetchcontent_makeavailable(bpfrepo) set(FETCHCONTENT_QUIET ${FETCHCONTENT_QUIET_PREV}) # Check if we have already built the libbpf library. - find_library(LIBBPF NAMES bpf HINTS "${CMAKE_CURRENT_SOURCE_DIR}/runtime/usr/lib64/") + find_library(LIBBPF NAMES bpf HINTS "${CMAKE_CURRENT_SOURCE_DIR}/runtime/install/libbpf/") if (NOT LIBBPF) message("Building libbpf...") execute_process( diff --git a/backends/ebpf/build_libbpf b/backends/ebpf/build_libbpf index 8ff46ad5987..f3541770338 100755 --- a/backends/ebpf/build_libbpf +++ b/backends/ebpf/build_libbpf @@ -10,7 +10,7 @@ # 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. -""" This programs builds a libbpf static library and places it in the runtime +""" This programs builds a bpftool binary and the libbpf static library and places it in the runtime folder. The library and its headers are required by the kernel target. """ @@ -31,21 +31,24 @@ def main() -> int: level=logging.WARN, filemode="w", ) - libbpf_dir = FILE_DIR.joinpath("runtime/contrib/libbpf") - libbpf_src_dir = libbpf_dir.joinpath("src") - libbpf_build_dir = libbpf_src_dir.joinpath("build") - libbpf_target_dir = FILE_DIR.joinpath("runtime") - # Create the libbpf build directory - testutils.check_and_create_dir(libbpf_build_dir) - # Build libbpf - mk_cmd = f"make -C {libbpf_src_dir} install " + bpftool_dir = FILE_DIR.joinpath("runtime/contrib/bpftool") + bpftool_src_dir = bpftool_dir.joinpath("src") + bpftool_build_dir = bpftool_src_dir.joinpath("build") + bpftool_target_dir = FILE_DIR.joinpath("runtime/install/") + bpftool_target_dir.mkdir(parents=True, exist_ok=True) + # Create the bpftool build directory + testutils.check_and_create_dir(bpftool_build_dir) + # Build bpftool + mk_cmd = f"make -C {bpftool_src_dir} install " + mk_cmd += f"OUTPUT={bpftool_target_dir}/ " mk_cmd += "BUILD_STATIC_ONLY=y " - mk_cmd += f"OBJDIR={libbpf_build_dir} " - mk_cmd += f"DESTDIR={libbpf_target_dir} " + mk_cmd += f"OBJDIR={bpftool_build_dir} " + mk_cmd += f"DESTDIR={bpftool_target_dir} " mk_cmd += "EXTRA_CFLAGS=-fPIE" + print(mk_cmd) result = testutils.exec_process(args=mk_cmd) if result.returncode != testutils.SUCCESS: - testutils.log.error("Could not build libbpf") + testutils.log.error("Could not build bpftool") return result.returncode diff --git a/backends/ebpf/ebpfProgram.cpp b/backends/ebpf/ebpfProgram.cpp index e57a6c55a14..c8381e3d2d9 100644 --- a/backends/ebpf/ebpfProgram.cpp +++ b/backends/ebpf/ebpfProgram.cpp @@ -102,7 +102,7 @@ void EBPFProgram::emitC(CodeBuilder *builder, const std::filesystem::path &heade if (model.arch == ModelArchitecture::XdpSwitch) builder->target->emitCodeSection(builder, "xdp"_cs); else - builder->target->emitCodeSection(builder, "prog"_cs); + builder->target->emitCodeSection(builder, "tc"_cs); builder->emitIndent(); builder->target->emitMain(builder, functionName, model.CPacketName.toString()); builder->blockStart(); diff --git a/backends/ebpf/run-ebpf-test.py b/backends/ebpf/run-ebpf-test.py index 6757042a4bd..f69b9aaf851 100755 --- a/backends/ebpf/run-ebpf-test.py +++ b/backends/ebpf/run-ebpf-test.py @@ -123,9 +123,9 @@ def __init__(self): self.replace = False # Replace previous outputs. self.target = "test" # The name of the target compiler. # Actual location of the test framework. - self.testdir = str(FILE_DIR) + self.testdir = FILE_DIR # The location of the eBPF runtime, some targets may overwrite this. - self.runtimedir = str(FILE_DIR.joinpath("runtime")) + self.runtimedir = FILE_DIR.joinpath("runtime") self.extern = "" # Path to C file with extern definition. diff --git a/backends/ebpf/runtime/.gitignore b/backends/ebpf/runtime/.gitignore new file mode 100644 index 00000000000..7c32f559819 --- /dev/null +++ b/backends/ebpf/runtime/.gitignore @@ -0,0 +1 @@ +install diff --git a/backends/ebpf/runtime/contrib/.gitignore b/backends/ebpf/runtime/contrib/.gitignore index cc3ea8dd440..d30b12ae8bf 100644 --- a/backends/ebpf/runtime/contrib/.gitignore +++ b/backends/ebpf/runtime/contrib/.gitignore @@ -1 +1,2 @@ libbpf +bpftool diff --git a/backends/ebpf/runtime/ebpf_kernel.h b/backends/ebpf/runtime/ebpf_kernel.h index 6813c0affca..4012795483a 100644 --- a/backends/ebpf/runtime/ebpf_kernel.h +++ b/backends/ebpf/runtime/ebpf_kernel.h @@ -23,7 +23,7 @@ limitations under the License. #include "ebpf_common.h" -#include // definitions for bpf_ntohs etc... +#include "install/libbpf/include/bpf/bpf_endian.h" // definitions for bpf_ntohs etc... #undef htonl #undef htons @@ -47,7 +47,7 @@ limitations under the License. #ifdef CONTROL_PLANE // BEGIN EBPF USER SPACE DEFINITIONS -#include // bpf_obj_get/pin, bpf_map_update_elem +#include "install/libbpf/include/bpf/bpf.h" // bpf_obj_get/pin, bpf_map_update_elem #define BPF_USER_MAP_UPDATE_ELEM(index, key, value, flags)\ bpf_map_update_elem(index, key, value, flags) @@ -56,30 +56,18 @@ limitations under the License. #else // BEGIN EBPF KERNEL DEFINITIONS -#include // TC_ACT_OK, TC_ACT_SHOT -#include "linux/bpf.h" // types, and general bpf definitions +// These files are provided by the system, not libbpf. +#include "contrib/bpftool/include/uapi/linux/bpf.h" // BPF_ANY, +#include "contrib/bpftool/include/uapi/linux/pkt_cls.h" // TC_ACT_OK, TC_ACT_SHOT // This file contains the definitions of all the kernel bpf essentials -#include - -/// A helper structure used by an eBPF C program -/// to describe map attributes for the elf_bpf loader -/// FIXME: We only need this because we are loading with iproute2 -struct bpf_elf_map { - __u32 type; - __u32 size_key; - __u32 size_value; - __u32 max_elem; - __u32 flags; - __u32 id; - __u32 pinning; - __u32 inner_id; - __u32 inner_idx; -}; +#include "install/libbpf/include/bpf/bpf_helpers.h" +#include "install/libbpf/include/bpf/btf.h" /// Simple descriptor which replaces the kernel sk_buff structure. #define SK_BUFF struct __sk_buff /// From iproute2, annotate table with BTF which allows to read types at runtime. +/// TODO: Do we need this with Ubuntu 24.04? #define BPF_ANNOTATE_KV_PAIR(name, type_key, type_val) \ struct ____btf_map_##name { \ type_key key; \ @@ -90,7 +78,22 @@ struct bpf_elf_map { ____btf_map_##name = {}; #define REGISTER_START() -#ifndef BTF +/// TODO: Do we need this with Ubuntu 24.04? +#if !defined(BTF) && (defined(__clang__) && (__clang_major__ < 12)) +/// A helper structure used by an eBPF C program +/// to describe map attributes for the elf_bpf loader +/// FIXME: We only need this because we are loading with iproute2. Remove with Ubuntu 24.04 +struct bpf_elf_map { + __u32 type; + __u32 size_key; + __u32 size_value; + __u32 max_elem; + __u32 flags; + __u32 id; + __u32 pinning; + __u32 inner_id; + __u32 inner_idx; +}; /// Note: pinning exports the table name globally, do not remove. #define REGISTER_TABLE(NAME, TYPE, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) \ struct bpf_elf_map SEC("maps") NAME = { \ @@ -139,7 +142,7 @@ struct { \ VALUE_TYPE *value; \ __uint(max_entries, MAX_ENTRIES); \ __uint(pinning, LIBBPF_PIN_BY_NAME); \ -} NAME SEC(".maps"); +} NAME SEC(MAPS_ELF_SEC); #define REGISTER_TABLE_FLAGS(NAME, TYPE, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES, FLAGS) \ struct { \ __uint(type, TYPE); \ @@ -148,14 +151,14 @@ struct { \ __uint(max_entries, MAX_ENTRIES); \ __uint(pinning, LIBBPF_PIN_BY_NAME); \ __uint(map_flags, FLAGS); \ -} NAME SEC(".maps"); +} NAME SEC(MAPS_ELF_SEC); #define REGISTER_TABLE_INNER(NAME, TYPE, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES, ID, INNER_IDX) \ struct NAME { \ __uint(type, TYPE); \ KEY_TYPE *key; \ VALUE_TYPE *value; \ __uint(max_entries, MAX_ENTRIES); \ -} NAME SEC(".maps"); +} NAME SEC(MAPS_ELF_SEC); #define REGISTER_TABLE_OUTER(NAME, TYPE, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES, INNER_ID, INNER_NAME) \ struct { \ __uint(type, TYPE); \ @@ -164,7 +167,7 @@ struct { \ __uint(max_entries, MAX_ENTRIES); \ __uint(pinning, LIBBPF_PIN_BY_NAME); \ __array(values, struct INNER_NAME); \ -} NAME SEC(".maps"); +} NAME SEC(MAPS_ELF_SEC); #define REGISTER_TABLE_NO_KEY_TYPE(NAME, TYPE, KEY_SIZE, VALUE_TYPE, MAX_ENTRIES) \ struct { \ __uint(type, TYPE); \ @@ -172,7 +175,7 @@ struct { \ VALUE_TYPE *value; \ __uint(max_entries, MAX_ENTRIES); \ __uint(pinning, LIBBPF_PIN_BY_NAME); \ -} NAME SEC(".maps"); +} NAME SEC(MAPS_ELF_SEC); #endif #define REGISTER_END() diff --git a/backends/ebpf/runtime/kernel.mk b/backends/ebpf/runtime/kernel.mk index dd205709947..edfe554e725 100644 --- a/backends/ebpf/runtime/kernel.mk +++ b/backends/ebpf/runtime/kernel.mk @@ -3,7 +3,7 @@ ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) # Argument for the CLANG compiler LLC ?= llc CLANG ?= clang -override INCLUDES+= -I$(ROOT_DIR) -I$(ROOT_DIR)usr/include/ -I$(ROOT_DIR)contrib/libbpf/include/uapi/ +override INCLUDES+= -I$(ROOT_DIR) override LIBS+= # Optimization flags to save space override CFLAGS+= -O2 -g -c -D__KERNEL__ -D__ASM_SYSREG_H \ diff --git a/backends/ebpf/targets/ebpfenv.py b/backends/ebpf/targets/ebpfenv.py index 69b04f9026e..21112e43e92 100644 --- a/backends/ebpf/targets/ebpfenv.py +++ b/backends/ebpf/targets/ebpfenv.py @@ -96,7 +96,7 @@ def ns_proc_append(self, proc: subprocess.Popen, cmd: str) -> int: def ns_proc_close(self, proc: subprocess.Popen, **extra_args: Any) -> int: """Close and actually run the process in the namespace. Returns the exit code.""" - testutils.log.info("Executing command: %s", proc) + testutils.log.info("Executing command: %s", proc.args) result = testutils.run_process(proc, timeout=testutils.TIMEOUT, **extra_args) if result.returncode != testutils.SUCCESS: testutils.log.error( @@ -109,7 +109,7 @@ def _configure_bridge(self, br_name: str) -> int: avoid ICMPv6 spam.""" # We do not care about failures here self.ns_exec(f"ip link set dev {br_name} up") - self.ns_exec(f"ip link set dev {br_name} mtu 9000") + self.ns_exec(f"ip link set dev {br_name} mtu 1500") # Prevent the broadcasting of ipv6 link discovery messages self.ns_exec("sysctl -w net.ipv6.conf.all.disable_ipv6=1") self.ns_exec("sysctl -w net.ipv6.conf.default.disable_ipv6=1") @@ -130,7 +130,7 @@ def _configure_bridge_port(self, port_name: str) -> int: result = self.ns_exec(cmd) if result != testutils.SUCCESS: return result - cmd = f"ip link set dev {port_name} mtu 9000" + cmd = f"ip link set dev {port_name} mtu 1500" return self.ns_exec(cmd) def attach_interfaces(self, num_ifaces: int) -> int: diff --git a/backends/ebpf/targets/kernel_target.py b/backends/ebpf/targets/kernel_target.py index 571c0447bda..70f13c29e2d 100644 --- a/backends/ebpf/targets/kernel_target.py +++ b/backends/ebpf/targets/kernel_target.py @@ -20,6 +20,7 @@ import time from glob import glob from pathlib import Path +from typing import Optional from .ebpfenv import Bridge from .target import EBPFTarget @@ -33,10 +34,15 @@ class Target(EBPFTarget): - EBPF_MAP_PATH = "/sys/fs/bpf/tc/globals" + EBPF_PATH = Path("/sys/fs/bpf") def __init__(self, tmpdir, options, template): EBPFTarget.__init__(self, tmpdir, options, template) + self.bpftool = self.runtimedir.joinpath("install/bpftool") + if self.options.target == "xdp": + self.ebpf_map_path = self.EBPF_PATH.joinpath(f"xdp/globals") + else: + self.ebpf_map_path = self.EBPF_PATH.joinpath(f"tc/globals") def compile_dataplane(self): # Use clang to compile the generated C code to a LLVM IR @@ -68,10 +74,7 @@ def _create_runtime(self): args += "CFLAGS+=-DCONTROL_PLANE " # add the folder local to the P4 file to the list of includes args += f"INCLUDES+=-I{os.path.dirname(self.options.p4filename)} " - # some kernel specific includes for libbpf - args += f"INCLUDES+=-I{self.runtimedir}/usr/include " - args += f"INCLUDES+=-I{self.runtimedir}/contrib/libbpf/include/uapi " - args += f"LIBS+={self.runtimedir}/usr/lib64/libbpf.a " + args += f"LIBS+={self.runtimedir}/install/libbpf/libbpf.a " args += "LIBS+=-lz " args += "LIBS+=-lelf " result = testutils.exec_process(args) @@ -79,19 +82,25 @@ def _create_runtime(self): testutils.log.error("Failed to build the filter") return result.returncode - def _create_bridge(self): + def _create_bridge(self) -> Optional[Bridge]: # The namespace is the id of the process namespace = str(os.getpid()) # Number of input files direction = "in" num_files = len(glob(self.filename("*", direction))) # Create the namespace and the bridge with all its ports - br = Bridge(namespace) - result = br.create_virtual_env(num_files) + bridge = Bridge(namespace) + result = bridge.create_virtual_env(num_files) if result != testutils.SUCCESS: - br.ns_del() + bridge.ns_del() return None - return br + if self.options.target != "xdp": + # Add the qdisc. MUST be clsact layer. + for port_name in bridge.edge_ports: + result = bridge.ns_exec(f"tc qdisc add dev {port_name} clsact") + if result != testutils.SUCCESS: + return None + return bridge def _get_run_cmd(self): direction = "in" @@ -108,7 +117,7 @@ def _get_run_cmd(self): cmd += "-d" return cmd - def _kill_processes(self, procs): + def _kill_processes(self, procs) -> None: for proc in procs: # kill process, 15 is SIGTERM os.kill(proc.pid, 15) @@ -127,7 +136,7 @@ def _load_filter(self, bridge, proc, port_name): bridge.ns_exec(f"tc qdisc add dev {port_name} clsact") cmd = ( f"tc filter add dev {port_name} egress" - f" bpf da obj {self.template}.o section prog verbose" + f" bpf da obj {self.template}.o section tc verbose " ) return bridge.ns_proc_write(proc, cmd) @@ -145,6 +154,44 @@ def _attach_filters(self, bridge, proc): return result return testutils.SUCCESS + # def _attach_filters(self, bridge: Bridge, proc: subprocess.Popen) -> int: + # # Is this a XDP or TC (ebpf_filter) program? + # p_result = testutils.exec_process(f"objdump -hj xdp {self.template}.o").returncode + # is_xdp = p_result == testutils.SUCCESS + # # Load the specified eBPF object to "port_name" egress + # # As a side-effect, this may create maps in /sys/fs/bpf/ + # # Get the command to load eBPF code to all the attached ports + # result = bridge.ns_proc_write(proc, f"mount -t bpf none {self.EBPF_PATH}") + # if result != testutils.SUCCESS: + # return result + # result = bridge.ns_proc_append(proc, f"mkdir -p {self.ebpf_map_path}") + # if result != testutils.SUCCESS: + # return result + # load_type = "xdp" if is_xdp else "tc" + # cmd = f"{self.bpftool} prog load {self.template}.o {self.EBPF_PATH}/ebpf_filter pinmaps {self.ebpf_map_path} type {load_type}" + # result = bridge.ns_proc_append(proc, cmd) + # if result != testutils.SUCCESS: + # return result + + # attach_type = "xdp" if is_xdp else "tcx_egress" + # ports = bridge.br_ports if is_xdp else bridge.edge_ports + # if len(ports) > 0: + # for port_name in ports: + # result = bridge.ns_proc_append( + # proc, + # f"{self.bpftool} net attach {attach_type} pinned {self.EBPF_PATH}/ebpf_filter dev {port_name}", + # ) + # if result != testutils.SUCCESS: + # return result + # else: + # # No ports attached (no pcap files), load to bridge instead + # result = bridge.ns_proc_append( + # proc, + # f"{self.bpftool} net attach {attach_type} pinned {self.EBPF_PATH}/ebpf_filter dev {bridge.br_name}", + # ) + + # return result + def _run_tcpdump(self, bridge, filename, port): cmd = f"{bridge.get_ns_prefix()} tcpdump -w {filename} -i {port}" return subprocess.Popen(cmd.split()) @@ -166,14 +213,10 @@ def _run_in_namespace(self, bridge): return testutils.FAILURE dump_procs = self._init_tcpdump_listeners(bridge) result = self._attach_filters(bridge, proc) - if result != testutils.SUCCESS: - return result - # Check if eBPF maps have actually been created - result = bridge.ns_proc_write(proc, f"ls -1 {self.EBPF_MAP_PATH}") if result != testutils.SUCCESS: return result # Finally, append the actual runtime command to the process - result = bridge.ns_proc_append(proc, self._get_run_cmd()) + result = bridge.ns_proc_write(proc, self._get_run_cmd()) if result != testutils.SUCCESS: return result # Execute the command queue and close the process, retrieve result diff --git a/cmake/Linters.cmake b/cmake/Linters.cmake index ecab52b80e5..616f1e77b51 100644 --- a/cmake/Linters.cmake +++ b/cmake/Linters.cmake @@ -136,7 +136,7 @@ file( tools/*.py ) list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "backends/p4tools/submodules") -list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "backends/ebpf/runtime/contrib/libbpf") +list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "backends/ebpf/runtime/contrib") list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "backends/tofino/third_party") list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "tools/cpplint.py") diff --git a/testdata/extern_modules/extern-conntrack-ebpf.c b/testdata/extern_modules/extern-conntrack-ebpf.c index 2b90d0a3b7e..f5046fd6b85 100644 --- a/testdata/extern_modules/extern-conntrack-ebpf.c +++ b/testdata/extern_modules/extern-conntrack-ebpf.c @@ -29,7 +29,7 @@ struct connInfo { }; REGISTER_START() -REGISTER_TABLE(tcp_reg, BPF_MAP_TYPE_HASH, sizeof(u32), sizeof(struct connInfo), MAX_ENTRIES) +REGISTER_TABLE(tcp_reg, BPF_MAP_TYPE_HASH, u32, struct connInfo, MAX_ENTRIES) REGISTER_END() static inline u8 tcp_conntrack(struct Headers_t hdrs)