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..f3e92595 --- /dev/null +++ b/filterc/.gitignore @@ -0,0 +1,2 @@ +filterc +tests/test_bpfc diff --git a/filterc/Makefile b/filterc/Makefile new file mode 100644 index 00000000..41790ec5 --- /dev/null +++ b/filterc/Makefile @@ -0,0 +1,19 @@ +# 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 + +EXTRA_BUILD := bpfc.o +USER_GEN := $(EXTRA_BUILD) +EXTRA_USER_DEPS := $(USER_GEN) + +LIB_DIR = ../lib +USER_LIBS = $(EXTRA_BUILD) -lpcap -lelf + +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..1701f5c7 --- /dev/null +++ b/filterc/bpfc.c @@ -0,0 +1,1287 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include +#include +#include +#include +#include + +#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 +// prevent the conflicts. +#define bpf_insn cbpf_insn +#define bpf_program cbpf_program +#include +#include +#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 512 + STRERR_BUFSIZE + +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) +{ + pcap_t *pcap = NULL; + struct cbpf_program *prog = NULL; + int err; + + pcap = pcap_open_dead(DLT_EN10MB, BPFC_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) +{ + 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) +{ + pcap_freecode(prog); + free(prog); +} + +struct ebpf_program { + struct bpf_insn *insns; + 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. + * + * 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) +{ + // 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, stack_off, err = 0; + int match_insn = -1, nomatch_insn = -1; + + 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) { + /* 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 += 2; + } + + 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 || \ + (first_insn && 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; + } + + // 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; + *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; + + /* 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) { + 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: + 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); + 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) { + // 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"); + 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 = calloc(sizeof(*prog), 1); + if (!prog) { + goto error; + } + + err = convert_cbpf(cbpf_prog, prog); + if (err) + goto error; + + return prog; + +error: + if (prog) + free(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 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); + 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; +} + +static Elf_Scn *add_elf_bpf_prog(Elf *elf, struct table *strtab, + struct table *symtab, + struct ebpf_program *prog, + struct elf_write_opts *opts) +{ + 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, opts->secname); + if (!scn) + return NULL; + + off = strtab_add(strtab, opts->progname); + 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; +} + +/** + * 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(struct elf_write_opts *opts) +{ + 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, 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)); + 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; +} + +/* 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) +{ + int err = 0; + Elf *elf = NULL; + 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; + bpfc_error("libelf initialization failed"); + 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_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(opts); + if (!btf) { + err = EINVAL; + bpfc_error("Failed to add build BTF information"); + 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; + 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"); + 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 (btf) + btf__free(btf); + if (strtab) + table_free(strtab); + if (symtab) + table_free(symtab); + if (elf) + elf_end(elf); + + 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; + } + + 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: + if (opts->path && fd >= 0) + close(fd); + return err; +} + +void ebpf_program_dump(struct ebpf_program *prog) +{ + size_t i; + + 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", 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"); +} + +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 new file mode 100644 index 00000000..f6dcf438 --- /dev/null +++ b/filterc/bpfc.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include + +#ifndef _BPFC_H_ +#define _BPFC_H_ + +#define BPFC_PROG_SYM_NAME "filterc_prog" + +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(); + +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*); +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*); + +#endif /* _BPFC_H_ */ 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 new file mode 100644 index 00000000..b932d151 --- /dev/null +++ b/filterc/filterc.c @@ -0,0 +1,93 @@ +/* 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 *progname; + bool linkable; + 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("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("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, + .metavar = "", + .help = "pcap-filter(7) string to compile"), + END_OPTIONS +}; + +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, + 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); + + 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; + } + + 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, + .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", + bpfc_geterr()); + rc = err; + goto out; + } + + rc = EXIT_SUCCESS; + +out: + if (ebpf_prog) + ebpf_program_free(ebpf_prog); + if (cbpf_prog) + cbpf_program_free(cbpf_prog); + return rc; +} diff --git a/filterc/tests/test-filterc.sh b/filterc/tests/test-filterc.sh new file mode 100644 index 00000000..8f93ed2d --- /dev/null +++ b/filterc/tests/test-filterc.sh @@ -0,0 +1,19 @@ +ALL_TESTS="\ + test_bpfc_standalone \ + test_bpfc_linked \ + test_bpfc_filters \ + " + +test_bpfc="tests/test_bpfc" + +test_bpfc_standalone() { + $test_bpfc test_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 new file mode 100644 index 00000000..60a7d494 --- /dev/null +++ b/filterc/tests/test_bpfc.c @@ -0,0 +1,466 @@ +/* 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" +#define TEST_PROG_NAME "filterc_test_prog" + +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; + int fd, err; + + fd = mkstemps(fname, strlen(TMP_SUFFIX)); + if (fd < 0) { + printf("Failed to create temp file\n"); + return -1; + } + + cbpf_prog = cbpf_program_from_filter(filter); + if (!cbpf_prog) { + printf("Failed to compile filter.\n"); + 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 -1; + } + + 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; + } + + 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; +} + +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; +} + +void *insert_eth(void *pkt, int proto) +{ + struct ethhdr *eh = pkt; + eh->h_proto = htons(proto); + + return (void *)(eh + 1); +} + +struct vlan_hdr { + __be16 h_vlan_TCI; + __be16 h_vlan_encapsulated_proto; +}; + +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); +} + +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); +} + +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; + 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, TEST_PROG_NAME); + if (!bpf_prog) { + printf("Failed to find bpf program in object\n"); + return 1; + } + prog_fd = bpf_program__fd(bpf_prog); + + build_udp_packet(pkt, len, 53); + + 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; + } + + 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); + 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; +} + +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); + + build_udp_packet(pkt, len, 53); + + 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; + } + + 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); + 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; +} + +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 +}; + +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, + 0, PROG_NAME, false); + return -1; +} 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";