diff --git a/AF_XDP-interaction/Makefile b/AF_XDP-interaction/Makefile index 74fe7d8..e752306 100644 --- a/AF_XDP-interaction/Makefile +++ b/AF_XDP-interaction/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) USER_TARGETS := af_xdp_user +USER_TARGETS += btf_unit_test BPF_TARGETS := af_xdp_kern +BPF_TARGETS += btf_unit_test_bpf # Define C-code objects USER_TARGETS needs USER_TARGETS_OBJS := common_params.o common_user_bpf_xdp.o diff --git a/AF_XDP-interaction/af_xdp_user.c b/AF_XDP-interaction/af_xdp_user.c index b69b608..492e82d 100644 --- a/AF_XDP-interaction/af_xdp_user.c +++ b/AF_XDP-interaction/af_xdp_user.c @@ -143,18 +143,11 @@ struct xsk_btf_info *setup_btf_info(struct btf *btf, err = xsk_btf__init_xdp_hint(btf, struct_name, &xbi); if (err) { - fprintf(stderr, "WARN(%d): Cannot BTF find struct:%s\n", + fprintf(stderr, "WARN(%d): Cannot BTF locate valid struct:%s\n", err, struct_name); return NULL; } - if (!xsk_btf__has_field("btf_id", xbi)) { - fprintf(stderr, "ERR: %s doesn't contain member btf_id\n", - struct_name); - xsk_btf__free_xdp_hint(xbi); - return NULL; - } - if (debug_meta) printf("Setup BTF based XDP hints for struct: %s\n", struct_name); diff --git a/AF_XDP-interaction/btf_unit_test.c b/AF_XDP-interaction/btf_unit_test.c new file mode 100644 index 0000000..85919cb --- /dev/null +++ b/AF_XDP-interaction/btf_unit_test.c @@ -0,0 +1,218 @@ +/* Code exercising BTF userspace decoding */ + +#include +#include +#include + +#include + +#include +#include + +#include /* provided by libbpf */ + +#include "lib_xsk_extend.h" + +static int verbose = 1; + +/* Exit return codes - can be used by scripts looking at exit code */ +#define EXIT_OK 0 /* == EXIT_SUCCESS (stdlib.h) man exit(3) */ +#define EXIT_FAIL 1 /* == EXIT_FAILURE (stdlib.h) man exit(3) */ +#define EXIT_FAIL_OPTION 2 +#define EXIT_FAIL_XDP 3 +#define EXIT_FAIL_BPF 4 +#define EXIT_FAIL_BTF 5 + +struct bpf_object *load_bpf_object(const char *filename) { + struct bpf_object *obj; + char buf[100]; + int err; + + obj = bpf_object__open_file(filename, NULL); + err = libbpf_get_error(obj); + if (err) { + libbpf_strerror(err, buf, sizeof(buf)); + printf("Error opening file: %s\n", buf); + return NULL; + } + return obj; +} + +/** + * BTF setup XDP-hints + * ------------------- + * Setup the data structures for accessing the XDP-hints provided by + * kernel side BPF-prog via decoding BTF-info provided in BPF + * ELF-object file. + */ + +/* This struct BTF mirrors kernel-side struct xdp_hints_rx_time */ +struct xdp_hints_rx_time { + __u32 btf_type_id; /* cached xsk_btf__btf_type_id(xbi) */ + struct xsk_btf_info *xbi; + struct xsk_btf_member rx_ktime; + struct xsk_btf_member xdp_rx_cpu; +} xdp_hints_rx_time = { 0 }; + +/* This struct BTF mirrors kernel-side struct xdp_hints_mark */ +struct xdp_hints_mark { + __u32 btf_type_id; /* cached xsk_btf__btf_type_id(xbi) */ + struct xsk_btf_info *xbi; + struct xsk_btf_member mark; +} xdp_hints_mark = { 0 }; + +struct xsk_btf_info *setup_btf_info(struct btf *btf, + const char *struct_name, + int *errval) +{ + struct xsk_btf_info *xbi = NULL; + int err; + + err = xsk_btf__init_xdp_hint(btf, struct_name, &xbi); + if (err) { + if (errval) { + /* Expect caller to handle err detection */ + *errval = err; + } else { + fprintf(stderr, + "WARN(%d): Cannot BTF locate valid struct:%s\n", + err, struct_name); + } + return NULL; + } + + if (verbose) { + int btf_id = xsk_btf__btf_type_id(xbi); + printf("Setup BTF based XDP hints for (btf_id:%d) struct: %s\n", + btf_id, struct_name); + } + + return xbi; +} + +int init_btf_info_via_bpf_object(struct bpf_object *bpf_obj) +{ + struct btf *btf = bpf_object__btf(bpf_obj); + struct xsk_btf_info *xbi; + + xbi = setup_btf_info(btf, "xdp_hints_rx_time", NULL); + if (xbi) { + /* Lookup info on required member "rx_ktime" */ + if (!xsk_btf__field_member("rx_ktime", xbi, + &xdp_hints_rx_time.rx_ktime)) + return -EBADSLT; + if (!xsk_btf__field_member("xdp_rx_cpu", xbi, + &xdp_hints_rx_time.xdp_rx_cpu)) + return -EBADSLT; + xdp_hints_rx_time.btf_type_id = xsk_btf__btf_type_id(xbi); + xdp_hints_rx_time.xbi = xbi; + } + /* Remember to cleanup later: xsk_btf__free_xdp_hint(xbi); */ + + xbi = setup_btf_info(btf, "xdp_hints_mark", NULL); + if (xbi) { + if (!xsk_btf__field_member("mark", xbi, &xdp_hints_mark.mark)) + return -EBADSLT; + xdp_hints_mark.btf_type_id = xsk_btf__btf_type_id(xbi); + xdp_hints_mark.xbi = xbi; + } + + return 0; +} + +int test01_normal() +{ + struct bpf_object *bpf_obj; + int err = 0; + + bpf_obj = load_bpf_object("af_xdp_kern.o"); + if (!bpf_obj) + return EXIT_FAIL_BPF; + + err = init_btf_info_via_bpf_object(bpf_obj); + if (err) { + if (verbose) + printf("ERR(%d): Failed loading BTF info", err); + bpf_object__close(bpf_obj); + return EXIT_FAIL_BTF; + } + /* Teardown structs and memory again */ + xsk_btf__free_xdp_hint(xdp_hints_rx_time.xbi); + xsk_btf__free_xdp_hint(xdp_hints_mark.xbi); + bpf_object__close(bpf_obj); + + return EXIT_OK; +} + +int helper_expect_invalid_btf_id(struct btf *btf, + const char *xdp_hint_name) +{ + struct xsk_btf_info *xbi; + int ret = EXIT_OK; + int errval = 0; + + xbi = setup_btf_info(btf, xdp_hint_name, &errval); + if (xbi) { + /* Unexpected success - as hints layout should be invalid */ + printf(" - Unexpected success in test that should fail\n"); + xsk_btf__free_xdp_hint(xbi); + ret = EXIT_FAIL_BPF; + goto out; + } + if (errval != -EOVERFLOW) { + /* Expecting failure with EOVERFLOW as btf_id not last member */ + printf("Unexpect FAIL - with errno:%d\n", errval); + ret = EXIT_FAIL_BTF; + goto out; + } + if (verbose) { + printf("SUCCESS - " + "detect btf_id not last member in struct %s\n", + xdp_hint_name); + } + +out: + return ret; +} + +int test02_should_fail() +{ + const char *xdp_hint01 = "xdp_hints_fail001"; + const char *xdp_hint02 = "xdp_hints_fail002"; + struct bpf_object *bpf_obj; + int ret = EXIT_OK; + struct btf *btf; + + bpf_obj = load_bpf_object("btf_unit_test_bpf.o"); + if (!bpf_obj) + return EXIT_FAIL_BPF; + + btf = bpf_object__btf(bpf_obj); + + ret = helper_expect_invalid_btf_id(btf, xdp_hint01); + if (ret) + goto out; + + ret = helper_expect_invalid_btf_id(btf, xdp_hint02); + if (ret) + goto out; + +out: + bpf_object__close(bpf_obj); + return ret; +} + +int main(int argc, char **argv) +{ + int err; + + err = test01_normal(); + if (err) + return err; + + err = test02_should_fail(); + if (err) + return err; + + return EXIT_OK; +} diff --git a/AF_XDP-interaction/btf_unit_test_bpf.c b/AF_XDP-interaction/btf_unit_test_bpf.c new file mode 100644 index 0000000..7d214aa --- /dev/null +++ b/AF_XDP-interaction/btf_unit_test_bpf.c @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include + +#include /* bpf_core_type_id_local */ + +/* + * The xdp_hints_xxx struct's are stored in the XDP 'data_meta' area, + * which is located just in-front-of the raw packet payload data. + * + * Explaining the struct attribute's: + * ---------------------------------- + * The struct must be 4 byte aligned (kernel requirement), which here + * is enforced by the struct __attribute__((aligned(4))). + * + * To avoid any C-struct padding attribute "packed" is used. + * + * NOTICE: Do NOT define __attribute__((preserve_access_index)) here, + * as libbpf will try to find a matching kernel data-structure, + * e.g. it will cause BPF-prog loading step to fail (with invalid func + * unknown#195896080 which is 0xbad2310 in hex for "bad relo"). + */ + +struct xdp_hints_fail001 { + __u64 hash64; + __u32 btf_id; + __u32 pad; /* Pad that breaks btf_id as last member */ +} __attribute__((aligned(4))) __attribute__((packed)); + +/* Notice struct is without attribute "packed", thus (64-bit) C-compiler will + * add padding. This will cause btf_id to NOT be the last member (which is a + * requirement). + */ +struct xdp_hints_fail002 { + __u64 hash64; + __u32 btf_id; +} __attribute__((aligned(4))) /* not packed */; + + +SEC("xdp") +int xdp_prog_fail001(struct xdp_md *ctx) +{ + struct xdp_hints_fail001 *meta; + void *data; + int err; + + err = bpf_xdp_adjust_meta(ctx, -(int)sizeof(*meta)); + if (err) + return XDP_ABORTED; + + data = (void *)(unsigned long)ctx->data; + meta = (void *)(unsigned long)ctx->data_meta; + if (meta + 1 > data) /* Verify meta area is accessible */ + return XDP_ABORTED; + + meta->btf_id = bpf_core_type_id_local(struct xdp_hints_fail001); + meta->hash64 = 0x4142434445464748; + + return XDP_PASS; +} + +SEC("xdp") +int xdp_prog_fail002(struct xdp_md *ctx) +{ + struct xdp_hints_fail002 f002; + f002.btf_id = bpf_core_type_id_local(struct xdp_hints_fail002); + if (f002.btf_id == 0) + return XDP_ABORTED; + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/AF_XDP-interaction/lib_xsk_extend.c b/AF_XDP-interaction/lib_xsk_extend.c index 7221af9..237833b 100644 --- a/AF_XDP-interaction/lib_xsk_extend.c +++ b/AF_XDP-interaction/lib_xsk_extend.c @@ -67,59 +67,6 @@ static bool __xsk_equal_fn(const void *k1, const void *k2, void *ctx) return k1 == k2; } -int xsk_btf__init_xdp_hint(struct btf *btf_obj, - const char *xdp_hints_name, - struct xsk_btf_info **xbi) -{ - const struct btf_member *m; - const struct btf_type *t; - unsigned short vlen; - int i, id, ret = 0; - - if (!xbi) - return -EINVAL; - - /* Require XDP-hints are defined as a struct */ - id = btf__find_by_name_kind(btf_obj, xdp_hints_name, BTF_KIND_STRUCT); - if (id < 0) { - ret = id; - goto error_btf; - } - - t = btf__type_by_id(btf_obj, id); - - *xbi = malloc(sizeof(**xbi)); - if (!*xbi) { - ret = -ENOMEM; - goto error_btf; - } - - hashmap__init(&(*xbi)->map, __xsk_hash_fn, __xsk_equal_fn, NULL); - - /* Validate no BTF field is a bitfield */ - m = btf_members(t); - vlen = BTF_INFO_VLEN(t->info); - for (i = 0; i < vlen; i++, m++) { - if (BTF_MEMBER_BITFIELD_SIZE(m->offset)) { - ret = -ENOTSUP; - goto error_entry; - } - } - - (*xbi)->btf = btf_obj; - (*xbi)->type = t; - (*xbi)->btf_type_id = id; - - return ret; - -error_entry: - __xsk_btf_free_hash(*xbi); - free(*xbi); - -error_btf: - return ret; -} - static int __xsk_btf_field_entry(struct xsk_btf_info *xbi, const char *field, struct xsk_btf_member *entry) { @@ -219,3 +166,70 @@ int xsk_btf__read_field(void **dest, size_t size, const char *field, xsk_btf__read(dest, size, entry, xbi,addr); return 0; } + +int xsk_btf__init_xdp_hint(struct btf *btf_obj, + const char *xdp_hints_name, + struct xsk_btf_info **xbi) +{ + struct xsk_btf_member btf_id_member; + const struct btf_member *m; + const struct btf_type *t; + unsigned short vlen; + __u32 member_end; + int i, id, err = 0; + + if (!xbi) + return -EINVAL; + + /* Require XDP-hints are defined as a struct */ + id = btf__find_by_name_kind(btf_obj, xdp_hints_name, BTF_KIND_STRUCT); + if (id < 0) { + err = id; + goto error_btf; + } + + t = btf__type_by_id(btf_obj, id); + + *xbi = malloc(sizeof(**xbi)); + if (!*xbi) { + err = -ENOMEM; + goto error_btf; + } + + hashmap__init(&(*xbi)->map, __xsk_hash_fn, __xsk_equal_fn, NULL); + + /* Validate no BTF field is a bitfield */ + m = btf_members(t); + vlen = BTF_INFO_VLEN(t->info); + for (i = 0; i < vlen; i++, m++) { + if (BTF_MEMBER_BITFIELD_SIZE(m->offset)) { + err = -ENOTSUP; + goto error_entry; + } + } + + (*xbi)->btf = btf_obj; + (*xbi)->type = t; + (*xbi)->btf_type_id = id; + + /* Validate 'btf_id' member MUST exist */ + err = __xsk_btf_field_entry((*xbi), "btf_id", &btf_id_member); + if (err) + goto error_entry; + + /* Validate 'btf_id' is last member */ + member_end = btf_id_member.offset + btf_id_member.size; + if (t->size != member_end) { + /* Situation can happen if compiler adds struct padding */ + err = -EOVERFLOW; + goto error_entry; + } + + return 0; + +error_entry: + __xsk_btf_free_hash(*xbi); + free(*xbi); +error_btf: + return err; +}