diff --git a/.github/workflows/ci-build-ratelimiting.yaml b/.github/workflows/ci-build-ratelimiting.yaml index f525796..d377d97 100644 --- a/.github/workflows/ci-build-ratelimiting.yaml +++ b/.github/workflows/ci-build-ratelimiting.yaml @@ -9,7 +9,7 @@ jobs: ratelimiting-build-ubuntu: runs-on: ubuntu-latest container: - image: ubuntu:bionic + image: ubuntu:focal steps: - name: Set up build environment @@ -48,9 +48,11 @@ jobs: - name: Clone dependencies run: | - git clone --branch v5.1 --depth 1 https://github.com/torvalds/linux.git $LINUX_SRC_PATH + git clone --branch v5.15 --depth 1 https://github.com/torvalds/linux.git $LINUX_SRC_PATH cd $LINUX_SRC_PATH make defconfig + make prepare + make headers_install - name: Clone kernel function repository uses: actions/checkout@v2 diff --git a/ratelimiting/Makefile b/ratelimiting/Makefile index 91be28b..0fbf398 100644 --- a/ratelimiting/Makefile +++ b/ratelimiting/Makefile @@ -1,3 +1,4 @@ +# Copyright Contributors to the L3AF Project. # SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) LINUX_SRC_PATH ?= /usr/src/linux @@ -6,47 +7,101 @@ TOOLS_PATH := $(BPF_SAMPLES_PATH)/../../tools L3AF_SRC_PATH := $(CURDIR) # List of programs to build -hostprogs-y := ratelimiting +tprogs-y := ratelimiting # Libbpf dependencies LIBBPF = $(TOOLS_PATH)/lib/bpf/libbpf.a -CGROUP_HELPERS := ../../../tools/testing/selftests/bpf/cgroup_helpers.o -TRACE_HELPERS := ../../../tools/testing/selftests/bpf/trace_helpers.o - -ratelimiting-objs := ratelimiting_user.o ../../bpf_load.o +ratelimiting-objs := ratelimiting_user.o # Tell kbuild to always build the programs -always := $(hostprogs-y) -always += ratelimiting_kern.o +always-y := $(tprogs-y) +always-y += ratelimiting_kern.o + +ifeq ($(ARCH), arm) +# Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux +# headers when arm instruction set identification is requested. +ARM_ARCH_SELECTOR := $(filter -D__LINUX_ARM_ARCH__%, $(KBUILD_CFLAGS)) +BPF_EXTRA_CFLAGS := $(ARM_ARCH_SELECTOR) +TPROGS_CFLAGS += $(ARM_ARCH_SELECTOR) +endif + +ifeq ($(ARCH), mips) +TPROGS_CFLAGS += -D__SANE_USERSPACE_TYPES__ +ifdef CONFIG_MACH_LOONGSON64 +BPF_EXTRA_CFLAGS += -I$(srctree)/arch/mips/include/asm/mach-loongson64 +BPF_EXTRA_CFLAGS += -I$(srctree)/arch/mips/include/asm/mach-generic +endif +endif -KBUILD_HOSTCFLAGS += -I$(objtree)/usr/include -KBUILD_HOSTCFLAGS += -I$(srctree)/tools/lib/ -KBUILD_HOSTCFLAGS += -I$(srctree)/tools/testing/selftests/bpf/ -KBUILD_HOSTCFLAGS += -I$(srctree)/tools/lib/ -I$(srctree)/tools/include -I$(srctree)/tools/include/uapi -KBUILD_HOSTCFLAGS += -I$(srctree)/tools/perf +TPROGS_CFLAGS += -Wall -O2 +TPROGS_CFLAGS += -Wmissing-prototypes +TPROGS_CFLAGS += -Wstrict-prototypes -HOSTCFLAGS_bpf_load.o += -I$(objtree)/usr/include -Wno-unused-variable -HOSTCFLAGS_trace_helpers.o += -I$(srctree)/tools/lib/bpf/ +TPROGS_CFLAGS += -I$(objtree)/usr/include +TPROGS_CFLAGS += -I$(srctree)/tools/testing/selftests/bpf/ +TPROGS_CFLAGS += -I$(srctree)/tools/lib/ +TPROGS_CFLAGS += -I$(srctree)/tools/include +TPROGS_CFLAGS += -I$(srctree)/tools/perf +TPROGS_CFLAGS += -DHAVE_ATTR_TEST=0 -HOSTCFLAGS_ratelimiting_user.o += -I. -I$(BPF_SAMPLES_PATH) -I$(srctree)/tools/lib/bpf/ -g -LTEST/libbpf.a +ifdef SYSROOT +TPROGS_CFLAGS += --sysroot=$(SYSROOT) +TPROGS_LDFLAGS := -L$(SYSROOT)/usr/lib +endif -KBUILD_HOSTLDLIBS += $(LIBBPF) -lelf -HOSTLDLIBS_test_overhead += -lrt +TPROGS_LDLIBS += $(LIBBPF) -lelf -lz +# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline: +# make M=samples/bpf LLC=~/git/llvm-project/llvm/build/bin/llc CLANG=~/git/llvm-project/llvm/build/bin/clang LLC ?= llc CLANG ?= clang +OPT ?= opt +LLVM_DIS ?= llvm-dis LLVM_OBJCOPY ?= llvm-objcopy BTF_PAHOLE ?= pahole # Detect that we're cross compiling and use the cross compiler ifdef CROSS_COMPILE -HOSTCC = $(CROSS_COMPILE)gcc -CLANG_ARCH_ARGS = -target $(ARCH) +CLANG_ARCH_ARGS = --target=$(notdir $(CROSS_COMPILE:%-=%)) +endif + +# Don't evaluate probes and warnings if we need to run make recursively +ifneq ($(src),) +HDR_PROBE := $(shell printf "\#include \n struct list_head { int a; }; int main() { return 0; }" | \ + $(CC) $(TPROGS_CFLAGS) $(TPROGS_LDFLAGS) -x c - \ + -o /dev/null 2>/dev/null && echo okay) + +ifeq ($(HDR_PROBE),) +$(warning WARNING: Detected possible issues with include path.) +$(warning WARNING: Please install kernel headers locally (make headers_install).) +endif + +BTF_LLC_PROBE := $(shell $(LLC) -march=bpf -mattr=help 2>&1 | grep dwarfris) +BTF_PAHOLE_PROBE := $(shell $(BTF_PAHOLE) --help 2>&1 | grep BTF) +BTF_OBJCOPY_PROBE := $(shell $(LLVM_OBJCOPY) --help 2>&1 | grep -i 'usage.*llvm') +BTF_LLVM_PROBE := $(shell echo "int main() { return 0; }" | \ + $(CLANG) -target bpf -O2 -g -c -x c - -o ./llvm_btf_verify.o; \ + readelf -S ./llvm_btf_verify.o | grep BTF; \ + /bin/rm -f ./llvm_btf_verify.o) + +BPF_EXTRA_CFLAGS += -fno-stack-protector +ifneq ($(BTF_LLVM_PROBE),) + BPF_EXTRA_CFLAGS += -g +else +ifneq ($(and $(BTF_LLC_PROBE),$(BTF_PAHOLE_PROBE),$(BTF_OBJCOPY_PROBE)),) + BPF_EXTRA_CFLAGS += -g + LLC_FLAGS += -mattr=dwarfris + DWARF2BTF = y +endif +endif endif # Trick to allow make to be run from this directory all:build tar.zip +build: + $(MAKE) -C $(LINUX_SRC_PATH) M=$(L3AF_SRC_PATH) + tar.zip: @rm -rf l3af_ratelimiting @rm -f l3af_ratelimiting.tar.gz @@ -56,53 +111,104 @@ tar.zip: @tar -cvf l3af_ratelimiting.tar ./l3af_ratelimiting @gzip l3af_ratelimiting.tar -build: $(LIBBPF) - $(MAKE) -C $(LINUX_SRC_PATH)/ $(L3AF_SRC_PATH)/ BPF_SAMPLES_PATH=$(BPF_SAMPLES_PATH) clean: - $(MAKE) -C $(LINUX_SRC_PATH) M=$(L3AF_SRC_PATH)/ clean - @rm -f ../*.o - @rm -f *~ + $(MAKE) -C $(LINUX_SRC_PATH) M=$(L3AF_SRC_PATH) clean + @find $(CURDIR) -type f -name '*~' -delete + @rm -f ./*.o @rm -f l3af_ratelimiting.tar.gz $(LIBBPF): FORCE - # Fix up variables inherited from Kbuild that tools/ build system won't like $(MAKE) -C $(dir $@) RM='rm -rf' EXTRA_CFLAGS="$(TPROGS_CFLAGS)" \ LDFLAGS=$(TPROGS_LDFLAGS) srctree=$(BPF_SAMPLES_PATH)/../../ O= +BPFTOOLDIR := $(TOOLS_PATH)/bpf/bpftool +BPFTOOL := $(BPFTOOLDIR)/bpftool +$(BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) + $(MAKE) -C $(BPFTOOLDIR) srctree=$(BPF_SAMPLES_PATH)/../../ + +$(obj)/syscall_nrs.h: $(obj)/syscall_nrs.s FORCE + $(call filechk,offsets,__SYSCALL_NRS_H__) + +targets += syscall_nrs.s +clean-files += syscall_nrs.h + FORCE: # Verify LLVM compiler tools are available and bpf target is supported by llc .PHONY: verify_cmds verify_target_bpf $(CLANG) $(LLC) -#verify_cmds: $(CLANG) $(LLC) +verify_cmds: $(CLANG) $(LLC) @for TOOL in $^ ; do \ - if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \ - echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\ - exit 1; \ - else true; fi; \ - done + if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \ + echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\ + exit 1; \ + else true; fi; \ + done -#verify_target_bpf: verify_cmds +verify_target_bpf: verify_cmds @if ! (${LLC} -march=bpf -mattr=help > /dev/null 2>&1); then \ - echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\ - echo " NOTICE: LLVM version >= 3.7.1 required" ;\ - exit 2; \ - else true; fi + echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\ + echo " NOTICE: LLVM version >= 3.7.1 required" ;\ + exit 2; \ + else true; fi $(BPF_SAMPLES_PATH)/*.c: verify_target_bpf $(LIBBPF) $(src)/*.c: verify_target_bpf $(LIBBPF) -$(obj)/%.o: $(src)/%.c - $(Q)$(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(EXTRA_CFLAGS) -I$(obj) \ - -I$(srctree)/tools/testing/selftests/bpf/ \ + +-include $(BPF_SAMPLES_PATH)/Makefile.target + +VMLINUX_BTF_PATHS ?= $(abspath $(if $(O),$(O)/vmlinux)) \ + $(abspath $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux)) \ + $(abspath ./vmlinux) +VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS)))) + +clean-files += vmlinux.h + +# Get Clang's default includes on this system, as opposed to those seen by +# '-target bpf'. This fixes "missing" files on some architectures/distros, +# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +define get_sys_includes +$(shell $(1) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \ +$(shell $(1) -dM -E - #include -#include "bpf_load.h" +#include "bpf/libbpf.h" +#include "constants.h" +#include "log.h" + #ifdef __linux__ #include "bpf_util.h" +int map_fd[MAP_COUNT]; #endif #ifdef WIN32 #include #include #include #define sleep(seconds) Sleep((seconds) * 1000) + char* strsep(char** stringp, const char* delim) { static char* next_token = NULL; @@ -52,17 +57,13 @@ int gettimeofday(struct timeval* tv, struct timezone* tz) return 0; } #include "bpf/bpf.h" +#include "bpf_load.h" #endif -#include "bpf/libbpf.h" - -#include "constants.h" -#include "log.h" static const char *__doc__ = "Ratelimit incoming TCP connections using XDP"; static int ifindex; - FILE *info; static char prev_prog_map[1024]; static const struct option long_options[] = { @@ -142,9 +143,9 @@ static FILE* set_logfile(void) static int xdp_unlink_bpf_chain(const char *map_filename) { int ret = 0; int key = 0; - int map_fd = bpf_obj_get(map_filename); - if (map_fd > 0) { - ret = bpf_map_delete_elem(map_fd, &key); + int prog_map_fd = bpf_obj_get(map_filename); + if (prog_map_fd > 0) { + ret = bpf_map_delete_elem(prog_map_fd, &key); if (ret != 0) { log_err("Failed to remove XDP program from the chain"); } @@ -236,6 +237,85 @@ static void update_ports(char *ports) free(tmp); } +#ifdef __linux__ +static int load_maps(struct bpf_object *obj) { + map_fd[0] = bpf_map__fd(bpf_object__find_map_by_name(obj, rl_config_map)); + if (map_fd[0] < 0) + { + fprintf(stderr, "Failed to get rl_config_map FD\n"); + return 1; + } + map_fd[1] = bpf_map__fd(bpf_object__find_map_by_name(obj, rl_window_map)); + if (map_fd[1] < 0) + { + fprintf(stderr, "Failed to get rl_window_map FD\n"); + return 1; + } + map_fd[2] = bpf_map__fd(bpf_object__find_map_by_name(obj, rl_recv_count_map)); + if (map_fd[1] < 0) + { + fprintf(stderr, "Failed to get rl_recv_count_map FD\n"); + return 1; + } + map_fd[3] = bpf_map__fd(bpf_object__find_map_by_name(obj, rl_drop_count_map)); + if (map_fd[1] < 0) + { + fprintf(stderr, "Failed to get rl_drop_count_map FD\n"); + return 1; + } + map_fd[4] = bpf_map__fd(bpf_object__find_map_by_name(obj, rl_ports_map)); + if (map_fd[4] < 0) + { + fprintf(stderr, "Failed to get rl_ports_map FD\n"); + return 1; + } + map_fd[5] = bpf_map__fd(bpf_object__find_map_by_name(obj, xdp_rl_ingress_next_prog)); + if (map_fd[5] < 0) + { + fprintf(stderr, "Failed to get xdp_rl_ingress_next_prog FD\n"); + return 1; + } + return 0; +} + +static struct bpf_object* load_bpf_programs(const char *bpf_file, int prog_fd[1]) +{ + struct bpf_object *bpf_obj; + struct bpf_program *bpf_prog; + + struct bpf_object_open_attr open_attr = { + .file = bpf_file, + .prog_type = BPF_PROG_TYPE_UNSPEC, + }; + bpf_obj = bpf_object__open_xattr(&open_attr); + if (!bpf_obj) + { + fprintf(stderr, "ERR: failed to open object %s\n", bpf_file); + return NULL; + } + + if (bpf_object__load(bpf_obj)) + { + fprintf(stderr, "Failed to load BPF Object\n"); + return NULL; + } + bpf_prog = bpf_program__next(NULL, bpf_obj); + if (!bpf_prog) + { + fprintf(stderr, "Couldn't find a program trace_inet_sock_set_state in the BPF file %s\n", bpf_file); + return NULL; + } + + prog_fd[0] = bpf_program__fd(bpf_prog); + if (prog_fd[0] <= 0) { + fprintf(stderr, "Failed to get program FD\n"); + return NULL; + } + + return bpf_obj; +} +#endif + int main(int argc, char **argv) { int longindex = 0, rate = 0, opt; @@ -243,13 +323,18 @@ int main(int argc, char **argv) char bpf_obj_file[256]; char ports[2048]; verbosity = LOG_INFO; + char map_file_path[2048]; #ifdef __linux__ struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; #endif int len = 0; + struct bpf_object *obj; + int prog_fd[2]; + snprintf(bpf_obj_file, sizeof(bpf_obj_file), "%s_kern.o", argv[0]); memset(&ports, 0, 2048); + memset(&map_file_path, 0, 2048); /* Parse commands line args */ while ((opt = getopt_long(argc, argv, "h", long_options, &longindex)) != -1) @@ -299,16 +384,25 @@ int main(int argc, char **argv) __u64 ckey = 0, rkey = 0, dkey = 0, pkey = 0; __u64 recv_count = 0, drop_count = 0; - +#ifdef __linux__ + obj = load_bpf_programs(bpf_obj_file,prog_fd); + if (obj == NULL) { + log_err("load_bpf_programs failed\n"); + return 1; + } +#else if (load_bpf_file(bpf_obj_file)) { log_err("Failed to load bpf program"); return 1; } +#endif if (!prog_fd[0]) { log_err("Failed to get bpf program fd") return 1; } + log_info("loaded bpf file\n"); + /* Get the previous program's map fd in the chain */ int prev_prog_map_fd = bpf_obj_get(prev_prog_map); if (prev_prog_map_fd < 0) { @@ -324,11 +418,21 @@ int main(int argc, char **argv) /* closing map fd to avoid stale map */ close(prev_prog_map_fd); +#ifdef __linux__ + /* load map fds into map_fd array */ + if (load_maps(obj) != 0) { + log_err("Failed to load bpf maps"); + exit(EXIT_FAILURE); + } +#endif + int next_prog_map_fd = bpf_obj_get(xdp_rl_ingress_next_prog); if (next_prog_map_fd < 0) { - log_info("Failed to fetch next prog map fd, creating one"); - if (bpf_obj_pin(map_fd[5], xdp_rl_ingress_next_prog)) { - log_info("Failed to pin next prog fd map"); + log_info("Failed to fetch next prog map fd, creating one %s", xdp_rl_ingress_next_prog); + sprintf(map_file_path,"%s/%s",pin_basedir,xdp_rl_ingress_next_prog); + int errorno = bpf_obj_pin(map_fd[5], map_file_path); + if (errorno) { + log_info("Failed to pin next prog fd map %d map name %s error %s", map_fd[5], map_file_path, strerror(errorno)); exit(EXIT_FAILURE); } } @@ -382,4 +486,4 @@ int main(int argc, char **argv) #elif WIN32 Sleep(INFINITE); #endif -} +} \ No newline at end of file