Certain data types get exceptionally unwieldy when formatted by bpftool, e.g. IP6 addresses. Introduce custom formatting in bpftool driven by user:fmt: decl tag. When a type is tagged user:fmt:ip, the value is formatted as IP4 or IP6 address depending on the value size. When a type is tagged user:fmt:be, the value is interpreted as a big-endian integer (2, 4 or 8 bytes). Example: typedef struct in6_addr bpf_in6_addr __attribute__((__btf_decl_tag__("user:fmt:ip"))); bpf_in6_addr in6; $ bpftool map dump name .data [{ "value": { ".data": [{ "in6": "2001:db8:130f::9c0:876a:130b" } ] } } ] versus $ bpftool map dump name .data [{ "value": { ".data": [{ "in6": { "in6_u": { "u6_addr8": [32,1,13,184,19,15,0,0,0,0,9,192,135,106,19,11 ], "u6_addr16": [288,47117,3859,0,0,49161,27271,2835 ], "u6_addr32": [3087860000,3859,3221815296,185821831 ] } } } ] } } ] Signed-off-by: Nick Zavaritsky --- tools/bpf/bpftool/btf_dumper.c | 119 +++++++++++++++++++++++++++++---- tools/bpf/bpftool/main.h | 12 ++++ tools/bpf/bpftool/map.c | 7 +- 3 files changed, 124 insertions(+), 14 deletions(-) diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c index ff12628593ae..8f21c9e39abc 100644 --- a/tools/bpf/bpftool/btf_dumper.c +++ b/tools/bpf/bpftool/btf_dumper.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "json_writer.h" #include "main.h" @@ -132,18 +134,6 @@ static void btf_dumper_ptr(const struct btf_dumper *d, jsonw_printf(d->jw, "%lu", value); } -static int btf_dumper_modifier(const struct btf_dumper *d, __u32 type_id, - __u8 bit_offset, const void *data) -{ - int actual_type_id; - - actual_type_id = btf__resolve_type(d->btf, type_id); - if (actual_type_id < 0) - return actual_type_id; - - return btf_dumper_do_type(d, actual_type_id, bit_offset, data); -} - static int btf_dumper_enum(const struct btf_dumper *d, const struct btf_type *t, const void *data) @@ -556,6 +546,36 @@ static int btf_dumper_do_type(const struct btf_dumper *d, __u32 type_id, __u8 bit_offset, const void *data) { const struct btf_type *t = btf__type_by_id(d->btf, type_id); + char addr[INET6_ADDRSTRLEN]; + + if (!t) + return -errno; + + switch ((bit_offset == 0 && d->fmt_tags) ? d->fmt_tags[type_id] : + BTF_FMT_DEFAULT) { + case BTF_FMT_BE16: + jsonw_printf(d->jw, "\"%d\"", + be16_to_cpu(*(const __be16 *)data)); + return 0; + case BTF_FMT_BE32: + jsonw_printf(d->jw, "\"%u\"", + be32_to_cpu(*(const __be32 *)data)); + return 0; + case BTF_FMT_BE64: + jsonw_printf(d->jw, "\"%lu\"", + be64_to_cpu(*(const __be64 *)data)); + return 0; + case BTF_FMT_IP4: + jsonw_string(d->jw, + inet_ntop(AF_INET, data, addr, sizeof(addr))); + return 0; + case BTF_FMT_IP6: + jsonw_string(d->jw, + inet_ntop(AF_INET6, data, addr, sizeof(addr))); + return 0; + default: + break; + } switch (BTF_INFO_KIND(t->info)) { case BTF_KIND_INT: @@ -584,7 +604,7 @@ static int btf_dumper_do_type(const struct btf_dumper *d, __u32 type_id, case BTF_KIND_VOLATILE: case BTF_KIND_CONST: case BTF_KIND_RESTRICT: - return btf_dumper_modifier(d, type_id, bit_offset, data); + return btf_dumper_do_type(d, t->type, bit_offset, data); case BTF_KIND_VAR: return btf_dumper_var(d, type_id, bit_offset, data); case BTF_KIND_DATASEC: @@ -595,6 +615,79 @@ static int btf_dumper_do_type(const struct btf_dumper *d, __u32 type_id, } } +enum btf_fmt_tag *btf_fmt_tags_get(const struct btf *btf) +{ + int n = btf__type_cnt(btf); + enum btf_fmt_tag *tags = calloc(n, sizeof(tags[0])); + + if (!tags) + return NULL; + + for (int i = 1; i < n; i++) { + const struct btf_type *t = btf__type_by_id(btf, i); + const struct btf_decl_tag *tag; + const char *name; + __s64 size; + + if (!t) + goto err_free; + if (btf_kind(t) != BTF_KIND_DECL_TAG) + continue; + + tag = (const void *)(t + 1); + if (tag->component_idx != -1) + continue; + + name = btf__name_by_offset(btf, t->name_off); + if (!name) + goto err_free; + +#define BTF_FMT_TAG_PREFIX "user:fmt:" + if (strncmp(name, BTF_FMT_TAG_PREFIX, + sizeof(BTF_FMT_TAG_PREFIX) - 1)) + continue; + + size = btf__resolve_size(btf, t->type); + if (size < 0) + continue; // could be a forward decl + + if (btf_kind(btf__type_by_id(btf, t->type)) == BTF_KIND_VAR) + continue; + + name += sizeof(BTF_FMT_TAG_PREFIX) - 1; + if (!strcmp(name, "be")) { + switch (size) { + case 2: + tags[t->type] = BTF_FMT_BE16; + break; + case 4: + tags[t->type] = BTF_FMT_BE32; + break; + case 8: + tags[t->type] = BTF_FMT_BE64; + break; + default: + break; + } + } else if (!strcmp(name, "ip")) { + switch (size) { + case 4: + tags[t->type] = BTF_FMT_IP4; + break; + case 16: + tags[t->type] = BTF_FMT_IP6; + break; + default: + break; + } + } + } + return tags; +err_free: + free(tags); + return NULL; +} + int btf_dumper_type(const struct btf_dumper *d, __u32 type_id, const void *data) { diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h index 374cac2a8c66..6ff186300dc2 100644 --- a/tools/bpf/bpftool/main.h +++ b/tools/bpf/bpftool/main.h @@ -210,8 +210,20 @@ unsigned int get_possible_cpus(void); const char * ifindex_to_arch(__u32 ifindex, __u64 ns_dev, __u64 ns_ino, const char **opt); +enum btf_fmt_tag { + BTF_FMT_DEFAULT = 0, + BTF_FMT_BE16, + BTF_FMT_BE32, + BTF_FMT_BE64, + BTF_FMT_IP4, + BTF_FMT_IP6, +}; + +enum btf_fmt_tag *btf_fmt_tags_get(const struct btf *btf); + struct btf_dumper { const struct btf *btf; + const enum btf_fmt_tag *fmt_tags; json_writer_t *jw; bool is_plain_text; bool prog_id_as_func_ptr; diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c index c9de44a45778..e91ae50710fc 100644 --- a/tools/bpf/bpftool/map.c +++ b/tools/bpf/bpftool/map.c @@ -744,6 +744,7 @@ static int do_show(int argc, char **argv) static int dump_map_elem(int fd, void *key, void *value, struct bpf_map_info *map_info, struct btf *btf, + const enum btf_fmt_tag *fmt_tags, json_writer_t *btf_wtr) { if (bpf_map_lookup_elem(fd, key, value)) { @@ -756,6 +757,7 @@ static int dump_map_elem(int fd, void *key, void *value, } else if (btf) { struct btf_dumper d = { .btf = btf, + .fmt_tags = fmt_tags, .jw = btf_wtr, .is_plain_text = true, }; @@ -829,6 +831,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, void *key, *value, *prev_key; unsigned int num_elems = 0; struct btf *btf = NULL; + enum btf_fmt_tag *fmt_tags = NULL; int err; key = malloc(info->key_size); @@ -846,6 +849,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, if (err) { goto exit_free; } + fmt_tags = btf_fmt_tags_get(btf); if (show_header) { jsonw_start_object(wtr); /* map object */ @@ -872,7 +876,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, err = 0; break; } - if (!dump_map_elem(fd, key, value, info, btf, wtr)) + if (!dump_map_elem(fd, key, value, info, btf, fmt_tags, wtr)) num_elems++; prev_key = key; } @@ -891,6 +895,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr, free(value); close(fd); free_map_kv_btf(btf); + free(fmt_tags); return err; } -- 2.43.0