From a60d6dc79afbfbe088828d9d4bcb18b98bbac5bd Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Fri, 4 Nov 2022 16:39:21 +0100 Subject: [PATCH 01/17] filterc: Compile filter to cBPF Signed-off-by: Felix Maurer --- Makefile | 3 ++- filterc/.gitignore | 1 + filterc/Makefile | 15 +++++++++++ filterc/bpfc.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++ filterc/bpfc.h | 8 ++++++ filterc/filterc.c | 57 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 filterc/.gitignore create mode 100644 filterc/Makefile create mode 100644 filterc/bpfc.c create mode 100644 filterc/bpfc.h create mode 100644 filterc/filterc.c diff --git a/Makefile b/Makefile index 5c22e854..70f59ade 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,8 @@ endif include version.mk -UTILS := xdp-filter xdp-loader xdp-dump xdp-bench xdp-monitor xdp-trafficgen +UTILS := xdp-filter xdp-loader xdp-dump xdp-bench xdp-monitor xdp-trafficgen \ + filterc SUBDIRS := lib $(UTILS) .PHONY: check_submodule help clobber distclean clean install test libxdp $(SUBDIRS) diff --git a/filterc/.gitignore b/filterc/.gitignore new file mode 100644 index 00000000..4b49348c --- /dev/null +++ b/filterc/.gitignore @@ -0,0 +1 @@ +filterc diff --git a/filterc/Makefile b/filterc/Makefile new file mode 100644 index 00000000..fb7e26d8 --- /dev/null +++ b/filterc/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +TOOL_NAME := filterc +USER_TARGETS := filterc + +EXTRA_BUILD := bpfc.o +USER_GEN := $(EXTRA_BUILD) +EXTRA_USER_DEPS := $(USER_GEN) + +LIB_DIR = ../lib +USER_LIBS = $(EXTRA_BUILD) -lpcap + +include $(LIB_DIR)/common.mk + +$(EXTRA_BUILD): %.o: %.c %.h + $(QUIET_CC)$(CC) -Wall $(CFLAGS) -c $< -o $@ diff --git a/filterc/bpfc.c b/filterc/bpfc.c new file mode 100644 index 00000000..02332250 --- /dev/null +++ b/filterc/bpfc.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include + +// We need the bpf_insn and bpf_program definitions from libpcap, but they +// conflict with the Linux/libbpf definitions. Rename the libpcap structs to +// prevent the conflicts. +#define bpf_insn cbpf_insn +#define bpf_program cbpf_program +#include +#include +#undef bpf_insn +#undef bpf_program + +#include "bpfc.h" + +#define DEFAULT_SNAP_LEN 262144 + +struct cbpf_program *cbpf_program_from_filter(char *filter) +{ + pcap_t *pcap = NULL; + struct cbpf_program *prog = NULL; + int err; + + pcap = pcap_open_dead(DLT_EN10MB, DEFAULT_SNAP_LEN); + if (!pcap) { + goto error; + } + + prog = malloc(sizeof(struct cbpf_program)); + if (!prog) { + goto error; + } + + err = pcap_compile(pcap, prog, filter, 1, PCAP_NETMASK_UNKNOWN); + if (err) { + goto error; + } + + goto out; + +error: + if (prog) { + free(prog); + prog = NULL; + } +out: + if (pcap) + pcap_close(pcap); + return prog; +} + +void cbpf_program_dump(struct cbpf_program *prog) +{ + bpf_dump(prog, 1); +} + +void cbpf_program_free(struct cbpf_program *prog) +{ + pcap_freecode(prog); + free(prog); +} diff --git a/filterc/bpfc.h b/filterc/bpfc.h new file mode 100644 index 00000000..b861d9f6 --- /dev/null +++ b/filterc/bpfc.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +struct cbpf_insn; +struct cbpf_program; + +struct cbpf_program *cbpf_program_from_filter(char*); +void cbpf_program_dump(struct cbpf_program*); +void cbpf_program_free(struct cbpf_program*); diff --git a/filterc/filterc.c b/filterc/filterc.c new file mode 100644 index 00000000..1f05679a --- /dev/null +++ b/filterc/filterc.c @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include + +#include "logging.h" +#include "params.h" + +#include "bpfc.h" + +#define PROG_NAME "filterc" + +static const struct filteropt { + char *output; + char *filter; +} filteropt_defaults = { +}; +struct filteropt cfg_filteropt; + +static struct prog_option filterc_options[] = { + DEFINE_OPTION("output", OPT_STRING, struct filteropt, output, + .short_opt = 'o', + .required = true, + .metavar = "", + .help = "Output compiled object to "), + DEFINE_OPTION("filter", OPT_STRING, struct filteropt, filter, + .required = true, + .positional = true, + .metavar = "", + .help = "pcap-filter(7) string to compile"), + END_OPTIONS +}; + +int main(int argc, char **argv) +{ + struct cbpf_program *cbpf_prog = NULL; + int rc = EXIT_FAILURE; + + if (parse_cmdline_args(argc, argv, filterc_options, &cfg_filteropt, + sizeof(cfg_filteropt), PROG_NAME, PROG_NAME, + "Compile pcap-filter expressions to eBPF object files", + &filteropt_defaults) != 0) + goto out; + + cbpf_prog = cbpf_program_from_filter(cfg_filteropt.filter); + if (!cbpf_prog) { + pr_warn("Failed to compile filter\n"); + goto out; + } + + cbpf_program_dump(cbpf_prog); + + rc = EXIT_SUCCESS; + +out: + if (cbpf_prog) + cbpf_program_free(cbpf_prog); + return rc; +} From 63af7cefe658abd88000ac5473fda183fcdf9316 Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Fri, 4 Aug 2023 11:52:49 +0200 Subject: [PATCH 02/17] filterc: Structure to compile cBPF to eBPF Add a few helpful functions for error handling and to dump the program. Signed-off-by: Felix Maurer --- filterc/bpfc.c | 68 +++++++++++++++++++++++++++++++++++++++++++++-- filterc/bpfc.h | 12 +++++++++ filterc/filterc.c | 9 +++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index 02332250..fa04b041 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -2,6 +2,8 @@ #include +#include + // We need the bpf_insn and bpf_program definitions from libpcap, but they // conflict with the Linux/libbpf definitions. Rename the libpcap structs to // prevent the conflicts. @@ -14,7 +16,22 @@ #include "bpfc.h" -#define DEFAULT_SNAP_LEN 262144 +#define BPFC_DEFAULT_SNAP_LEN 262144 +#define BPFC_ERRBUFF_SZ 256 + +static char bpfc_errbuff[BPFC_ERRBUFF_SZ + 1]; + +char *bpfc_geterr() +{ + return bpfc_errbuff; +} + +#define bpfc_error(format, ...) \ + do { \ + snprintf(bpfc_errbuff, BPFC_ERRBUFF_SZ, format, \ + ## __VA_ARGS__); \ + bpfc_errbuff[BPFC_ERRBUFF_SZ] = 0; \ + } while (0) struct cbpf_program *cbpf_program_from_filter(char *filter) { @@ -22,7 +39,7 @@ struct cbpf_program *cbpf_program_from_filter(char *filter) struct cbpf_program *prog = NULL; int err; - pcap = pcap_open_dead(DLT_EN10MB, DEFAULT_SNAP_LEN); + pcap = pcap_open_dead(DLT_EN10MB, BPFC_DEFAULT_SNAP_LEN); if (!pcap) { goto error; } @@ -52,6 +69,7 @@ struct cbpf_program *cbpf_program_from_filter(char *filter) void cbpf_program_dump(struct cbpf_program *prog) { + printf("cBPF program (insn cnt = %d)\n", prog->bf_len); bpf_dump(prog, 1); } @@ -60,3 +78,49 @@ void cbpf_program_free(struct cbpf_program *prog) pcap_freecode(prog); free(prog); } + +struct ebpf_program { + struct bpf_insn *insns; + size_t insns_cnt; +}; + +struct ebpf_program *ebpf_program_from_cbpf(struct cbpf_program *cbpf_prog) +{ + struct ebpf_program *prog = NULL; + + prog = malloc(sizeof(*prog)); + if (!prog) { + goto error; + } + + prog->insns_cnt = cbpf_prog->bf_len; + + return prog; + +error: + if (prog) + free(prog); + return NULL; +} + +void ebpf_program_dump(struct ebpf_program *prog) +{ + size_t i; + + printf("eBPF program (insn cnt = %lu)\n", prog->insns_cnt); + for (i = 0; i < prog->insns_cnt; i++) { + struct bpf_insn insn = prog->insns[i]; + printf("(%03lu) code:0x%02x (m:%02x|s:%02x|c:%02x) dst:0x%01x " + "src:0x%01x off:0x%04x imm:0x%08x\n", i, insn.code, + BPF_MODE(insn.code), BPF_SIZE(insn.code), + BPF_CLASS(insn.code), insn.dst_reg, insn.src_reg, + insn.off, insn.imm); + } +} + +void ebpf_program_free(struct ebpf_program *prog) +{ + if (prog) + free(prog->insns); + free(prog); +} diff --git a/filterc/bpfc.h b/filterc/bpfc.h index b861d9f6..61e96010 100644 --- a/filterc/bpfc.h +++ b/filterc/bpfc.h @@ -1,8 +1,20 @@ /* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BPFC_H_ +#define _BPFC_H_ + struct cbpf_insn; struct cbpf_program; +struct ebpf_program; + +char *bpfc_geterr(); struct cbpf_program *cbpf_program_from_filter(char*); void cbpf_program_dump(struct cbpf_program*); void cbpf_program_free(struct cbpf_program*); + +struct ebpf_program *ebpf_program_from_cbpf(struct cbpf_program*); +void ebpf_program_dump(struct ebpf_program*); +void ebpf_program_free(struct ebpf_program*); + +#endif /* _BPFC_H_ */ diff --git a/filterc/filterc.c b/filterc/filterc.c index 1f05679a..ba455557 100644 --- a/filterc/filterc.c +++ b/filterc/filterc.c @@ -32,6 +32,7 @@ static struct prog_option filterc_options[] = { int main(int argc, char **argv) { struct cbpf_program *cbpf_prog = NULL; + struct ebpf_program *ebpf_prog = NULL; int rc = EXIT_FAILURE; if (parse_cmdline_args(argc, argv, filterc_options, &cfg_filteropt, @@ -47,10 +48,18 @@ int main(int argc, char **argv) } cbpf_program_dump(cbpf_prog); + printf("\n"); + + ebpf_prog = ebpf_program_from_cbpf(cbpf_prog); + + printf("\n"); + ebpf_program_dump(ebpf_prog); rc = EXIT_SUCCESS; out: + if (ebpf_prog) + ebpf_program_free(ebpf_prog); if (cbpf_prog) cbpf_program_free(cbpf_prog); return rc; From 631a6142d3a420b499d1c499941daca57fbc916c Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Fri, 8 Sep 2023 18:48:12 +0200 Subject: [PATCH 03/17] filterc: Start converting cBPF to eBPF Start with the arithmetic instructions and a simple conversion of jumps. The jumps will be optimized in the future (if one of the branches goes to the next instruction). Signed-off-by: Felix Maurer --- filterc/bpfc.c | 216 ++++++++++++++++++++++++++- filterc/filter.h | 370 ++++++++++++++++++++++++++++++++++++++++++++++ filterc/filterc.c | 4 + 3 files changed, 588 insertions(+), 2 deletions(-) create mode 100644 filterc/filter.h diff --git a/filterc/bpfc.c b/filterc/bpfc.c index fa04b041..e0cb1a09 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -1,6 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0 */ +#include +#include #include +#include + +#include +#include #include @@ -14,6 +20,7 @@ #undef bpf_insn #undef bpf_program +#include "filter.h" #include "bpfc.h" #define BPFC_DEFAULT_SNAP_LEN 262144 @@ -84,16 +91,221 @@ struct ebpf_program { size_t insns_cnt; }; +/** + * Converts a cBPF program to a eBPF XDP program that passes the verifier. The + * design is similar to what bpf_convert_filter does. The conversion happens in + * two passes: + * (1) convert to check the resulting length and calculate jump offsets + * (2) do the conversion again and store the result with the correct jump + * offsets. + */ + +static int convert_cbpf(struct cbpf_program *cbpf_prog, + struct ebpf_program *prog) +{ + // NULL on first pass and allocated for the second pass + struct bpf_insn *new_insn, *first_insn = NULL; + struct cbpf_insn *fp; + int *addrs = NULL; + size_t insns_cnt = 0; + u_int i; + int code, target, imm, src_reg, dst_reg, bpf_src, err = 0; + + addrs = calloc(sizeof(*addrs), cbpf_prog->bf_len); + if (!addrs) { + bpfc_error("Failed to allocate memory for offset calculation"); + err = errno; + goto out; + } + +do_pass: + new_insn = first_insn; + fp = cbpf_prog->bf_insns; + + if (first_insn) { + /* All programs must keep CTX in callee saved BPF_REG_CTX. + * In eBPF case it's done by the compiler, here we need to + * do this ourself. Initial CTX is present in BPF_REG_ARG1. + */ + *new_insn++ = BPF_MOV64_REG(BPF_REG_CTX, BPF_REG_ARG1); + } else { + new_insn += 1; + } + + for (i = 0; i < cbpf_prog->bf_len; fp++, i++) { + struct bpf_insn tmp_insns[32] = { }; + struct bpf_insn *insn = tmp_insns; + + addrs[i] = new_insn - first_insn; + + switch (fp->code) { + /* All arithmetic insns and skb loads map as-is. */ + case BPF_ALU | BPF_ADD | BPF_X: + case BPF_ALU | BPF_ADD | BPF_K: + case BPF_ALU | BPF_SUB | BPF_X: + case BPF_ALU | BPF_SUB | BPF_K: + case BPF_ALU | BPF_AND | BPF_X: + case BPF_ALU | BPF_AND | BPF_K: + case BPF_ALU | BPF_OR | BPF_X: + case BPF_ALU | BPF_OR | BPF_K: + case BPF_ALU | BPF_LSH | BPF_X: + case BPF_ALU | BPF_LSH | BPF_K: + case BPF_ALU | BPF_RSH | BPF_X: + case BPF_ALU | BPF_RSH | BPF_K: + case BPF_ALU | BPF_XOR | BPF_X: + case BPF_ALU | BPF_XOR | BPF_K: + case BPF_ALU | BPF_MUL | BPF_X: + case BPF_ALU | BPF_MUL | BPF_K: + case BPF_ALU | BPF_DIV | BPF_X: + case BPF_ALU | BPF_DIV | BPF_K: + case BPF_ALU | BPF_MOD | BPF_X: + case BPF_ALU | BPF_MOD | BPF_K: + case BPF_ALU | BPF_NEG: + if (fp->code == (BPF_ALU | BPF_DIV | BPF_X) || + fp->code == (BPF_ALU | BPF_MOD | BPF_X)) { + *insn++ = BPF_MOV32_REG(BPF_REG_X, BPF_REG_X); + /* Error with exception code on div/mod by 0. + * For cBPF programs, this was always return 0. + */ + *insn++ = BPF_JMP_IMM(BPF_JNE, BPF_REG_X, 0, 2); + *insn++ = BPF_ALU32_REG(BPF_XOR, BPF_REG_A, BPF_REG_A); + *insn++ = BPF_EXIT_INSN(); + } + + // verifier wants src to be 0 when using imm + if (BPF_SRC(fp->code) == BPF_X) + src_reg = BPF_REG_X; + else + src_reg = 0; + + *insn++ = BPF_RAW_INSN(fp->code, BPF_REG_A, src_reg, 0, fp->k); + break; + +#define BPF_JMP_INSN(insn_code, dst, src, immv, target) ({ \ + int32_t off; \ + \ + if (target >= (int)cbpf_prog->bf_len || target < 0) { \ + bpfc_error("insn %d: Invalid jump target", i);\ + err = EINVAL; \ + goto out; \ + } \ + off = addrs[target] - addrs[i] - 1; \ + /* Adjust pc relative offset for 2nd or 3rd insn. */ \ + off -= insn - tmp_insns; \ + /* Reject anything not fitting into insn->off. */ \ + if (off < INT16_MIN || off > INT16_MAX) { \ + bpfc_error("insn %d: Jump too big for eBPF offsets", i);\ + err = EINVAL; \ + goto out; \ + } \ + \ + (struct bpf_insn) { \ + .code = insn_code, \ + .dst_reg = dst, \ + .src_reg = src, \ + .off = off, \ + .imm = immv }; \ + }) +#define BPF_JA_INSN(insn_code, target) \ + BPF_JMP_INSN(insn_code, 0, 0, 0, target) + + case BPF_JMP | BPF_JA: + *insn++ = BPF_JA_INSN(fp->code, (int)(i + fp->k + 1)); + break; + + case BPF_JMP | BPF_JEQ | BPF_K: + case BPF_JMP | BPF_JEQ | BPF_X: + case BPF_JMP | BPF_JSET | BPF_K: + case BPF_JMP | BPF_JSET | BPF_X: + case BPF_JMP | BPF_JGT | BPF_K: + case BPF_JMP | BPF_JGT | BPF_X: + case BPF_JMP | BPF_JGE | BPF_K: + case BPF_JMP | BPF_JGE | BPF_X: + code = 0; + target = 0; + imm = 0; + src_reg = 0; + dst_reg = 0; + bpf_src = 0; + + if (BPF_SRC(fp->code) == BPF_K && (int) fp->k < 0) { + /* BPF immediates are signed, zero extend + * immediate into tmp register and use it + * in compare insn. + */ + *insn++ = BPF_MOV32_IMM(BPF_REG_TMP, fp->k); + + dst_reg = BPF_REG_A; + src_reg = BPF_REG_TMP; + bpf_src = BPF_X; + } else { + dst_reg = BPF_REG_A; + imm = fp->k; + bpf_src = BPF_SRC(fp->code); + src_reg = bpf_src == BPF_X ? BPF_REG_X : 0; + } + + /* Other jumps are mapped into two insns: Jxx and JA. */ + code = BPF_JMP | BPF_OP(fp->code) | bpf_src; + target = i + fp->jt + 1; + *insn++ = BPF_JMP_INSN(code, dst_reg, src_reg, imm, target); + + code = BPF_JMP | BPF_JA; + target = i + fp->jf + 1; + *insn++ = BPF_JA_INSN(code, target); + break; + + /* Unknown instruction. */ + default: + bpfc_error("Unknown instruction: %d", i); + err = EINVAL; + goto out; + } + + insns_cnt = insn - tmp_insns; + if (first_insn) + memcpy(new_insn, tmp_insns, sizeof(*insn) * insns_cnt); + new_insn += insns_cnt; + + + } + + insns_cnt = new_insn - first_insn; + if (!first_insn) { + first_insn = calloc(sizeof(*first_insn), insns_cnt); + if (!new_insn) { + bpfc_error("Failed to allocate memory for eBPF instructions"); + err = errno; + goto out; + } + goto do_pass; + } + +out: + if (err) { + free(first_insn); + } else { + prog->insns = first_insn; + prog->insns_cnt = insns_cnt; + } + + free(addrs); + return err; +} + struct ebpf_program *ebpf_program_from_cbpf(struct cbpf_program *cbpf_prog) { struct ebpf_program *prog = NULL; + int err; - prog = malloc(sizeof(*prog)); + prog = calloc(sizeof(*prog), 1); if (!prog) { goto error; } - prog->insns_cnt = cbpf_prog->bf_len; + err = convert_cbpf(cbpf_prog, prog); + if (err) + goto error; return prog; diff --git a/filterc/filter.h b/filterc/filter.h new file mode 100644 index 00000000..f94698e7 --- /dev/null +++ b/filterc/filter.h @@ -0,0 +1,370 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Linux Socket Filter Data Structures + * From include/linux/filter.h + */ +#ifndef _FILTERC_LINUX_FILTER_H_ +#define _FILTERC_LINUX_FILTER_H_ + +#include + +/* ArgX, context and stack frame pointer register positions. Note, + * Arg1, Arg2, Arg3, etc are used as argument mappings of function + * calls in BPF_CALL instruction. + */ +#define BPF_REG_ARG1 BPF_REG_1 +#define BPF_REG_ARG2 BPF_REG_2 +#define BPF_REG_ARG3 BPF_REG_3 +#define BPF_REG_ARG4 BPF_REG_4 +#define BPF_REG_ARG5 BPF_REG_5 +#define BPF_REG_CTX BPF_REG_6 +#define BPF_REG_FP BPF_REG_10 + +/* Additional register mappings for converted user programs. */ +#define BPF_REG_A BPF_REG_0 +#define BPF_REG_X BPF_REG_7 +#define BPF_REG_TMP BPF_REG_2 /* scratch reg */ +#define BPF_REG_D BPF_REG_8 /* data, callee-saved */ +#define BPF_REG_H BPF_REG_9 /* hlen, callee-saved */ + +/* Kernel hidden auxiliary/helper register. */ +#define BPF_REG_AX MAX_BPF_REG +#define MAX_BPF_EXT_REG (MAX_BPF_REG + 1) +#define MAX_BPF_JIT_REG MAX_BPF_EXT_REG + +/* unused opcode to mark special call to bpf_tail_call() helper */ +#define BPF_TAIL_CALL 0xf0 + +/* unused opcode to mark special load instruction. Same as BPF_ABS */ +#define BPF_PROBE_MEM 0x20 + +/* unused opcode to mark call to interpreter with arguments */ +#define BPF_CALL_ARGS 0xe0 + +/* unused opcode to mark speculation barrier for mitigating + * Speculative Store Bypass + */ +#define BPF_NOSPEC 0xc0 + +/* As per nm, we expose JITed images as text (code) section for + * kallsyms. That way, tools like perf can find it to match + * addresses. + */ +#define BPF_SYM_ELF_TYPE 't' + +/* BPF program can access up to 512 bytes of stack space. */ +#define MAX_BPF_STACK 512 + +/* Helper macros for filter block array initializers. */ + +/* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */ + +#define BPF_ALU64_REG(OP, DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +#define BPF_ALU32_REG(OP, DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_OP(OP) | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +/* ALU ops on immediates, bpf_add|sub|...: dst_reg += imm32 */ + +#define BPF_ALU64_IMM(OP, DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +#define BPF_ALU32_IMM(OP, DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +/* Endianess conversion, cpu_to_{l,b}e(), {l,b}e_to_cpu() */ + +#define BPF_ENDIAN(TYPE, DST, LEN) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_END | BPF_SRC(TYPE), \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = LEN }) + +/* Short form of mov, dst_reg = src_reg */ + +#define BPF_MOV64_REG(DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_MOV | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +#define BPF_MOV32_REG(DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_MOV | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +/* Short form of mov, dst_reg = imm32 */ + +#define BPF_MOV64_IMM(DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_MOV | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +#define BPF_MOV32_IMM(DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_MOV | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +/* Special form of mov32, used for doing explicit zero extension on dst. */ +#define BPF_ZEXT_REG(DST) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_MOV | BPF_X, \ + .dst_reg = DST, \ + .src_reg = DST, \ + .off = 0, \ + .imm = 1 }) + +/* BPF_LD_IMM64 macro encodes single 'load 64-bit immediate' insn */ +#define BPF_LD_IMM64(DST, IMM) \ + BPF_LD_IMM64_RAW(DST, 0, IMM) + +#define BPF_LD_IMM64_RAW(DST, SRC, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_LD | BPF_DW | BPF_IMM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = (__u32) (IMM) }), \ + ((struct bpf_insn) { \ + .code = 0, /* zero is reserved opcode */ \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = ((__u64) (IMM)) >> 32 }) + +/* pseudo BPF_LD_IMM64 insn used to refer to process-local map_fd */ +#define BPF_LD_MAP_FD(DST, MAP_FD) \ + BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD) + +/* Short form of mov based on type, BPF_X: dst_reg = src_reg, BPF_K: dst_reg = imm32 */ + +#define BPF_MOV64_RAW(TYPE, DST, SRC, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_MOV | BPF_SRC(TYPE), \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = IMM }) + +#define BPF_MOV32_RAW(TYPE, DST, SRC, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_MOV | BPF_SRC(TYPE), \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = IMM }) + +/* Direct packet access, R0 = *(uint *) (skb->data + imm32) */ + +#define BPF_LD_ABS(SIZE, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +/* Indirect packet access, R0 = *(uint *) (skb->data + src_reg + imm32) */ + +#define BPF_LD_IND(SIZE, SRC, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_LD | BPF_SIZE(SIZE) | BPF_IND, \ + .dst_reg = 0, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = IMM }) + +/* Memory load, dst_reg = *(uint *) (src_reg + off16) */ + +#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + +/* Memory store, *(uint *) (dst_reg + off16) = src_reg */ + +#define BPF_STX_MEM(SIZE, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + + +/* + * Atomic operations: + * + * BPF_ADD *(uint *) (dst_reg + off16) += src_reg + * BPF_AND *(uint *) (dst_reg + off16) &= src_reg + * BPF_OR *(uint *) (dst_reg + off16) |= src_reg + * BPF_XOR *(uint *) (dst_reg + off16) ^= src_reg + * BPF_ADD | BPF_FETCH src_reg = atomic_fetch_add(dst_reg + off16, src_reg); + * BPF_AND | BPF_FETCH src_reg = atomic_fetch_and(dst_reg + off16, src_reg); + * BPF_OR | BPF_FETCH src_reg = atomic_fetch_or(dst_reg + off16, src_reg); + * BPF_XOR | BPF_FETCH src_reg = atomic_fetch_xor(dst_reg + off16, src_reg); + * BPF_XCHG src_reg = atomic_xchg(dst_reg + off16, src_reg) + * BPF_CMPXCHG r0 = atomic_cmpxchg(dst_reg + off16, r0, src_reg) + */ + +#define BPF_ATOMIC_OP(SIZE, OP, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_STX | BPF_SIZE(SIZE) | BPF_ATOMIC, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = OP }) + +/* Legacy alias */ +#define BPF_STX_XADD(SIZE, DST, SRC, OFF) BPF_ATOMIC_OP(SIZE, BPF_ADD, DST, SRC, OFF) + +/* Memory store, *(uint *) (dst_reg + off16) = imm32 */ + +#define BPF_ST_MEM(SIZE, DST, OFF, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = OFF, \ + .imm = IMM }) + +/* Conditional jumps against registers, if (dst_reg 'op' src_reg) goto pc + off16 */ + +#define BPF_JMP_REG(OP, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_OP(OP) | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + +/* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto pc + off16 */ + +#define BPF_JMP_IMM(OP, DST, IMM, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = OFF, \ + .imm = IMM }) + +/* Like BPF_JMP_REG, but with 32-bit wide operands for comparison. */ + +#define BPF_JMP32_REG(OP, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_JMP32 | BPF_OP(OP) | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + +/* Like BPF_JMP_IMM, but with 32-bit wide operands for comparison. */ + +#define BPF_JMP32_IMM(OP, DST, IMM, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_JMP32 | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = OFF, \ + .imm = IMM }) + +/* Unconditional jumps, goto pc + off16 */ + +#define BPF_JMP_A(OFF) \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_JA, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = OFF, \ + .imm = 0 }) + +/* Relative call */ + +#define BPF_CALL_REL(TGT) \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_CALL, \ + .dst_reg = 0, \ + .src_reg = BPF_PSEUDO_CALL, \ + .off = 0, \ + .imm = TGT }) + +/* Convert function address to BPF immediate */ + +#define BPF_CALL_IMM(x) ((void *)(x) - (void *)__bpf_call_base) + +#define BPF_EMIT_CALL(FUNC) \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_CALL, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = BPF_CALL_IMM(FUNC) }) + +/* Raw code statement block */ + +#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \ + ((struct bpf_insn) { \ + .code = CODE, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = IMM }) + +/* Program exit */ + +#define BPF_EXIT_INSN() \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_EXIT, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = 0 }) + +/* Speculation barrier */ + +#define BPF_ST_NOSPEC() \ + ((struct bpf_insn) { \ + .code = BPF_ST | BPF_NOSPEC, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = 0 }) + +#endif /* _FILTERC_LINUX_FILTER_H_ */ diff --git a/filterc/filterc.c b/filterc/filterc.c index ba455557..6e29da39 100644 --- a/filterc/filterc.c +++ b/filterc/filterc.c @@ -51,6 +51,10 @@ int main(int argc, char **argv) printf("\n"); ebpf_prog = ebpf_program_from_cbpf(cbpf_prog); + if (!ebpf_prog) { + pr_warn("Failed to convert cBPF to eBPF: %s\n", bpfc_geterr()); + goto out; + } printf("\n"); ebpf_program_dump(ebpf_prog); From 62b752b037801a8ae4c54649357e4627be153bee Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Thu, 8 Dec 2022 18:43:53 +0100 Subject: [PATCH 04/17] filterc: Add small instruction conversions Signed-off-by: Felix Maurer --- filterc/bpfc.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index e0cb1a09..c3b23943 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -109,7 +109,7 @@ static int convert_cbpf(struct cbpf_program *cbpf_prog, int *addrs = NULL; size_t insns_cnt = 0; u_int i; - int code, target, imm, src_reg, dst_reg, bpf_src, err = 0; + int code, target, imm, src_reg, dst_reg, bpf_src, stack_off, err = 0; addrs = calloc(sizeof(*addrs), cbpf_prog->bf_len); if (!addrs) { @@ -255,6 +255,52 @@ static int convert_cbpf(struct cbpf_program *cbpf_prog, *insn++ = BPF_JA_INSN(code, target); break; + /* RET_K is remaped into 2 insns. RET_A case doesn't need an + * extra mov as BPF_REG_0 is already mapped into BPF_REG_A. + */ + case BPF_RET | BPF_A: + case BPF_RET | BPF_K: + if (BPF_RVAL(fp->code) == BPF_K) + *insn++ = BPF_MOV32_IMM(BPF_REG_0, fp->k); + + *insn++ = BPF_EXIT_INSN(); + break; + + /* Store to stack. */ + case BPF_ST: + case BPF_STX: + stack_off = fp->k * 4 + 4; + *insn = BPF_STX_MEM(BPF_W, BPF_REG_FP, BPF_CLASS(fp->code) == + BPF_ST ? BPF_REG_A : BPF_REG_X, + -stack_off); + break; + + /* Load from stack. */ + case BPF_LD | BPF_MEM: + case BPF_LDX | BPF_MEM: + stack_off = fp->k * 4 + 4; + *insn = BPF_LDX_MEM(BPF_W, BPF_CLASS(fp->code) == BPF_LD ? + BPF_REG_A : BPF_REG_X, BPF_REG_FP, + -stack_off); + break; + + /* A = K or X = K */ + case BPF_LD | BPF_IMM: + case BPF_LDX | BPF_IMM: + *insn++ = BPF_MOV32_IMM(BPF_CLASS(fp->code) == BPF_LD ? + BPF_REG_A : BPF_REG_X, fp->k); + break; + + /* X = A */ + case BPF_MISC | BPF_TAX: + *insn++ = BPF_MOV64_REG(BPF_REG_X, BPF_REG_A); + break; + + /* A = X */ + case BPF_MISC | BPF_TXA: + *insn++ = BPF_MOV64_REG(BPF_REG_A, BPF_REG_X); + break; + /* Unknown instruction. */ default: bpfc_error("Unknown instruction: %d", i); From 604f0708beae1c03022cf0e7f1e222907be689bd Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Fri, 8 Sep 2023 16:47:29 +0200 Subject: [PATCH 05/17] filterc: Convert missing store instructions The store instructions need to be guarded, i.e., checked that they only access data within the packet, because we need to pass the verifier. Linux supports legacy packet access instructions that were carried over from cBPF. However, they are only supported for programs where the context is an skb, not for XDP programs. Therefore, rewrite legacy packet access instructions to a series of eBPF instructions. Document register usage for the future. We prefer caller-saved registers at the moment because they don't need to be saved, i.e., pushed to the stack, in the JITed function, and we don't call into other functions anyways. Signed-off-by: Felix Maurer --- filterc/bpfc.c | 105 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 10 deletions(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index c3b23943..a9d2e9dc 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -98,8 +99,28 @@ struct ebpf_program { * (1) convert to check the resulting length and calculate jump offsets * (2) do the conversion again and store the result with the correct jump * offsets. + * + * The resulting program uses the following registers (defined here and in + * filter.h): + * r0: Register A of cBPF, return value + * r1: Register X of cBPF + * r2: Temporary register for calculations + * r3: Keeps xdp_md.data for guards + * r4: Keeps xdp_md.data_end for guards + * + * We can freely use any register (caller-saved and callee-saved) right now. + * Caller-saved registers (r0-r5) can be used because we are not calling into + * any other functions; callee-saved registers (r6-r9) can be used because the + * JIT will take of saving them for us. If we start calling other functions, we + * need to have X, data and data_end in callee-saved registers to persist them + * across calls. */ +#undef BPF_REG_X /* already defined differently in filter.h */ +#define BPF_REG_X BPF_REG_1 +#define BPF_REG_DP BPF_REG_3 /* data ptr */ +#define BPF_REG_DEP BPF_REG_4 /* data end ptr */ + static int convert_cbpf(struct cbpf_program *cbpf_prog, struct ebpf_program *prog) { @@ -110,6 +131,7 @@ static int convert_cbpf(struct cbpf_program *cbpf_prog, size_t insns_cnt = 0; u_int i; int code, target, imm, src_reg, dst_reg, bpf_src, stack_off, err = 0; + int match_insn = -1, nomatch_insn = -1; addrs = calloc(sizeof(*addrs), cbpf_prog->bf_len); if (!addrs) { @@ -123,13 +145,13 @@ static int convert_cbpf(struct cbpf_program *cbpf_prog, fp = cbpf_prog->bf_insns; if (first_insn) { - /* All programs must keep CTX in callee saved BPF_REG_CTX. - * In eBPF case it's done by the compiler, here we need to - * do this ourself. Initial CTX is present in BPF_REG_ARG1. - */ - *new_insn++ = BPF_MOV64_REG(BPF_REG_CTX, BPF_REG_ARG1); + /* Load data and data_end into registers */ + *new_insn++ = BPF_LDX_MEM(BPF_W, BPF_REG_DP, BPF_REG_ARG1, + offsetof(struct xdp_md, data)); + *new_insn++ = BPF_LDX_MEM(BPF_W, BPF_REG_DEP, BPF_REG_ARG1, + offsetof(struct xdp_md, data_end)); } else { - new_insn += 1; + new_insn += 2; } for (i = 0; i < cbpf_prog->bf_len; fp++, i++) { @@ -184,8 +206,9 @@ static int convert_cbpf(struct cbpf_program *cbpf_prog, #define BPF_JMP_INSN(insn_code, dst, src, immv, target) ({ \ int32_t off; \ \ - if (target >= (int)cbpf_prog->bf_len || target < 0) { \ - bpfc_error("insn %d: Invalid jump target", i);\ + if (target >= (int)cbpf_prog->bf_len || \ + (first_insn && target < 0)) { \ + bpfc_error("insn %d: Invalid jump target", i); \ err = EINVAL; \ goto out; \ } \ @@ -260,12 +283,65 @@ static int convert_cbpf(struct cbpf_program *cbpf_prog, */ case BPF_RET | BPF_A: case BPF_RET | BPF_K: - if (BPF_RVAL(fp->code) == BPF_K) - *insn++ = BPF_MOV32_IMM(BPF_REG_0, fp->k); + if (BPF_RVAL(fp->code) == BPF_K) { + if (fp->k == 0) { + nomatch_insn = i; + imm = XDP_DROP; + } else { + match_insn = i; + imm = XDP_PASS; + } + *insn++ = BPF_MOV32_IMM(BPF_REG_0, imm); + } *insn++ = BPF_EXIT_INSN(); break; + case BPF_LD | BPF_ABS | BPF_W: + case BPF_LD | BPF_ABS | BPF_H: + case BPF_LD | BPF_ABS | BPF_B: + case BPF_LD | BPF_IND | BPF_W: + case BPF_LD | BPF_IND | BPF_H: + case BPF_LD | BPF_IND | BPF_B: + //case ldxb 4*([]&0xf) + case BPF_LDX | BPF_MSH | BPF_B: + dst_reg = 0; + + if (BPF_CLASS(fp->code) == BPF_LDX) + dst_reg = BPF_REG_X; + else + dst_reg = BPF_REG_A; + + *insn++ = BPF_MOV64_REG(dst_reg, BPF_REG_DP); + if (BPF_MODE(fp->code) == BPF_IND) + *insn++ = BPF_ALU64_REG(BPF_ADD, dst_reg, BPF_REG_X); + + // Guard packet access + int sz = 0; + if (BPF_SIZE(fp->code) == BPF_B) + sz = 1; + else if (BPF_SIZE(fp->code) == BPF_H) + sz = 2; + else if (BPF_SIZE(fp->code) == BPF_W) + sz = 4; + *insn++ = BPF_MOV64_REG(BPF_REG_TMP, dst_reg); + *insn++ = BPF_ALU64_IMM(BPF_ADD, BPF_REG_TMP, fp->k + sz); + *insn++ = BPF_JMP_INSN(BPF_JMP | BPF_X | BPF_JGT, + BPF_REG_TMP, BPF_REG_DEP, 0, + (nomatch_insn != -1 ? nomatch_insn : 0)); + + *insn++ = BPF_LDX_MEM(BPF_SIZE(fp->code), dst_reg, dst_reg, fp->k); + if (sz > 1) + *insn++ = BPF_ENDIAN(BPF_FROM_BE, dst_reg, sz * 8); + + if (BPF_MODE(fp->code) == BPF_MSH) { + /* dst &= 0xf */ + *insn++ = BPF_ALU32_IMM(BPF_AND, dst_reg, 0xf); + /* dst <<= 2 */ + *insn++ = BPF_ALU32_IMM(BPF_LSH, dst_reg, 2); + } + break; + /* Store to stack. */ case BPF_ST: case BPF_STX: @@ -318,6 +394,15 @@ static int convert_cbpf(struct cbpf_program *cbpf_prog, insns_cnt = new_insn - first_insn; if (!first_insn) { + // Checking prerequisites for second pass + if (match_insn == -1 || nomatch_insn == -1) { + bpfc_error("Failed to detect match and no match return" \ + " instructions. Most likely, the cBPF code" \ + " does not return both as immediate values."); + err = -EINVAL; + goto out; + } + first_insn = calloc(sizeof(*first_insn), insns_cnt); if (!new_insn) { bpfc_error("Failed to allocate memory for eBPF instructions"); From af30fb368b67bfb1f4de1a59dcb2658929bccc88 Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Wed, 14 Dec 2022 19:06:26 +0100 Subject: [PATCH 06/17] filterc: Write structure for ELF file We can now write something that is a valid ELF file, although not containing anything useful yet. The code and BTF information will be added in the next commits. Signed-off-by: Felix Maurer --- filterc/Makefile | 2 +- filterc/bpfc.c | 291 +++++++++++++++++++++++++++++++++++++++++++++- filterc/bpfc.h | 1 + filterc/filterc.c | 11 +- 4 files changed, 302 insertions(+), 3 deletions(-) diff --git a/filterc/Makefile b/filterc/Makefile index fb7e26d8..6acf2ae7 100644 --- a/filterc/Makefile +++ b/filterc/Makefile @@ -7,7 +7,7 @@ USER_GEN := $(EXTRA_BUILD) EXTRA_USER_DEPS := $(USER_GEN) LIB_DIR = ../lib -USER_LIBS = $(EXTRA_BUILD) -lpcap +USER_LIBS = $(EXTRA_BUILD) -lpcap -lelf include $(LIB_DIR)/common.mk diff --git a/filterc/bpfc.c b/filterc/bpfc.c index a9d2e9dc..d1128900 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0 */ #include +#include +#include #include #include #include @@ -9,7 +11,7 @@ #include #include -#include +#include // We need the bpf_insn and bpf_program definitions from libpcap, but they // conflict with the Linux/libbpf definitions. Rename the libpcap structs to @@ -21,6 +23,7 @@ #undef bpf_insn #undef bpf_program +#include "util.h" #include "filter.h" #include "bpfc.h" @@ -446,6 +449,292 @@ struct ebpf_program *ebpf_program_from_cbpf(struct cbpf_program *cbpf_prog) return NULL; } +struct table { + void *data; + size_t len; +}; + +static struct table *strtab_init() +{ + struct table *tab = calloc(sizeof(struct table), 1); + if (!tab) + return NULL; + + tab->data = calloc(sizeof(char), 1); + if (!tab->data) { + free (tab); + return NULL; + } + + tab->len = 1; + return tab; +} + +static struct table *symtab_init() +{ + Elf64_Sym *sym; + + struct table *tab = calloc(sizeof(struct table), 1); + if (!tab) + return NULL; + + sym = calloc(sizeof(*sym), 1); + if (!sym) { + free (tab); + return NULL; + } + + tab->data = sym; + tab->len = sizeof(*sym); + return tab; +} + +static int strtab_add(struct table *strtab, char *str) +{ + size_t add_len = strlen(str) + 1; + size_t new_len = strtab->len + add_len; + size_t off = strtab->len; + char *new_data = NULL; + + new_data = realloc(strtab->data, new_len); + if (!new_data) + return -errno; + + strncpy(new_data + off, str, add_len); + new_data[new_len - 1] = 0; + + strtab->data = new_data; + strtab->len = new_len; + + return off; +} + +static void table_free(struct table *tab) +{ + free(tab->data); + free(tab); +} + +static Elf_Scn *add_elf_sec(Elf *elf, struct table *strtab, char *name) +{ + Elf_Scn *scn; + Elf64_Shdr *shdr; + int off; + + scn = elf_newscn(elf); + if (!scn) + return NULL; + + shdr = elf64_getshdr(scn); + if (!shdr) + return NULL; + + off = strtab_add(strtab, name); + if (off < 0) + return NULL; + + shdr->sh_name = off; + + return scn; +} + +static Elf_Scn *add_elf_strtab(Elf *elf, struct table *strtab) +{ + Elf_Scn *scn; + Elf64_Shdr *shdr; + + scn = add_elf_sec(elf, strtab, ".strtab"); + if (!scn) + return NULL; + + shdr = elf64_getshdr(scn); + if (!shdr) + return NULL; + + shdr->sh_type = SHT_STRTAB; + shdr->sh_addralign = 1; + //shdr->sh_flags = SHF_STRINGS; + //shdr->sh_offset = 0; + //shdr->sh_link = 0; + //shdr->sh_info = 0; + //shdr->sh_entsize = 0; + + return scn; +} + +static int finalize_elf_strtab(Elf *elf, Elf_Scn *scn, struct table *strtab) +{ + Elf64_Ehdr *elf_hdr; + Elf64_Shdr *shdr; + Elf_Data *data; + + shdr = elf64_getshdr(scn); + if (!shdr) + return EINVAL; + + shdr->sh_size = strtab->len; + + data = elf_newdata(scn); + if (!data) + return EINVAL; + + data->d_align = 1; + data->d_off = 0LL; + data->d_buf = strtab->data; + data->d_type = ELF_T_BYTE; + data->d_size = strtab->len; + + elf_hdr = elf64_getehdr(elf); + if (!elf_hdr) + return EINVAL; + + elf_hdr->e_shstrndx = elf_ndxscn(scn); + return 0; +} + +static Elf_Scn *add_elf_symtab(Elf *elf, struct table *strtab, + struct table *symtab, int strtab_ndx) +{ + Elf_Scn *scn; + Elf64_Shdr *shdr; + Elf_Data *data; + + scn = add_elf_sec(elf, strtab, ".symtab"); + if (!scn) + return NULL; + + shdr = elf64_getshdr(scn); + if (!shdr) + return NULL; + + shdr->sh_type = SHT_SYMTAB; + shdr->sh_addralign = 8; + shdr->sh_size = symtab->len; + shdr->sh_link = strtab_ndx; + // sh_info should be the number of local symbols, but why? elfutils + // does not even have a warnign for this, just binutils. + //shdr->sh_info = symtab->len / sizeof(Elf64_Sym); + shdr->sh_entsize = sizeof(Elf64_Sym); + + data = elf_newdata(scn); + if (!data) + return NULL; + + data->d_align = 8; + data->d_off = 0LL; + data->d_buf = symtab->data; + data->d_type = ELF_T_BYTE; + data->d_size = symtab->len; + + return scn; +} + +int ebpf_program_write_elf(__unused struct ebpf_program *prog, char *filename) +{ + int err = 0, fd = -1; + Elf *elf = NULL; + Elf_Scn *scn, *scn_strtab; + Elf64_Ehdr *elf_hdr; + struct table *strtab = NULL, *symtab = NULL; + + if (elf_version(EV_CURRENT) == EV_NONE) { + err = EINVAL; + bpfc_error("libelf initialization failed"); + goto out; + } + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd < 0) { + err = errno; + bpfc_error("Failed to create '%s': %d", filename, err); + goto out; + } + + elf = elf_begin(fd, ELF_C_WRITE, NULL); + if (!elf) { + err = EINVAL; + bpfc_error("Failed to create ELF object"); + goto out; + } + + /* ELF header */ + elf_hdr = elf64_newehdr(elf); + if (!elf_hdr) { + err = EINVAL; + bpfc_error("Failed to create ELF header"); + goto out; + } + + elf_hdr->e_machine = EM_BPF; + elf_hdr->e_type = ET_REL; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + elf_hdr->e_ident[EI_DATA] = ELFDATA2LSB; +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + elf_hdr->e_ident[EI_DATA] = ELFDATA2MSB; +#else +#error "Unknown __BYTE_ORDER__" +#endif + + strtab = strtab_init(); + if (!strtab) { + err = EINVAL; + bpfc_error("Failed to initialize strtab"); + goto out; + } + scn_strtab = add_elf_strtab(elf, strtab); + if (!scn_strtab) { + bpfc_error("Failed to add STRTAB section to ELF object"); + goto out; + } + + symtab = symtab_init(); + if (!strtab) { + err = EINVAL; + bpfc_error("Failed to initialize symtab"); + goto out; + } + + scn = add_elf_symtab(elf, strtab, symtab, elf_ndxscn(scn_strtab)); + if (!scn) { + bpfc_error("Failed to add SYMTAB section to ELF object"); + goto out; + } + + err = finalize_elf_strtab(elf, scn_strtab, strtab); + if (err) { + bpfc_error("Failed to finalize strtab"); + goto out; + } + + /* Finalize ELF layout */ + if (elf_update(elf, ELF_C_NULL) < 0) { + err = EINVAL; + bpfc_error("Failed to finalize ELF layout: %s", + elf_errmsg(elf_errno())); + goto out; + } + + /* Write out final ELF contents */ + if (elf_update(elf, ELF_C_WRITE) < 0) { + err = EINVAL; + bpfc_error("Failed to write ELF contents: %s", + elf_errmsg(elf_errno())); + goto out; + } + +out: + if (strtab) + table_free(strtab); + if (symtab) + table_free(symtab); + if (elf) + elf_end(elf); + if (fd >= 0) + close(fd); + + return err; +} + void ebpf_program_dump(struct ebpf_program *prog) { size_t i; diff --git a/filterc/bpfc.h b/filterc/bpfc.h index 61e96010..3e8a67a6 100644 --- a/filterc/bpfc.h +++ b/filterc/bpfc.h @@ -14,6 +14,7 @@ void cbpf_program_dump(struct cbpf_program*); void cbpf_program_free(struct cbpf_program*); struct ebpf_program *ebpf_program_from_cbpf(struct cbpf_program*); +int ebpf_program_write_elf(struct ebpf_program*, char *); void ebpf_program_dump(struct ebpf_program*); void ebpf_program_free(struct ebpf_program*); diff --git a/filterc/filterc.c b/filterc/filterc.c index 6e29da39..0d4f697c 100644 --- a/filterc/filterc.c +++ b/filterc/filterc.c @@ -33,7 +33,7 @@ int main(int argc, char **argv) { struct cbpf_program *cbpf_prog = NULL; struct ebpf_program *ebpf_prog = NULL; - int rc = EXIT_FAILURE; + int err, rc = EXIT_FAILURE; if (parse_cmdline_args(argc, argv, filterc_options, &cfg_filteropt, sizeof(cfg_filteropt), PROG_NAME, PROG_NAME, @@ -59,6 +59,15 @@ int main(int argc, char **argv) printf("\n"); ebpf_program_dump(ebpf_prog); + printf("\nWriting BPF object file (ELF)\n"); + err = ebpf_program_write_elf(ebpf_prog, cfg_filteropt.output); + if (err) { + pr_warn("Failed to write BPF object in ELF format: %s\n", + bpfc_geterr()); + rc = err; + goto out; + } + rc = EXIT_SUCCESS; out: From fbe30efabe1a9993d208eeee4d1f91534dd1f847 Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Thu, 15 Dec 2022 13:52:25 +0100 Subject: [PATCH 07/17] filterc: Write bpf insns to ELF file Signed-off-by: Felix Maurer --- filterc/bpfc.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index d1128900..c8dbd265 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -509,6 +509,25 @@ static int strtab_add(struct table *strtab, char *str) return off; } +static int symtab_add(struct table *symtab, Elf64_Sym *sym) +{ + size_t add_len = sizeof(*sym); + size_t new_len = symtab->len + add_len; + size_t off = symtab->len; + char *new_data = NULL; + + new_data = realloc(symtab->data, new_len); + if (!new_data) + return -errno; + + memcpy(new_data + off, sym, add_len); + + symtab->data = new_data; + symtab->len = new_len; + + return 0; +} + static void table_free(struct table *tab) { free(tab->data); @@ -629,7 +648,57 @@ static Elf_Scn *add_elf_symtab(Elf *elf, struct table *strtab, return scn; } -int ebpf_program_write_elf(__unused struct ebpf_program *prog, char *filename) +static Elf_Scn *add_elf_bpf_prog(Elf *elf, struct table *strtab, + struct table *symtab, + struct ebpf_program *prog) +{ + Elf_Scn *scn; + Elf64_Shdr *shdr; + Elf_Data *data; + Elf64_Sym sym = {0}; + int off, err; + int prog_size = prog->insns_cnt * 8; + + scn = add_elf_sec(elf, strtab, "xdp"); + if (!scn) + return NULL; + + + off = strtab_add(strtab, "filterc_prog"); + if (off < 0) + return NULL; + + sym.st_name = off; + sym.st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC); + sym.st_shndx = elf_ndxscn(scn); + sym.st_size = prog_size; + err = symtab_add(symtab, &sym); + if (err) + return NULL; + + shdr = elf64_getshdr(scn); + if (!shdr) + return NULL; + + shdr->sh_type = SHT_PROGBITS; + shdr->sh_addralign = 8; + shdr->sh_size = prog_size; + shdr->sh_flags = SHF_ALLOC | SHF_EXECINSTR; + + data = elf_newdata(scn); + if (!data) + return NULL; + + data->d_align = 8; + data->d_off = 0LL; + data->d_buf = prog->insns; + data->d_type = ELF_T_BYTE; + data->d_size = prog_size; + + return scn; +} + +int ebpf_program_write_elf(struct ebpf_program *prog, char *filename) { int err = 0, fd = -1; Elf *elf = NULL; @@ -694,6 +763,13 @@ int ebpf_program_write_elf(__unused struct ebpf_program *prog, char *filename) goto out; } + scn = add_elf_bpf_prog(elf, strtab, symtab, prog); + if (!scn) { + err = EINVAL; + bpfc_error("Failed to add BPF program section to ELF object"); + goto out; + } + scn = add_elf_symtab(elf, strtab, symtab, elf_ndxscn(scn_strtab)); if (!scn) { bpfc_error("Failed to add SYMTAB section to ELF object"); From be1cf2ed018bd8efe9c25d356af2988275fabe15 Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Thu, 15 Dec 2022 15:00:07 +0100 Subject: [PATCH 08/17] filterc: Add BTF info to ELF file Signed-off-by: Felix Maurer --- filterc/bpfc.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 221 insertions(+), 3 deletions(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index c8dbd265..640ab284 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -12,6 +12,7 @@ #include #include +#include // We need the bpf_insn and bpf_program definitions from libpcap, but they // conflict with the Linux/libbpf definitions. Rename the libpcap structs to @@ -23,12 +24,15 @@ #undef bpf_insn #undef bpf_program +#include "logging.h" #include "util.h" + #include "filter.h" #include "bpfc.h" #define BPFC_DEFAULT_SNAP_LEN 262144 -#define BPFC_ERRBUFF_SZ 256 +#define BPFC_ERRBUFF_SZ 512 + STRERR_BUFSIZE +#define BPFC_PROG_SYM_NAME "filterc_prog" static char bpfc_errbuff[BPFC_ERRBUFF_SZ + 1]; @@ -663,8 +667,7 @@ static Elf_Scn *add_elf_bpf_prog(Elf *elf, struct table *strtab, if (!scn) return NULL; - - off = strtab_add(strtab, "filterc_prog"); + off = strtab_add(strtab, BPFC_PROG_SYM_NAME); if (off < 0) return NULL; @@ -698,6 +701,204 @@ static Elf_Scn *add_elf_bpf_prog(Elf *elf, struct table *strtab, return scn; } +/** + * Populates the load opts with the necessary BTF data to allow loading of the + * bare instructions. The structure of the BTF information is the same as from + * a plain XDP program, i.e., similar to the following (with different order): + * + * [1] PTR '(anon)' type_id=2 + * [2] STRUCT 'xdp_md' size=24 vlen=6 + * 'data' type_id=3 bits_offset=0 + * 'data_end' type_id=3 bits_offset=32 + * 'data_meta' type_id=3 bits_offset=64 + * 'ingress_ifindex' type_id=3 bits_offset=96 + * 'rx_queue_index' type_id=3 bits_offset=128 + * 'egress_ifindex' type_id=3 bits_offset=160 + * [3] TYPEDEF '__u32' type_id=4 + * [4] INT 'unsigned int' size=4 bits_offset=0 nr_bits=32 encoding=(none) + * [5] FUNC_PROTO '(anon)' ret_type_id=6 vlen=1 + * 'ctx' type_id=1 + * [6] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED + * [7] FUNC 'prog' type_id=5 linkage=global + */ +struct btf *build_xdp_btf() +{ + char errmsg[STRERR_BUFSIZE]; + int err = 0; + struct btf *btf; + + btf = btf__new_empty(); + if (!btf) { + err = errno; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create btf structure: %s (%d)", + errmsg, err); + goto out; + } + + int unsig_int_id = btf__add_int(btf, "unsigned int", 4, 0); + if (unsig_int_id < 0) { + err = unsig_int_id; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'unsigned int' btf: %s (%d)", + errmsg, err); + goto out; + } + + int u32_id = btf__add_typedef(btf, "__u32", unsig_int_id); + if (u32_id < 0) { + err = u32_id; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create '__u32' btf: %s (%d)", + errmsg, err); + goto out; + } + + int xdp_md_id = btf__add_struct(btf, "xdp_md", 24); + if (xdp_md_id < 0) { + err = xdp_md_id; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'xdp_md' btf: %s (%d)", + errmsg, err); + goto out; + } + + err = btf__add_field(btf, "data", u32_id, 0, 32); + if (err < 0) { + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'xdp_md' field 'data' btf: %s (%d)", + errmsg, err); + goto out; + } + + err = btf__add_field(btf, "data_end", u32_id, 32, 32); + if (err < 0) { + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'xdp_md' field 'data_end' btf: %s (%d)", + errmsg, err); + goto out; + } + + err = btf__add_field(btf, "data_meta", u32_id, 64, 32); + if (err < 0) { + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'xdp_md' field 'data_meta' btf: %s (%d)", + errmsg, err); + goto out; + } + + err = btf__add_field(btf, "ingress_ifindex", u32_id, 96, 32); + if (err < 0) { + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'xdp_md' field 'ingress_ifindex' btf: %s (%d)", + errmsg, err); + goto out; + } + + err = btf__add_field(btf, "rx_queue_index", u32_id, 128, 32); + if (err < 0) { + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'xdp_md' field 'rx_queue_index' btf: %s (%d)", + errmsg, err); + goto out; + } + + err = btf__add_field(btf, "egress_ifindex", u32_id, 160, 32); + if (err < 0) { + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'xdp_md' field 'egress_ifindex' btf: %s (%d)", + errmsg, err); + goto out; + } + + int ptr_xdp_md_id = btf__add_ptr(btf, xdp_md_id); + if (ptr_xdp_md_id < 0) { + err = ptr_xdp_md_id; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create 'xdp_md *' btf: %s (%d)", + errmsg, err); + goto out; + } + + int func_return_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED); + if (func_return_id < 0) { + err = func_return_id; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create return int btf: %s (%d)", + errmsg, err); + goto out; + } + + int func_proto_id = btf__add_func_proto(btf, func_return_id); + if (func_proto_id < 0) { + err = func_proto_id; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create func proto btf: %s (%d)", + errmsg, err); + goto out; + } + + int ctx_param_id = btf__add_func_param(btf, "ctx", ptr_xdp_md_id); + if (ctx_param_id < 0) { + err = ctx_param_id; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create ctx param btf: %s (%d)", + errmsg, err); + goto out; + } + + int xdp_prog_func_id = btf__add_func(btf, BPFC_PROG_SYM_NAME, + BTF_FUNC_GLOBAL, func_proto_id); + if (xdp_prog_func_id < 0) { + err = xdp_prog_func_id; + libbpf_strerror(err, errmsg, sizeof(errmsg)); + bpfc_error("Could not create xdp func btf: %s (%d)", + errmsg, err); + goto out; + } + + return btf; + +out: + if (btf) + btf__free(btf); + + return NULL; +} + +static Elf_Scn *add_elf_btf(Elf *elf, struct table *strtab, struct btf *btf) +{ + Elf_Scn *scn; + Elf64_Shdr *shdr; + Elf_Data *data; + __u32 btf_size = 0; + + scn = add_elf_sec(elf, strtab, ".BTF"); + if (!scn) + return NULL; + + shdr = elf64_getshdr(scn); + if (!shdr) + return NULL; + + shdr->sh_type = SHT_PROGBITS; + shdr->sh_addralign = 4; + + data = elf_newdata(scn); + if (!data) + return NULL; + + data->d_align = 4; + data->d_off = 0LL; + data->d_buf = (void *)btf__raw_data(btf, &btf_size); + data->d_type = ELF_T_BYTE; + + data->d_size = btf_size; + shdr->sh_size = btf_size; + + return scn; +} + int ebpf_program_write_elf(struct ebpf_program *prog, char *filename) { int err = 0, fd = -1; @@ -705,6 +906,7 @@ int ebpf_program_write_elf(struct ebpf_program *prog, char *filename) Elf_Scn *scn, *scn_strtab; Elf64_Ehdr *elf_hdr; struct table *strtab = NULL, *symtab = NULL; + struct btf *btf = NULL; if (elf_version(EV_CURRENT) == EV_NONE) { err = EINVAL; @@ -770,6 +972,20 @@ int ebpf_program_write_elf(struct ebpf_program *prog, char *filename) goto out; } + btf = build_xdp_btf(); + if (!btf) { + err = EINVAL; + bpfc_error("Failed to add build BTF information"); + goto out; + } + + scn = add_elf_btf(elf, strtab, btf); + if (!scn) { + err = EINVAL; + bpfc_error("Failed to add BTF section to ELF object"); + goto out; + } + scn = add_elf_symtab(elf, strtab, symtab, elf_ndxscn(scn_strtab)); if (!scn) { bpfc_error("Failed to add SYMTAB section to ELF object"); @@ -799,6 +1015,8 @@ int ebpf_program_write_elf(struct ebpf_program *prog, char *filename) } out: + if (btf) + btf__free(btf); if (strtab) table_free(strtab); if (symtab) From 6334a2cc7dfce0f14054c72fa94db90c3489a3fc Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Mon, 7 Aug 2023 12:50:58 +0200 Subject: [PATCH 09/17] filterc: Respect log levels Only print the debug output if it is actually requested with the respective command line option. Signed-off-by: Felix Maurer --- filterc/bpfc.c | 21 +++++++++++++-------- filterc/filterc.c | 4 +--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index 640ab284..bb5200c1 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -84,8 +84,12 @@ struct cbpf_program *cbpf_program_from_filter(char *filter) void cbpf_program_dump(struct cbpf_program *prog) { - printf("cBPF program (insn cnt = %d)\n", prog->bf_len); - bpf_dump(prog, 1); + unsigned int i; + + pr_info("Compiled cBPF program (insn cnt = %d)\n", prog->bf_len); + for(i = 0; i < prog->bf_len; i++) + pr_debug("%s\n", bpf_image(&prog->bf_insns[i], i)); + pr_debug("\n"); } void cbpf_program_free(struct cbpf_program *prog) @@ -1033,15 +1037,16 @@ void ebpf_program_dump(struct ebpf_program *prog) { size_t i; - printf("eBPF program (insn cnt = %lu)\n", prog->insns_cnt); + pr_info("Compiled eBPF program (insn cnt = %lu)\n", prog->insns_cnt); for (i = 0; i < prog->insns_cnt; i++) { struct bpf_insn insn = prog->insns[i]; - printf("(%03lu) code:0x%02x (m:%02x|s:%02x|c:%02x) dst:0x%01x " - "src:0x%01x off:0x%04x imm:0x%08x\n", i, insn.code, - BPF_MODE(insn.code), BPF_SIZE(insn.code), - BPF_CLASS(insn.code), insn.dst_reg, insn.src_reg, - insn.off, insn.imm); + pr_debug("(%03lu) code:0x%02x (m:%02x|s:%02x|c:%02x) dst:0x%01x " + "src:0x%01x off:0x%04x imm:0x%08x\n", i, insn.code, + BPF_MODE(insn.code), BPF_SIZE(insn.code), + BPF_CLASS(insn.code), insn.dst_reg, insn.src_reg, + insn.off, insn.imm); } + pr_debug("\n"); } void ebpf_program_free(struct ebpf_program *prog) diff --git a/filterc/filterc.c b/filterc/filterc.c index 0d4f697c..a3fdfb57 100644 --- a/filterc/filterc.c +++ b/filterc/filterc.c @@ -48,7 +48,6 @@ int main(int argc, char **argv) } cbpf_program_dump(cbpf_prog); - printf("\n"); ebpf_prog = ebpf_program_from_cbpf(cbpf_prog); if (!ebpf_prog) { @@ -56,10 +55,9 @@ int main(int argc, char **argv) goto out; } - printf("\n"); ebpf_program_dump(ebpf_prog); - printf("\nWriting BPF object file (ELF)\n"); + pr_info("Writing BPF object file (ELF)\n"); err = ebpf_program_write_elf(ebpf_prog, cfg_filteropt.output); if (err) { pr_warn("Failed to write BPF object in ELF format: %s\n", From ac835109489b307cda62eef272efa808c41cffdd Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Fri, 8 Sep 2023 18:44:15 +0200 Subject: [PATCH 10/17] filterc: Fix eBPF dumping The opcode of eBPF instructions has different meaning depending on the instruction class. Respect that when dumping the program. Signed-off-by: Felix Maurer --- filterc/bpfc.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index bb5200c1..fb96efb8 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -1040,11 +1040,25 @@ void ebpf_program_dump(struct ebpf_program *prog) pr_info("Compiled eBPF program (insn cnt = %lu)\n", prog->insns_cnt); for (i = 0; i < prog->insns_cnt; i++) { struct bpf_insn insn = prog->insns[i]; - pr_debug("(%03lu) code:0x%02x (m:%02x|s:%02x|c:%02x) dst:0x%01x " - "src:0x%01x off:0x%04x imm:0x%08x\n", i, insn.code, - BPF_MODE(insn.code), BPF_SIZE(insn.code), - BPF_CLASS(insn.code), insn.dst_reg, insn.src_reg, - insn.off, insn.imm); + pr_debug("(%03lu) code:0x%02x", i, insn.code); + if (BPF_CLASS(insn.code) == BPF_ALU || + BPF_CLASS(insn.code) == BPF_ALU64 || + BPF_CLASS(insn.code) == BPF_JMP || + BPF_CLASS(insn.code) == BPF_JMP32) { + pr_debug(" (o:%02x|s:%01x|c:%02x)", + BPF_OP(insn.code), BPF_SRC(insn.code), + BPF_CLASS(insn.code)); + } + if (BPF_CLASS(insn.code) == BPF_LD || + BPF_CLASS(insn.code) == BPF_LDX || + BPF_CLASS(insn.code) == BPF_ST || + BPF_CLASS(insn.code) == BPF_STX) { + pr_debug(" (m:%02x|s:%02x|c:%02x)", + BPF_MODE(insn.code), BPF_SIZE(insn.code), + BPF_CLASS(insn.code)); + } + pr_debug(" dst:0x%01x src:0x%01x off:0x%04x imm:0x%08x\n", + insn.dst_reg, insn.src_reg, insn.off, insn.imm); } pr_debug("\n"); } From ba5cd0d13dbfa44aa5d6f3203aa3c323efda697b Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Mon, 7 Aug 2023 13:49:52 +0200 Subject: [PATCH 11/17] filterc: Allow to write to file descriptors Add write options in a struct that can be extended in the future. The struct is ready to be used with the validation mechanisms of libbpf/libxdp but they are not validated yet, because those mechanisms are internal to the libraries at the moment. Signed-off-by: Felix Maurer --- filterc/bpfc.c | 51 +++++++++++++++++++++++++++++++++++++---------- filterc/bpfc.h | 9 ++++++++- filterc/filterc.c | 4 +++- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index fb96efb8..6b23123f 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -903,9 +903,9 @@ static Elf_Scn *add_elf_btf(Elf *elf, struct table *strtab, struct btf *btf) return scn; } -int ebpf_program_write_elf(struct ebpf_program *prog, char *filename) +int _ebpf_program_write_elf(struct ebpf_program *prog, int fd) { - int err = 0, fd = -1; + int err = 0; Elf *elf = NULL; Elf_Scn *scn, *scn_strtab; Elf64_Ehdr *elf_hdr; @@ -918,13 +918,6 @@ int ebpf_program_write_elf(struct ebpf_program *prog, char *filename) goto out; } - fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); - if (fd < 0) { - err = errno; - bpfc_error("Failed to create '%s': %d", filename, err); - goto out; - } - elf = elf_begin(fd, ELF_C_WRITE, NULL); if (!elf) { err = EINVAL; @@ -1027,12 +1020,48 @@ int ebpf_program_write_elf(struct ebpf_program *prog, char *filename) table_free(symtab); if (elf) elf_end(elf); - if (fd >= 0) - close(fd); return err; } +int ebpf_program_write_elf(struct ebpf_program *prog, + struct elf_write_opts *opts) +{ + int err, fd = -1; + + if (!opts) { + err = EINVAL; + goto out; + } + + if (opts->fd && opts->path) { + err = EINVAL; + bpfc_error("Output path and fd both set, set only one of them"); + goto out; + } else if (opts->fd) { + fd = opts->fd; + } else if (opts->path) { + fd = open(opts->path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + 0644); + if (fd < 0) { + err = errno; + bpfc_error("Failed to create '%s': %d", opts->path, err); + goto out; + } + } else { + err = EINVAL; + bpfc_error("None of output path or fd set"); + goto out; + } + + err = _ebpf_program_write_elf(prog, fd); + +out: + if (opts->path && fd >= 0) + close(fd); + return err; +} + void ebpf_program_dump(struct ebpf_program *prog) { size_t i; diff --git a/filterc/bpfc.h b/filterc/bpfc.h index 3e8a67a6..2c3931b5 100644 --- a/filterc/bpfc.h +++ b/filterc/bpfc.h @@ -1,4 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 */ +#include #ifndef _BPFC_H_ #define _BPFC_H_ @@ -7,6 +8,12 @@ struct cbpf_insn; struct cbpf_program; struct ebpf_program; +struct elf_write_opts { + size_t sz; + int fd; + char *path; +}; + char *bpfc_geterr(); struct cbpf_program *cbpf_program_from_filter(char*); @@ -14,7 +21,7 @@ void cbpf_program_dump(struct cbpf_program*); void cbpf_program_free(struct cbpf_program*); struct ebpf_program *ebpf_program_from_cbpf(struct cbpf_program*); -int ebpf_program_write_elf(struct ebpf_program*, char *); +int ebpf_program_write_elf(struct ebpf_program*, struct elf_write_opts*); void ebpf_program_dump(struct ebpf_program*); void ebpf_program_free(struct ebpf_program*); diff --git a/filterc/filterc.c b/filterc/filterc.c index a3fdfb57..d2387db3 100644 --- a/filterc/filterc.c +++ b/filterc/filterc.c @@ -58,7 +58,9 @@ int main(int argc, char **argv) ebpf_program_dump(ebpf_prog); pr_info("Writing BPF object file (ELF)\n"); - err = ebpf_program_write_elf(ebpf_prog, cfg_filteropt.output); + LIBBPF_OPTS(elf_write_opts, write_opts, + .path = cfg_filteropt.output); + err = ebpf_program_write_elf(ebpf_prog, &write_opts); if (err) { pr_warn("Failed to write BPF object in ELF format: %s\n", bpfc_geterr()); From a143052d8297999486b189833c81c08f7357f6c2 Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Tue, 1 Aug 2023 12:13:56 +0200 Subject: [PATCH 12/17] filterc: Add standalone test Add a basic test to check if a compiled filter can be loaded into the kernel and does something similar to what we expect. Signed-off-by: Felix Maurer --- filterc/.gitignore | 1 + filterc/Makefile | 2 + filterc/tests/test-filterc.sh | 7 ++ filterc/tests/test_bpfc.c | 181 ++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 filterc/tests/test-filterc.sh create mode 100644 filterc/tests/test_bpfc.c diff --git a/filterc/.gitignore b/filterc/.gitignore index 4b49348c..f3e92595 100644 --- a/filterc/.gitignore +++ b/filterc/.gitignore @@ -1 +1,2 @@ filterc +tests/test_bpfc diff --git a/filterc/Makefile b/filterc/Makefile index 6acf2ae7..437aba88 100644 --- a/filterc/Makefile +++ b/filterc/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) TOOL_NAME := filterc USER_TARGETS := filterc +TEST_TARGETS := tests/test_bpfc +TEST_FILE := tests/test-filterc.sh EXTRA_BUILD := bpfc.o USER_GEN := $(EXTRA_BUILD) diff --git a/filterc/tests/test-filterc.sh b/filterc/tests/test-filterc.sh new file mode 100644 index 00000000..e2376084 --- /dev/null +++ b/filterc/tests/test-filterc.sh @@ -0,0 +1,7 @@ +ALL_TESTS="test_bpfc_standalone" + +test_bpfc="tests/test_bpfc" + +test_bpfc_standalone() { + $test_bpfc test_standalone +} diff --git a/filterc/tests/test_bpfc.c b/filterc/tests/test_bpfc.c new file mode 100644 index 00000000..5014a67d --- /dev/null +++ b/filterc/tests/test_bpfc.c @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include + +#include +#include +#include + +#include +#include + +#include "params.h" +#include "util.h" +#include "logging.h" + +#include "../bpfc.h" + +#define PROG_NAME "test_bpfc" + +#define TEST(_tn) \ + { .name = "test_"textify(_tn), .func = do_test_##_tn, .no_cfg = true } +#define TEST_FUNC(_tn) \ + int do_test_##_tn(__unused const void *cfg, __unused const char *pin_root_path) + +#define TMP_SUFFIX ".bpf.o" + +static struct bpf_object *compile_filter(char *filter) +{ + struct cbpf_program *cbpf_prog; + struct ebpf_program *ebpf_prog; + struct bpf_object *bpf_obj; + char fname[] = "/tmp/"PROG_NAME"_XXXXXX"TMP_SUFFIX; + int err, fd; + + fd = mkstemps(fname, strlen(TMP_SUFFIX)); + if (fd < 0) { + printf("Failed to create temp file\n"); + return NULL; + } + + + cbpf_prog = cbpf_program_from_filter(filter); + if (!cbpf_prog) { + printf("Failed to compile filter.\n"); + return NULL; + } + + ebpf_prog = ebpf_program_from_cbpf(cbpf_prog); + if (!ebpf_prog) { + printf("Failed to convert cBPF to eBPF: %s\n", bpfc_geterr()); + return NULL; + } + + LIBBPF_OPTS(elf_write_opts, write_opts, + .fd = fd); + err = ebpf_program_write_elf(ebpf_prog, &write_opts); + if (err) { + printf("Failed to write BPF object in ELF format: %s\n", + bpfc_geterr()); + return NULL; + } + + bpf_obj = bpf_object__open(fname); + if (!bpf_obj) { + err = errno; + printf("Failed to open BPF object: %s (%d)\n", strerror(err), err); + return NULL; + } + + err = unlink(fname); + if (err) { + err = errno; + printf("Failed to unlink temp file '%s': %s (%d)\n", + fname, strerror(err), err); + return NULL; + } + + return bpf_obj; +} + +int build_udp_packet(void *pkt, int len, int dst_port) +{ + struct ethhdr *eh = pkt; + struct iphdr *iph = (struct iphdr *)(eh + 1); + struct udphdr *udph = (struct udphdr *)(iph + 1); + + memset(pkt, 0, len); + + eh->h_proto = htons(ETH_P_IP); + + iph->protocol = IPPROTO_UDP; + iph->ihl = 5; + + udph->source = htons(54321); + udph->dest = htons(dst_port); + + return 0; +} + +TEST_FUNC(standalone) +{ + struct bpf_object *bpf_obj; + struct bpf_program *bpf_prog; + int err, prog_fd; + int len = 128; + void *pkt = malloc(len); + + bpf_obj = compile_filter("udp port 53"); + if (!bpf_obj) { + printf("Failed to compile and open filter\n"); + return 1; + } + + err = bpf_object__load(bpf_obj); + if (err) { + printf("Failed to load bpf object\n"); + return 1; + } + + bpf_prog = bpf_object__find_program_by_name(bpf_obj, "filterc_prog"); + if (!bpf_prog) { + printf("Failed to find bpf program in object\n"); + return 1; + } + prog_fd = bpf_program__fd(bpf_prog); + + err = build_udp_packet(pkt, len, 53); + if (err) { + printf("Failed to build packet (%d)\n", err); + return 1; + } + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, test_opts, + .data_in = pkt, + .data_size_in = len); + err = bpf_prog_test_run_opts(prog_fd, &test_opts); + if (err) { + printf("Failed to run bpf prog (%d)\n", err); + return 1; + } + if (test_opts.retval != XDP_PASS) { + printf("Unexpected return value %d, expected %d\n", + test_opts.retval, XDP_PASS); + return 1; + } + + err = build_udp_packet(pkt, len, 123); + if (err) { + printf("Failed to build packet (%d)\n", err); + return 1; + } + err = bpf_prog_test_run_opts(prog_fd, &test_opts); + if (err) { + printf("Failed to run bpf prog (%d)\n", err); + return 1; + } + if (test_opts.retval != XDP_DROP) { + printf("Unexpected return value %d, expected %d\n", + test_opts.retval, XDP_DROP); + return 1; + } + + free(pkt); + return 0; +} + +static const struct prog_command cmds[] = { + TEST(standalone), + END_COMMANDS +}; + +int main(int argc, char **argv) +{ + check_bpf_environ(); + set_log_level(LOG_VERBOSE); + + + if (argc > 1) + return dispatch_commands(argv[1], argc - 1, argv + 1, cmds, + 0, PROG_NAME, false); + return -1; +} From db19ef003f09ede7e6d02e54c0a30762d3dc5daf Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Mon, 7 Aug 2023 17:07:17 +0200 Subject: [PATCH 13/17] filterc: Allow to set program name Add an option to configure the program name in the BPF object file. Signed-off-by: Felix Maurer --- filterc/bpfc.c | 24 ++++++++++++++---------- filterc/bpfc.h | 3 +++ filterc/filterc.c | 7 +++++++ filterc/tests/test_bpfc.c | 6 ++++-- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index 6b23123f..39d095a0 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -32,7 +32,6 @@ #define BPFC_DEFAULT_SNAP_LEN 262144 #define BPFC_ERRBUFF_SZ 512 + STRERR_BUFSIZE -#define BPFC_PROG_SYM_NAME "filterc_prog" static char bpfc_errbuff[BPFC_ERRBUFF_SZ + 1]; @@ -658,7 +657,8 @@ static Elf_Scn *add_elf_symtab(Elf *elf, struct table *strtab, static Elf_Scn *add_elf_bpf_prog(Elf *elf, struct table *strtab, struct table *symtab, - struct ebpf_program *prog) + struct ebpf_program *prog, + struct elf_write_opts *opts) { Elf_Scn *scn; Elf64_Shdr *shdr; @@ -671,7 +671,7 @@ static Elf_Scn *add_elf_bpf_prog(Elf *elf, struct table *strtab, if (!scn) return NULL; - off = strtab_add(strtab, BPFC_PROG_SYM_NAME); + off = strtab_add(strtab, opts->progname); if (off < 0) return NULL; @@ -725,7 +725,7 @@ static Elf_Scn *add_elf_bpf_prog(Elf *elf, struct table *strtab, * [6] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED * [7] FUNC 'prog' type_id=5 linkage=global */ -struct btf *build_xdp_btf() +struct btf *build_xdp_btf(struct elf_write_opts *opts) { char errmsg[STRERR_BUFSIZE]; int err = 0; @@ -851,8 +851,8 @@ struct btf *build_xdp_btf() goto out; } - int xdp_prog_func_id = btf__add_func(btf, BPFC_PROG_SYM_NAME, - BTF_FUNC_GLOBAL, func_proto_id); + int xdp_prog_func_id = btf__add_func(btf, opts->progname, BTF_FUNC_GLOBAL, + func_proto_id); if (xdp_prog_func_id < 0) { err = xdp_prog_func_id; libbpf_strerror(err, errmsg, sizeof(errmsg)); @@ -903,7 +903,8 @@ static Elf_Scn *add_elf_btf(Elf *elf, struct table *strtab, struct btf *btf) return scn; } -int _ebpf_program_write_elf(struct ebpf_program *prog, int fd) +int _ebpf_program_write_elf(struct ebpf_program *prog, int fd, + struct elf_write_opts *opts) { int err = 0; Elf *elf = NULL; @@ -962,14 +963,14 @@ int _ebpf_program_write_elf(struct ebpf_program *prog, int fd) goto out; } - scn = add_elf_bpf_prog(elf, strtab, symtab, prog); + scn = add_elf_bpf_prog(elf, strtab, symtab, prog, opts); if (!scn) { err = EINVAL; bpfc_error("Failed to add BPF program section to ELF object"); goto out; } - btf = build_xdp_btf(); + btf = build_xdp_btf(opts); if (!btf) { err = EINVAL; bpfc_error("Failed to add build BTF information"); @@ -1054,7 +1055,10 @@ int ebpf_program_write_elf(struct ebpf_program *prog, goto out; } - err = _ebpf_program_write_elf(prog, fd); + if (!opts->progname) + opts->progname = BPFC_PROG_SYM_NAME; + + err = _ebpf_program_write_elf(prog, fd, opts); out: if (opts->path && fd >= 0) diff --git a/filterc/bpfc.h b/filterc/bpfc.h index 2c3931b5..71d169d9 100644 --- a/filterc/bpfc.h +++ b/filterc/bpfc.h @@ -4,6 +4,8 @@ #ifndef _BPFC_H_ #define _BPFC_H_ +#define BPFC_PROG_SYM_NAME "filterc_prog" + struct cbpf_insn; struct cbpf_program; struct ebpf_program; @@ -12,6 +14,7 @@ struct elf_write_opts { size_t sz; int fd; char *path; + char *progname; }; char *bpfc_geterr(); diff --git a/filterc/filterc.c b/filterc/filterc.c index d2387db3..8db26915 100644 --- a/filterc/filterc.c +++ b/filterc/filterc.c @@ -10,6 +10,7 @@ static const struct filteropt { char *output; + char *progname; char *filter; } filteropt_defaults = { }; @@ -21,6 +22,11 @@ static struct prog_option filterc_options[] = { .required = true, .metavar = "", .help = "Output compiled object to "), + DEFINE_OPTION("program-name", OPT_STRING, struct filteropt, progname, + .short_opt = 'n', + .metavar = "", + .help = "Name of the program in the BPF object file "\ + "(default: " BPFC_PROG_SYM_NAME ")"), DEFINE_OPTION("filter", OPT_STRING, struct filteropt, filter, .required = true, .positional = true, @@ -59,6 +65,7 @@ int main(int argc, char **argv) pr_info("Writing BPF object file (ELF)\n"); LIBBPF_OPTS(elf_write_opts, write_opts, + .progname = cfg_filteropt.progname, .path = cfg_filteropt.output); err = ebpf_program_write_elf(ebpf_prog, &write_opts); if (err) { diff --git a/filterc/tests/test_bpfc.c b/filterc/tests/test_bpfc.c index 5014a67d..9dffc7cd 100644 --- a/filterc/tests/test_bpfc.c +++ b/filterc/tests/test_bpfc.c @@ -23,6 +23,7 @@ int do_test_##_tn(__unused const void *cfg, __unused const char *pin_root_path) #define TMP_SUFFIX ".bpf.o" +#define TEST_PROG_NAME "filterc_test_prog" static struct bpf_object *compile_filter(char *filter) { @@ -52,7 +53,8 @@ static struct bpf_object *compile_filter(char *filter) } LIBBPF_OPTS(elf_write_opts, write_opts, - .fd = fd); + .fd = fd, + .progname = TEST_PROG_NAME); err = ebpf_program_write_elf(ebpf_prog, &write_opts); if (err) { printf("Failed to write BPF object in ELF format: %s\n", @@ -117,7 +119,7 @@ TEST_FUNC(standalone) return 1; } - bpf_prog = bpf_object__find_program_by_name(bpf_obj, "filterc_prog"); + bpf_prog = bpf_object__find_program_by_name(bpf_obj, TEST_PROG_NAME); if (!bpf_prog) { printf("Failed to find bpf program in object\n"); return 1; From ef7d95e34e0b34c167802ac8bbaa65b88100569d Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Mon, 7 Aug 2023 17:23:46 +0200 Subject: [PATCH 14/17] filterc: Allow to create linkable BPF objects If a BPF program should be linked into other programs, it requires some differences in the ELF object file: - The program needs to be in the text section Add an option to set how the BPF object file should look like. Also add a parameter to filterc to create linkable objects. Signed-off-by: Felix Maurer --- filterc/bpfc.c | 155 +++++++++++++++++++++++++++++++++++++++++++++- filterc/bpfc.h | 7 +++ filterc/filterc.c | 9 ++- 3 files changed, 169 insertions(+), 2 deletions(-) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index 39d095a0..df3d6453 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -667,7 +667,7 @@ static Elf_Scn *add_elf_bpf_prog(Elf *elf, struct table *strtab, int off, err; int prog_size = prog->insns_cnt * 8; - scn = add_elf_sec(elf, strtab, "xdp"); + scn = add_elf_sec(elf, strtab, opts->secname); if (!scn) return NULL; @@ -903,6 +903,147 @@ static Elf_Scn *add_elf_btf(Elf *elf, struct table *strtab, struct btf *btf) return scn; } +/* Header definitions from libbpf_internal.h */ +struct btf_ext_header { + __u16 magic; + __u8 version; + __u8 flags; + __u32 hdr_len; + + /* All offsets are in bytes relative to the end of this header */ + __u32 func_info_off; + __u32 func_info_len; + __u32 line_info_off; + __u32 line_info_len; +}; + +struct btf_ext_info_sec { + __u32 sec_name_off; + __u32 num_info; + /* Followed by num_info * record_size number of bytes */ + __u8 data[]; +}; + +static Elf_Scn *add_elf_btf_ext(Elf *elf, struct table *strtab, struct btf *btf, + struct elf_write_opts *opts) +{ + Elf_Scn *scn; + Elf64_Shdr *shdr; + Elf_Data *data; + __u32 size, fi_size, li_size; + void *buf; + char errmsg[STRERR_BUFSIZE]; + int sec_name_off; + + struct btf_ext_header *hdr; + __u32 *rec_size; + struct btf_ext_info_sec *func_info_sec, *line_info_sec; + struct bpf_func_info *func_info; + struct bpf_line_info *line_info; + + fi_size = sizeof(__u32) /* record size for func_info */ + + sizeof(*func_info_sec) /* BTF.ext info for one section */ + + sizeof(*func_info); /* one record of func_info */ + li_size = sizeof(__u32) /* record size for line_info */ + + sizeof(*line_info_sec) /* BTF.ext info for one section */ + + sizeof(*line_info); /* one record of line_info */ + size = sizeof(*hdr) /* BTF.ext header */ + + fi_size /* size of func_info */ + + li_size; /* size of line_info */ + buf = calloc(size, 1); + if (!buf) + goto err_out; + + hdr = buf; + hdr->magic = BTF_MAGIC; + hdr->version = 0x01; + hdr->flags = 0x00; + hdr->hdr_len = sizeof(*hdr); + hdr->func_info_off = 0; + hdr->func_info_len = fi_size; + hdr->line_info_off = fi_size; + hdr->line_info_len = li_size; + + /* func_info section */ + rec_size = (__u32*) (hdr + 1); + *rec_size = sizeof(*func_info); + + // Add section name as string + sec_name_off = btf__add_str(btf, opts->secname); + if (sec_name_off < 0) { + libbpf_strerror(sec_name_off, errmsg, sizeof(errmsg)); + bpfc_error("Could not add section name to BTF: %s (%d)", errmsg, + sec_name_off); + goto err_out; + } + + func_info_sec = (struct btf_ext_info_sec*) (rec_size + 1); + func_info_sec->sec_name_off = sec_name_off; + func_info_sec->num_info = 1; + + int type_id = btf__find_by_name_kind(btf, opts->progname, BTF_KIND_FUNC); + if (type_id < 0) { + libbpf_strerror(type_id, errmsg, sizeof(errmsg)); + bpfc_error("Could not find type of prog in BTF: %s (%d)", + errmsg, type_id); + goto err_out; + } + + func_info = (struct bpf_func_info*) func_info_sec->data; + func_info->insn_off = 0; + func_info->type_id = type_id; + + /* line_info section */ + rec_size = (__u32*) (func_info + 1); + *rec_size = sizeof(*line_info); + + line_info_sec = (struct btf_ext_info_sec*) (rec_size + 1); + line_info_sec->sec_name_off = sec_name_off; + line_info_sec->num_info = 1; + + // Add section name as string + int dummy_str_off = btf__add_str(btf, ""); + if (dummy_str_off < 0) { + libbpf_strerror(dummy_str_off, errmsg, sizeof(errmsg)); + bpfc_error("Could not add dummy line to BTF: %s (%d)", errmsg, + dummy_str_off); + goto err_out; + } + + line_info = (struct bpf_line_info*) line_info_sec->data; + line_info->file_name_off = dummy_str_off; + line_info->line_off = dummy_str_off; + + scn = add_elf_sec(elf, strtab, ".BTF.ext"); + if (!scn) + goto err_out; + + shdr = elf64_getshdr(scn); + if (!shdr) + goto err_out; + + shdr->sh_type = SHT_PROGBITS; + shdr->sh_addralign = 4; + + data = elf_newdata(scn); + if (!data) + goto err_out; + + data->d_align = 4; + data->d_off = 0LL; + data->d_buf = buf; + data->d_type = ELF_T_BYTE; + + data->d_size = size; + shdr->sh_size = size; + + return scn; +err_out: + if (buf) + free(buf); + return NULL; +} + int _ebpf_program_write_elf(struct ebpf_program *prog, int fd, struct elf_write_opts *opts) { @@ -977,6 +1118,13 @@ int _ebpf_program_write_elf(struct ebpf_program *prog, int fd, goto out; } + scn = add_elf_btf_ext(elf, strtab, btf, opts); + if (!scn) { + err = EINVAL; + bpfc_error("Failed to add BTF.ext section to ELF object"); + goto out; + } + scn = add_elf_btf(elf, strtab, btf); if (!scn) { err = EINVAL; @@ -1058,6 +1206,11 @@ int ebpf_program_write_elf(struct ebpf_program *prog, if (!opts->progname) opts->progname = BPFC_PROG_SYM_NAME; + if (!opts->secname && opts->mode == MODE_STANDALONE) + opts->secname = "xdp"; + else if (!opts->secname && opts->mode == MODE_LINKABLE) + opts->secname = ".text"; + err = _ebpf_program_write_elf(prog, fd, opts); out: diff --git a/filterc/bpfc.h b/filterc/bpfc.h index 71d169d9..f6dcf438 100644 --- a/filterc/bpfc.h +++ b/filterc/bpfc.h @@ -10,11 +10,18 @@ struct cbpf_insn; struct cbpf_program; struct ebpf_program; +enum object_mode { + MODE_STANDALONE = 0, + MODE_LINKABLE, +}; + struct elf_write_opts { size_t sz; int fd; char *path; char *progname; + char *secname; + enum object_mode mode; }; char *bpfc_geterr(); diff --git a/filterc/filterc.c b/filterc/filterc.c index 8db26915..b932d151 100644 --- a/filterc/filterc.c +++ b/filterc/filterc.c @@ -11,6 +11,7 @@ static const struct filteropt { char *output; char *progname; + bool linkable; char *filter; } filteropt_defaults = { }; @@ -27,6 +28,9 @@ static struct prog_option filterc_options[] = { .metavar = "", .help = "Name of the program in the BPF object file "\ "(default: " BPFC_PROG_SYM_NAME ")"), + DEFINE_OPTION("linkable", OPT_BOOL, struct filteropt, linkable, + .short_opt = 'l', + .help = "BPF object file should be linkable with other programs"), DEFINE_OPTION("filter", OPT_STRING, struct filteropt, filter, .required = true, .positional = true, @@ -39,6 +43,7 @@ int main(int argc, char **argv) { struct cbpf_program *cbpf_prog = NULL; struct ebpf_program *ebpf_prog = NULL; + enum object_mode mode; int err, rc = EXIT_FAILURE; if (parse_cmdline_args(argc, argv, filterc_options, &cfg_filteropt, @@ -64,9 +69,11 @@ int main(int argc, char **argv) ebpf_program_dump(ebpf_prog); pr_info("Writing BPF object file (ELF)\n"); + mode = cfg_filteropt.linkable ? MODE_LINKABLE : MODE_STANDALONE; LIBBPF_OPTS(elf_write_opts, write_opts, .progname = cfg_filteropt.progname, - .path = cfg_filteropt.output); + .path = cfg_filteropt.output, + .mode = mode); err = ebpf_program_write_elf(ebpf_prog, &write_opts); if (err) { pr_warn("Failed to write BPF object in ELF format: %s\n", From 15bf1372a27076ccdb2d25f51961f78e7d36a88e Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Tue, 14 Mar 2023 16:42:42 +0100 Subject: [PATCH 15/17] filterc: Add test with bpf_linker Signed-off-by: Felix Maurer --- filterc/Makefile | 2 + filterc/tests/test-filterc.sh | 9 +- filterc/tests/test_bpfc.c | 182 +++++++++++++++++++++++++++++++--- filterc/tests/wrapper_prog.c | 12 +++ 4 files changed, 191 insertions(+), 14 deletions(-) create mode 100644 filterc/tests/wrapper_prog.c diff --git a/filterc/Makefile b/filterc/Makefile index 437aba88..41790ec5 100644 --- a/filterc/Makefile +++ b/filterc/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) TOOL_NAME := filterc USER_TARGETS := filterc +# Just needed for tests +XDP_TARGETS := tests/wrapper_prog TEST_TARGETS := tests/test_bpfc TEST_FILE := tests/test-filterc.sh diff --git a/filterc/tests/test-filterc.sh b/filterc/tests/test-filterc.sh index e2376084..2e0bcaed 100644 --- a/filterc/tests/test-filterc.sh +++ b/filterc/tests/test-filterc.sh @@ -1,7 +1,14 @@ -ALL_TESTS="test_bpfc_standalone" +ALL_TESTS="\ + test_bpfc_standalone \ + test_bpfc_linked \ + " test_bpfc="tests/test_bpfc" test_bpfc_standalone() { $test_bpfc test_standalone } + +test_bpfc_linked() { + $test_bpfc test_linked +} diff --git a/filterc/tests/test_bpfc.c b/filterc/tests/test_bpfc.c index 9dffc7cd..72308290 100644 --- a/filterc/tests/test_bpfc.c +++ b/filterc/tests/test_bpfc.c @@ -25,40 +25,53 @@ #define TMP_SUFFIX ".bpf.o" #define TEST_PROG_NAME "filterc_test_prog" -static struct bpf_object *compile_filter(char *filter) +int compile_filter_to_file(char *filter, char *fname, + struct elf_write_opts *write_opts) { struct cbpf_program *cbpf_prog; struct ebpf_program *ebpf_prog; - struct bpf_object *bpf_obj; - char fname[] = "/tmp/"PROG_NAME"_XXXXXX"TMP_SUFFIX; - int err, fd; + int fd, err; fd = mkstemps(fname, strlen(TMP_SUFFIX)); if (fd < 0) { printf("Failed to create temp file\n"); - return NULL; + return -1; } - cbpf_prog = cbpf_program_from_filter(filter); if (!cbpf_prog) { printf("Failed to compile filter.\n"); - return NULL; + return -1; } ebpf_prog = ebpf_program_from_cbpf(cbpf_prog); if (!ebpf_prog) { printf("Failed to convert cBPF to eBPF: %s\n", bpfc_geterr()); - return NULL; + return -1; } - LIBBPF_OPTS(elf_write_opts, write_opts, - .fd = fd, - .progname = TEST_PROG_NAME); - err = ebpf_program_write_elf(ebpf_prog, &write_opts); + write_opts->fd = fd; + err = ebpf_program_write_elf(ebpf_prog, write_opts); if (err) { printf("Failed to write BPF object in ELF format: %s\n", bpfc_geterr()); + return -1; + } + + return 0; +} + +static struct bpf_object *compile_filter(char *filter) +{ + struct bpf_object *bpf_obj; + char fname[] = "/tmp/"PROG_NAME"_XXXXXX"TMP_SUFFIX; + int err; + + LIBBPF_OPTS(elf_write_opts, write_opts, + .progname = TEST_PROG_NAME); + err = compile_filter_to_file(filter, fname, &write_opts); + if (err) { + printf("Failed to compile filter string to object file\n"); return NULL; } @@ -80,6 +93,81 @@ static struct bpf_object *compile_filter(char *filter) return bpf_obj; } +static struct bpf_object *link_filter(char *filter) +{ + struct bpf_linker *linker; + struct bpf_object *bpf_obj; + char filter_fname[] = "/tmp/"PROG_NAME"_filter_XXXXXX"TMP_SUFFIX; + char linked_fname[] = "/tmp/"PROG_NAME"_linked_XXXXXX"TMP_SUFFIX; + int err, fd; + + LIBBPF_OPTS(elf_write_opts, write_opts, + .progname = TEST_PROG_NAME, + .mode = MODE_LINKABLE); + err = compile_filter_to_file(filter, filter_fname, &write_opts); + if (err) { + printf("Failed to compile filter string to object file\n"); + return NULL; + } + + fd = mkstemps(linked_fname, strlen(TMP_SUFFIX)); + if (fd < 0) { + printf("Failed to create temp file\n"); + return NULL; + } + close(fd); + + linker = bpf_linker__new(linked_fname, NULL); + if (!linker) { + printf("Failed to initialize linker\n"); + return NULL; + } + + err = bpf_linker__add_file(linker, "tests/wrapper_prog.o", NULL); + if (err) { + printf("Failed to add static prog to linker\n"); + return NULL; + } + + err = bpf_linker__add_file(linker, filter_fname, NULL); + if (err) { + printf("Failed to add compiled filter to linker\n"); + return NULL; + } + + err = bpf_linker__finalize(linker); + if (err) { + printf("Failed to finalize linker\n"); + return NULL; + } + + bpf_linker__free(linker); + err = unlink(filter_fname); + if (err) { + err = errno; + printf("Failed to unlink temp file '%s': %s (%d)\n", + filter_fname, strerror(err), err); + return NULL; + } + + bpf_obj = bpf_object__open(linked_fname); + if (!bpf_obj) { + err = errno; + printf("Failed to open BPF object: %s (%d)\n", strerror(err), err); + return NULL; + } + + err = unlink(linked_fname); + if (err) { + err = errno; + printf("Failed to unlink temp file '%s': %s (%d)\n", + linked_fname, strerror(err), err); + return NULL; + } + + return bpf_obj; +} + int build_udp_packet(void *pkt, int len, int dst_port) { struct ethhdr *eh = pkt; @@ -165,8 +253,75 @@ TEST_FUNC(standalone) return 0; } +TEST_FUNC(linked) +{ + struct bpf_object *bpf_obj; + struct bpf_program *bpf_prog; + int err, prog_fd; + int len = 128; + void *pkt = malloc(len); + + bpf_obj = link_filter("udp port 53"); + if (!bpf_obj) { + printf("Failed to compile, link, and open filter\n"); + return 1; + } + + err = bpf_object__load(bpf_obj); + if (err) { + printf("Failed to load bpf object\n"); + return 1; + } + + bpf_prog = bpf_object__find_program_by_name(bpf_obj, "wrapper_prog"); + if (!bpf_prog) { + printf("Failed to find bpf program in object\n"); + return 1; + } + prog_fd = bpf_program__fd(bpf_prog); + + err = build_udp_packet(pkt, len, 53); + if (err) { + printf("Failed to build packet (%d)\n", err); + return 1; + } + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, test_opts, + .data_in = pkt, + .data_size_in = len); + err = bpf_prog_test_run_opts(prog_fd, &test_opts); + if (err) { + printf("Failed to run bpf prog (%d)\n", err); + return 1; + } + if (test_opts.retval != XDP_PASS) { + printf("Unexpected return value %d, expected %d\n", + test_opts.retval, XDP_PASS); + return 1; + } + + err = build_udp_packet(pkt, len, 123); + if (err) { + printf("Failed to build packet (%d)\n", err); + return 1; + } + err = bpf_prog_test_run_opts(prog_fd, &test_opts); + if (err) { + printf("Failed to run bpf prog (%d)\n", err); + return 1; + } + if (test_opts.retval != XDP_DROP) { + printf("Unexpected return value %d, expected %d\n", + test_opts.retval, XDP_DROP); + return 1; + } + + free(pkt); + return 0; +} + static const struct prog_command cmds[] = { TEST(standalone), + TEST(linked), END_COMMANDS }; @@ -174,7 +329,8 @@ int main(int argc, char **argv) { check_bpf_environ(); set_log_level(LOG_VERBOSE); - + libbpf_set_strict_mode(LIBBPF_STRICT_DIRECT_ERRS + | LIBBPF_STRICT_CLEAN_PTRS); if (argc > 1) return dispatch_commands(argv[1], argc - 1, argv + 1, cmds, diff --git a/filterc/tests/wrapper_prog.c b/filterc/tests/wrapper_prog.c new file mode 100644 index 00000000..fd7ce31d --- /dev/null +++ b/filterc/tests/wrapper_prog.c @@ -0,0 +1,12 @@ +#include +#include + +extern int filterc_test_prog(struct xdp_md *ctx); + +SEC("xdp") +int wrapper_prog(struct xdp_md *ctx) +{ + return filterc_test_prog(ctx); +} + +char _license[] SEC("license") = "GPL"; From 0be7232a98188503f966f7e548a7ba3a25de7b04 Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Fri, 8 Sep 2023 18:45:01 +0200 Subject: [PATCH 16/17] filterc: Test more programs Compile a bunch of programs and check if they pass/fail as expected. Signed-off-by: Felix Maurer --- filterc/tests/test-filterc.sh | 5 + filterc/tests/test_bpfc.c | 181 +++++++++++++++++++++++++++++----- 2 files changed, 159 insertions(+), 27 deletions(-) diff --git a/filterc/tests/test-filterc.sh b/filterc/tests/test-filterc.sh index 2e0bcaed..8f93ed2d 100644 --- a/filterc/tests/test-filterc.sh +++ b/filterc/tests/test-filterc.sh @@ -1,6 +1,7 @@ ALL_TESTS="\ test_bpfc_standalone \ test_bpfc_linked \ + test_bpfc_filters \ " test_bpfc="tests/test_bpfc" @@ -12,3 +13,7 @@ test_bpfc_standalone() { test_bpfc_linked() { $test_bpfc test_linked } + +test_bpfc_filters() { + $test_bpfc test_filters +} diff --git a/filterc/tests/test_bpfc.c b/filterc/tests/test_bpfc.c index 72308290..60a7d494 100644 --- a/filterc/tests/test_bpfc.c +++ b/filterc/tests/test_bpfc.c @@ -168,25 +168,64 @@ static struct bpf_object *link_filter(char *filter) return bpf_obj; } -int build_udp_packet(void *pkt, int len, int dst_port) +void *insert_eth(void *pkt, int proto) { struct ethhdr *eh = pkt; - struct iphdr *iph = (struct iphdr *)(eh + 1); - struct udphdr *udph = (struct udphdr *)(iph + 1); + eh->h_proto = htons(proto); - memset(pkt, 0, len); + return (void *)(eh + 1); +} + +struct vlan_hdr { + __be16 h_vlan_TCI; + __be16 h_vlan_encapsulated_proto; +}; - eh->h_proto = htons(ETH_P_IP); +void *insert_vlan(void *pkt, int vlan_id, int proto) +{ + struct vlan_hdr *vlanh = pkt; + vlanh->h_vlan_TCI = htons(vlan_id); + vlanh->h_vlan_encapsulated_proto = htons(proto); + + return (vlanh + 1); +} - iph->protocol = IPPROTO_UDP; +void *insert_ipv4(void *pkt, int proto) +{ + struct iphdr *iph = pkt; + iph->protocol = proto; iph->ihl = 5; + return (iph + 1); +} + +void *insert_udp(void *pkt, int dst_port) +{ + struct udphdr *udph = pkt; udph->source = htons(54321); udph->dest = htons(dst_port); + return (udph + 1); +} - return 0; +void build_vlan_udp_packet(void *pkt, int len, int vlan_id, int dst_port) +{ + memset(pkt, 0, len); + pkt = insert_eth(pkt, ETH_P_8021Q); + pkt = insert_vlan(pkt, vlan_id, ETH_P_IP); + pkt = insert_ipv4(pkt, IPPROTO_UDP); + insert_udp(pkt, dst_port); +} + +void build_udp_packet(void *pkt, int len, int dst_port) +{ + memset(pkt, 0, len); + pkt = insert_eth(pkt, ETH_P_IP); + pkt = insert_ipv4(pkt, IPPROTO_UDP); + insert_udp(pkt, dst_port); } + + TEST_FUNC(standalone) { struct bpf_object *bpf_obj; @@ -214,11 +253,8 @@ TEST_FUNC(standalone) } prog_fd = bpf_program__fd(bpf_prog); - err = build_udp_packet(pkt, len, 53); - if (err) { - printf("Failed to build packet (%d)\n", err); - return 1; - } + build_udp_packet(pkt, len, 53); + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, test_opts, .data_in = pkt, .data_size_in = len); @@ -233,11 +269,8 @@ TEST_FUNC(standalone) return 1; } - err = build_udp_packet(pkt, len, 123); - if (err) { - printf("Failed to build packet (%d)\n", err); - return 1; - } + build_udp_packet(pkt, len, 123); + err = bpf_prog_test_run_opts(prog_fd, &test_opts); if (err) { printf("Failed to run bpf prog (%d)\n", err); @@ -280,11 +313,8 @@ TEST_FUNC(linked) } prog_fd = bpf_program__fd(bpf_prog); - err = build_udp_packet(pkt, len, 53); - if (err) { - printf("Failed to build packet (%d)\n", err); - return 1; - } + build_udp_packet(pkt, len, 53); + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, test_opts, .data_in = pkt, .data_size_in = len); @@ -299,11 +329,8 @@ TEST_FUNC(linked) return 1; } - err = build_udp_packet(pkt, len, 123); - if (err) { - printf("Failed to build packet (%d)\n", err); - return 1; - } + build_udp_packet(pkt, len, 123); + err = bpf_prog_test_run_opts(prog_fd, &test_opts); if (err) { printf("Failed to run bpf prog (%d)\n", err); @@ -319,9 +346,109 @@ TEST_FUNC(linked) return 0; } +typedef void (*pkt_func)(void *, int); +#define PKT_FUNC(fn) void pkt_##fn(void *pkt, int len) + +PKT_FUNC(udp53) { + build_udp_packet(pkt, len, 53); +} + +PKT_FUNC(udp123) { + build_udp_packet(pkt, len, 123); +} + +PKT_FUNC(vlan42_udp53) { + build_vlan_udp_packet(pkt, len, 42, 53); +} + +PKT_FUNC(vlan42_udp123) { + build_vlan_udp_packet(pkt, len, 42, 123); +} + +PKT_FUNC(vlan100_udp53) { + build_vlan_udp_packet(pkt, len, 100, 53); +} + +struct test_filter { + char *filter; + pkt_func good_pkt; + pkt_func bad_pkt; +} test_filters[] = { + {"udp port 53", pkt_udp53, pkt_udp123}, + {"vlan 42 and udp port 53", pkt_vlan42_udp53, pkt_vlan100_udp53}, + {"vlan 42 and udp port 53", pkt_vlan42_udp53, pkt_udp53}, + {"udp port 53 or (vlan and udp port 53)", pkt_udp53, pkt_udp123}, + {"udp port 53 or (vlan and udp port 53)", pkt_vlan42_udp53, pkt_vlan42_udp123}, + {NULL, NULL, NULL}, +}; + +TEST_FUNC(filters) +{ + struct bpf_object *bpf_obj; + struct bpf_program *bpf_prog; + struct test_filter *test; + int err, prog_fd, i = 0, len = 256; + void *pkt = malloc(len); + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, test_opts, + .data_in = pkt, + .data_size_in = len); + + while (test_filters[i].filter) { + test = &test_filters[i]; + bpf_obj = compile_filter(test->filter); + if (!bpf_obj) { + printf("%d: Failed to compile and open filter'\n", i); + return 1; + } + + err = bpf_object__load(bpf_obj); + if (err) { + printf("%d: Failed to load bpf object\n", i); + return 1; + } + + bpf_prog = bpf_object__find_program_by_name(bpf_obj, TEST_PROG_NAME); + if (!bpf_prog) { + printf("%d: Failed to find bpf program in object\n", i); + return 1; + } + prog_fd = bpf_program__fd(bpf_prog); + + test->good_pkt(pkt, len); + err = bpf_prog_test_run_opts(prog_fd, &test_opts); + if (err) { + printf("%d: Failed to run bpf prog (%d)\n", i, err); + return 1; + } + if (test_opts.retval != XDP_PASS) { + printf("%d: Unexpected return value %d, expected XDP_PASS\n", + i, test_opts.retval); + return 1; + } + + test->bad_pkt(pkt, len); + err = bpf_prog_test_run_opts(prog_fd, &test_opts); + if (err) { + printf("%d: Failed to run bpf prog (%d)\n", i, err); + return 1; + } + if (test_opts.retval != XDP_DROP) { + printf("%d: Unexpected return value %d, expected XDP_DROP\n", + i, test_opts.retval); + return 1; + } + + i++; + } + + free(pkt); + return 0; +} + static const struct prog_command cmds[] = { TEST(standalone), TEST(linked), + TEST(filters), END_COMMANDS }; From 3e8a3aa860a34297eaf8ecf662f295a81302dd15 Mon Sep 17 00:00:00 2001 From: Felix Maurer Date: Fri, 8 Sep 2023 19:22:06 +0200 Subject: [PATCH 17/17] filterc: Optimize jumps When one of the branches goes to the next insn, it is not necessary to emit two eBPF insns. We can either leave out the second jump if the false case is the next insn, or we can invert the condition and leave out the second jump if the true case is the next insn. Signed-off-by: Felix Maurer --- filterc/bpfc.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/filterc/bpfc.c b/filterc/bpfc.c index df3d6453..1701f5c7 100644 --- a/filterc/bpfc.c +++ b/filterc/bpfc.c @@ -278,6 +278,36 @@ static int convert_cbpf(struct cbpf_program *cbpf_prog, src_reg = bpf_src == BPF_X ? BPF_REG_X : 0; } + // Only emit one jump if jump_false is next insn + if (fp->jf == 0) { + code = BPF_JMP | BPF_OP(fp->code) | bpf_src; + target = i + fp->jt + 1; + *insn++ = BPF_JMP_INSN(code, dst_reg, src_reg, imm, target); + break; + } + + // Invert conditons where possible if jump_true is next insn + if (fp->jt == 0) { + switch (BPF_OP(fp->code)) { + case BPF_JEQ: + code = BPF_JMP | BPF_JNE | bpf_src; + break; + case BPF_JGT: + code = BPF_JMP | BPF_JLE | bpf_src; + break; + case BPF_JGE: + code = BPF_JMP | BPF_JLT | bpf_src; + break; + default: + goto jmp_rest; + } + + target = i + fp->jf + 1; + *insn++ = BPF_JMP_INSN(code, dst_reg, src_reg, imm, target); + break; + } + +jmp_rest: /* Other jumps are mapped into two insns: Jxx and JA. */ code = BPF_JMP | BPF_OP(fp->code) | bpf_src; target = i + fp->jt + 1;