Skip to content

Commit

Permalink
Use bpftool instead of iproute2 to load eBPF and XDP maps.
Browse files Browse the repository at this point in the history
Signed-off-by: fruffy <fruffy@nyu.edu>
  • Loading branch information
fruffy committed Dec 25, 2024
1 parent 7227d37 commit 1a6a9b6
Show file tree
Hide file tree
Showing 12 changed files with 119 additions and 68 deletions.
8 changes: 4 additions & 4 deletions backends/ebpf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ 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
)
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(
Expand Down
27 changes: 15 additions & 12 deletions backends/ebpf/build_libbpf
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""

Expand All @@ -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


Expand Down
2 changes: 1 addition & 1 deletion backends/ebpf/ebpfProgram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions backends/ebpf/run-ebpf-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
1 change: 1 addition & 0 deletions backends/ebpf/runtime/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
install
1 change: 1 addition & 0 deletions backends/ebpf/runtime/contrib/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
libbpf
bpftool
55 changes: 29 additions & 26 deletions backends/ebpf/runtime/ebpf_kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ limitations under the License.

#include "ebpf_common.h"

#include <bpf/bpf_endian.h> // definitions for bpf_ntohs etc...
#include "install/libbpf/include/bpf/bpf_endian.h" // definitions for bpf_ntohs etc...

#undef htonl
#undef htons
Expand All @@ -47,7 +47,7 @@ limitations under the License.

#ifdef CONTROL_PLANE // BEGIN EBPF USER SPACE DEFINITIONS

#include <bpf/bpf.h> // 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)
Expand All @@ -56,30 +56,18 @@ limitations under the License.

#else // BEGIN EBPF KERNEL DEFINITIONS

#include <linux/pkt_cls.h> // 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 <bpf/bpf_helpers.h>

/// 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; \
Expand All @@ -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 = { \
Expand Down Expand Up @@ -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); \
Expand All @@ -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); \
Expand All @@ -164,15 +167,15 @@ 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); \
__uint(key_size, KEY_SIZE); \
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()

Expand Down
2 changes: 1 addition & 1 deletion backends/ebpf/runtime/kernel.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
6 changes: 3 additions & 3 deletions backends/ebpf/targets/ebpfenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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")
Expand All @@ -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:
Expand Down
77 changes: 60 additions & 17 deletions backends/ebpf/targets/kernel_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -68,30 +74,33 @@ 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)
if result.returncode != testutils.SUCCESS:
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"
Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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())
Expand All @@ -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
Expand Down
Loading

0 comments on commit 1a6a9b6

Please sign in to comment.