| // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
| /* Copyright (C) 2017-2018 Netronome Systems, Inc. */ |
| |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <net/if.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #include <linux/err.h> |
| #include <linux/sizes.h> |
| |
| #include <bpf.h> |
| #include <btf.h> |
| #include <libbpf.h> |
| |
| #include "cfg.h" |
| #include "main.h" |
| #include "xlated_dumper.h" |
| |
| static const char * const attach_type_strings[] = { |
| [BPF_SK_SKB_STREAM_PARSER] = "stream_parser", |
| [BPF_SK_SKB_STREAM_VERDICT] = "stream_verdict", |
| [BPF_SK_MSG_VERDICT] = "msg_verdict", |
| [BPF_FLOW_DISSECTOR] = "flow_dissector", |
| [__MAX_BPF_ATTACH_TYPE] = NULL, |
| }; |
| |
| static enum bpf_attach_type parse_attach_type(const char *str) |
| { |
| enum bpf_attach_type type; |
| |
| for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) { |
| if (attach_type_strings[type] && |
| is_prefix(str, attach_type_strings[type])) |
| return type; |
| } |
| |
| return __MAX_BPF_ATTACH_TYPE; |
| } |
| |
| static void print_boot_time(__u64 nsecs, char *buf, unsigned int size) |
| { |
| struct timespec real_time_ts, boot_time_ts; |
| time_t wallclock_secs; |
| struct tm load_tm; |
| |
| buf[--size] = '\0'; |
| |
| if (clock_gettime(CLOCK_REALTIME, &real_time_ts) || |
| clock_gettime(CLOCK_BOOTTIME, &boot_time_ts)) { |
| perror("Can't read clocks"); |
| snprintf(buf, size, "%llu", nsecs / 1000000000); |
| return; |
| } |
| |
| wallclock_secs = (real_time_ts.tv_sec - boot_time_ts.tv_sec) + |
| (real_time_ts.tv_nsec - boot_time_ts.tv_nsec + nsecs) / |
| 1000000000; |
| |
| |
| if (!localtime_r(&wallclock_secs, &load_tm)) { |
| snprintf(buf, size, "%llu", nsecs / 1000000000); |
| return; |
| } |
| |
| if (json_output) |
| strftime(buf, size, "%s", &load_tm); |
| else |
| strftime(buf, size, "%FT%T%z", &load_tm); |
| } |
| |
| static int prog_fd_by_tag(unsigned char *tag) |
| { |
| unsigned int id = 0; |
| int err; |
| int fd; |
| |
| while (true) { |
| struct bpf_prog_info info = {}; |
| __u32 len = sizeof(info); |
| |
| err = bpf_prog_get_next_id(id, &id); |
| if (err) { |
| p_err("%s", strerror(errno)); |
| return -1; |
| } |
| |
| fd = bpf_prog_get_fd_by_id(id); |
| if (fd < 0) { |
| p_err("can't get prog by id (%u): %s", |
| id, strerror(errno)); |
| return -1; |
| } |
| |
| err = bpf_obj_get_info_by_fd(fd, &info, &len); |
| if (err) { |
| p_err("can't get prog info (%u): %s", |
| id, strerror(errno)); |
| close(fd); |
| return -1; |
| } |
| |
| if (!memcmp(tag, info.tag, BPF_TAG_SIZE)) |
| return fd; |
| |
| close(fd); |
| } |
| } |
| |
| int prog_parse_fd(int *argc, char ***argv) |
| { |
| int fd; |
| |
| if (is_prefix(**argv, "id")) { |
| unsigned int id; |
| char *endptr; |
| |
| NEXT_ARGP(); |
| |
| id = strtoul(**argv, &endptr, 0); |
| if (*endptr) { |
| p_err("can't parse %s as ID", **argv); |
| return -1; |
| } |
| NEXT_ARGP(); |
| |
| fd = bpf_prog_get_fd_by_id(id); |
| if (fd < 0) |
| p_err("get by id (%u): %s", id, strerror(errno)); |
| return fd; |
| } else if (is_prefix(**argv, "tag")) { |
| unsigned char tag[BPF_TAG_SIZE]; |
| |
| NEXT_ARGP(); |
| |
| if (sscanf(**argv, BPF_TAG_FMT, tag, tag + 1, tag + 2, |
| tag + 3, tag + 4, tag + 5, tag + 6, tag + 7) |
| != BPF_TAG_SIZE) { |
| p_err("can't parse tag"); |
| return -1; |
| } |
| NEXT_ARGP(); |
| |
| return prog_fd_by_tag(tag); |
| } else if (is_prefix(**argv, "pinned")) { |
| char *path; |
| |
| NEXT_ARGP(); |
| |
| path = **argv; |
| NEXT_ARGP(); |
| |
| return open_obj_pinned_any(path, BPF_OBJ_PROG); |
| } |
| |
| p_err("expected 'id', 'tag' or 'pinned', got: '%s'?", **argv); |
| return -1; |
| } |
| |
| static void show_prog_maps(int fd, u32 num_maps) |
| { |
| struct bpf_prog_info info = {}; |
| __u32 len = sizeof(info); |
| __u32 map_ids[num_maps]; |
| unsigned int i; |
| int err; |
| |
| info.nr_map_ids = num_maps; |
| info.map_ids = ptr_to_u64(map_ids); |
| |
| err = bpf_obj_get_info_by_fd(fd, &info, &len); |
| if (err || !info.nr_map_ids) |
| return; |
| |
| if (json_output) { |
| jsonw_name(json_wtr, "map_ids"); |
| jsonw_start_array(json_wtr); |
| for (i = 0; i < info.nr_map_ids; i++) |
| jsonw_uint(json_wtr, map_ids[i]); |
| jsonw_end_array(json_wtr); |
| } else { |
| printf(" map_ids "); |
| for (i = 0; i < info.nr_map_ids; i++) |
| printf("%u%s", map_ids[i], |
| i == info.nr_map_ids - 1 ? "" : ","); |
| } |
| } |
| |
| static void print_prog_json(struct bpf_prog_info *info, int fd) |
| { |
| char *memlock; |
| |
| jsonw_start_object(json_wtr); |
| jsonw_uint_field(json_wtr, "id", info->id); |
| if (info->type < ARRAY_SIZE(prog_type_name)) |
| jsonw_string_field(json_wtr, "type", |
| prog_type_name[info->type]); |
| else |
| jsonw_uint_field(json_wtr, "type", info->type); |
| |
| if (*info->name) |
| jsonw_string_field(json_wtr, "name", info->name); |
| |
| jsonw_name(json_wtr, "tag"); |
| jsonw_printf(json_wtr, "\"" BPF_TAG_FMT "\"", |
| info->tag[0], info->tag[1], info->tag[2], info->tag[3], |
| info->tag[4], info->tag[5], info->tag[6], info->tag[7]); |
| |
| jsonw_bool_field(json_wtr, "gpl_compatible", info->gpl_compatible); |
| if (info->run_time_ns) { |
| jsonw_uint_field(json_wtr, "run_time_ns", info->run_time_ns); |
| jsonw_uint_field(json_wtr, "run_cnt", info->run_cnt); |
| } |
| |
| print_dev_json(info->ifindex, info->netns_dev, info->netns_ino); |
| |
| if (info->load_time) { |
| char buf[32]; |
| |
| print_boot_time(info->load_time, buf, sizeof(buf)); |
| |
| /* Piggy back on load_time, since 0 uid is a valid one */ |
| jsonw_name(json_wtr, "loaded_at"); |
| jsonw_printf(json_wtr, "%s", buf); |
| jsonw_uint_field(json_wtr, "uid", info->created_by_uid); |
| } |
| |
| jsonw_uint_field(json_wtr, "bytes_xlated", info->xlated_prog_len); |
| |
| if (info->jited_prog_len) { |
| jsonw_bool_field(json_wtr, "jited", true); |
| jsonw_uint_field(json_wtr, "bytes_jited", info->jited_prog_len); |
| } else { |
| jsonw_bool_field(json_wtr, "jited", false); |
| } |
| |
| memlock = get_fdinfo(fd, "memlock"); |
| if (memlock) |
| jsonw_int_field(json_wtr, "bytes_memlock", atoi(memlock)); |
| free(memlock); |
| |
| if (info->nr_map_ids) |
| show_prog_maps(fd, info->nr_map_ids); |
| |
| if (info->btf_id) |
| jsonw_int_field(json_wtr, "btf_id", info->btf_id); |
| |
| if (!hash_empty(prog_table.table)) { |
| struct pinned_obj *obj; |
| |
| jsonw_name(json_wtr, "pinned"); |
| jsonw_start_array(json_wtr); |
| hash_for_each_possible(prog_table.table, obj, hash, info->id) { |
| if (obj->id == info->id) |
| jsonw_string(json_wtr, obj->path); |
| } |
| jsonw_end_array(json_wtr); |
| } |
| |
| jsonw_end_object(json_wtr); |
| } |
| |
| static void print_prog_plain(struct bpf_prog_info *info, int fd) |
| { |
| char *memlock; |
| |
| printf("%u: ", info->id); |
| if (info->type < ARRAY_SIZE(prog_type_name)) |
| printf("%s ", prog_type_name[info->type]); |
| else |
| printf("type %u ", info->type); |
| |
| if (*info->name) |
| printf("name %s ", info->name); |
| |
| printf("tag "); |
| fprint_hex(stdout, info->tag, BPF_TAG_SIZE, ""); |
| print_dev_plain(info->ifindex, info->netns_dev, info->netns_ino); |
| printf("%s", info->gpl_compatible ? " gpl" : ""); |
| if (info->run_time_ns) |
| printf(" run_time_ns %lld run_cnt %lld", |
| info->run_time_ns, info->run_cnt); |
| printf("\n"); |
| |
| if (info->load_time) { |
| char buf[32]; |
| |
| print_boot_time(info->load_time, buf, sizeof(buf)); |
| |
| /* Piggy back on load_time, since 0 uid is a valid one */ |
| printf("\tloaded_at %s uid %u\n", buf, info->created_by_uid); |
| } |
| |
| printf("\txlated %uB", info->xlated_prog_len); |
| |
| if (info->jited_prog_len) |
| printf(" jited %uB", info->jited_prog_len); |
| else |
| printf(" not jited"); |
| |
| memlock = get_fdinfo(fd, "memlock"); |
| if (memlock) |
| printf(" memlock %sB", memlock); |
| free(memlock); |
| |
| if (info->nr_map_ids) |
| show_prog_maps(fd, info->nr_map_ids); |
| |
| if (!hash_empty(prog_table.table)) { |
| struct pinned_obj *obj; |
| |
| hash_for_each_possible(prog_table.table, obj, hash, info->id) { |
| if (obj->id == info->id) |
| printf("\n\tpinned %s", obj->path); |
| } |
| } |
| |
| if (info->btf_id) |
| printf("\n\tbtf_id %d", info->btf_id); |
| |
| printf("\n"); |
| } |
| |
| static int show_prog(int fd) |
| { |
| struct bpf_prog_info info = {}; |
| __u32 len = sizeof(info); |
| int err; |
| |
| err = bpf_obj_get_info_by_fd(fd, &info, &len); |
| if (err) { |
| p_err("can't get prog info: %s", strerror(errno)); |
| return -1; |
| } |
| |
| if (json_output) |
| print_prog_json(&info, fd); |
| else |
| print_prog_plain(&info, fd); |
| |
| return 0; |
| } |
| |
| static int do_show(int argc, char **argv) |
| { |
| __u32 id = 0; |
| int err; |
| int fd; |
| |
| if (show_pinned) |
| build_pinned_obj_table(&prog_table, BPF_OBJ_PROG); |
| |
| if (argc == 2) { |
| fd = prog_parse_fd(&argc, &argv); |
| if (fd < 0) |
| return -1; |
| |
| err = show_prog(fd); |
| close(fd); |
| return err; |
| } |
| |
| if (argc) |
| return BAD_ARG(); |
| |
| if (json_output) |
| jsonw_start_array(json_wtr); |
| while (true) { |
| err = bpf_prog_get_next_id(id, &id); |
| if (err) { |
| if (errno == ENOENT) { |
| err = 0; |
| break; |
| } |
| p_err("can't get next program: %s%s", strerror(errno), |
| errno == EINVAL ? " -- kernel too old?" : ""); |
| err = -1; |
| break; |
| } |
| |
| fd = bpf_prog_get_fd_by_id(id); |
| if (fd < 0) { |
| if (errno == ENOENT) |
| continue; |
| p_err("can't get prog by id (%u): %s", |
| id, strerror(errno)); |
| err = -1; |
| break; |
| } |
| |
| err = show_prog(fd); |
| close(fd); |
| if (err) |
| break; |
| } |
| |
| if (json_output) |
| jsonw_end_array(json_wtr); |
| |
| return err; |
| } |
| |
| static int do_dump(int argc, char **argv) |
| { |
| struct bpf_prog_info_linear *info_linear; |
| struct bpf_prog_linfo *prog_linfo = NULL; |
| enum {DUMP_JITED, DUMP_XLATED} mode; |
| const char *disasm_opt = NULL; |
| struct bpf_prog_info *info; |
| struct dump_data dd = {}; |
| void *func_info = NULL; |
| struct btf *btf = NULL; |
| char *filepath = NULL; |
| bool opcodes = false; |
| bool visual = false; |
| char func_sig[1024]; |
| unsigned char *buf; |
| bool linum = false; |
| __u32 member_len; |
| __u64 arrays; |
| ssize_t n; |
| int fd; |
| |
| if (is_prefix(*argv, "jited")) { |
| if (disasm_init()) |
| return -1; |
| mode = DUMP_JITED; |
| } else if (is_prefix(*argv, "xlated")) { |
| mode = DUMP_XLATED; |
| } else { |
| p_err("expected 'xlated' or 'jited', got: %s", *argv); |
| return -1; |
| } |
| NEXT_ARG(); |
| |
| if (argc < 2) |
| usage(); |
| |
| fd = prog_parse_fd(&argc, &argv); |
| if (fd < 0) |
| return -1; |
| |
| if (is_prefix(*argv, "file")) { |
| NEXT_ARG(); |
| if (!argc) { |
| p_err("expected file path"); |
| return -1; |
| } |
| |
| filepath = *argv; |
| NEXT_ARG(); |
| } else if (is_prefix(*argv, "opcodes")) { |
| opcodes = true; |
| NEXT_ARG(); |
| } else if (is_prefix(*argv, "visual")) { |
| visual = true; |
| NEXT_ARG(); |
| } else if (is_prefix(*argv, "linum")) { |
| linum = true; |
| NEXT_ARG(); |
| } |
| |
| if (argc) { |
| usage(); |
| return -1; |
| } |
| |
| if (mode == DUMP_JITED) |
| arrays = 1UL << BPF_PROG_INFO_JITED_INSNS; |
| else |
| arrays = 1UL << BPF_PROG_INFO_XLATED_INSNS; |
| |
| arrays |= 1UL << BPF_PROG_INFO_JITED_KSYMS; |
| arrays |= 1UL << BPF_PROG_INFO_JITED_FUNC_LENS; |
| arrays |= 1UL << BPF_PROG_INFO_FUNC_INFO; |
| arrays |= 1UL << BPF_PROG_INFO_LINE_INFO; |
| arrays |= 1UL << BPF_PROG_INFO_JITED_LINE_INFO; |
| |
| info_linear = bpf_program__get_prog_info_linear(fd, arrays); |
| close(fd); |
| if (IS_ERR_OR_NULL(info_linear)) { |
| p_err("can't get prog info: %s", strerror(errno)); |
| return -1; |
| } |
| |
| info = &info_linear->info; |
| if (mode == DUMP_JITED) { |
| if (info->jited_prog_len == 0 || !info->jited_prog_insns) { |
| p_info("no instructions returned"); |
| goto err_free; |
| } |
| buf = (unsigned char *)(info->jited_prog_insns); |
| member_len = info->jited_prog_len; |
| } else { /* DUMP_XLATED */ |
| if (info->xlated_prog_len == 0) { |
| p_err("error retrieving insn dump: kernel.kptr_restrict set?"); |
| goto err_free; |
| } |
| buf = (unsigned char *)info->xlated_prog_insns; |
| member_len = info->xlated_prog_len; |
| } |
| |
| if (info->btf_id && btf__get_from_id(info->btf_id, &btf)) { |
| p_err("failed to get btf"); |
| goto err_free; |
| } |
| |
| func_info = (void *)info->func_info; |
| |
| if (info->nr_line_info) { |
| prog_linfo = bpf_prog_linfo__new(info); |
| if (!prog_linfo) |
| p_info("error in processing bpf_line_info. continue without it."); |
| } |
| |
| if (filepath) { |
| fd = open(filepath, O_WRONLY | O_CREAT | O_TRUNC, 0600); |
| if (fd < 0) { |
| p_err("can't open file %s: %s", filepath, |
| strerror(errno)); |
| goto err_free; |
| } |
| |
| n = write(fd, buf, member_len); |
| close(fd); |
| if (n != member_len) { |
| p_err("error writing output file: %s", |
| n < 0 ? strerror(errno) : "short write"); |
| goto err_free; |
| } |
| |
| if (json_output) |
| jsonw_null(json_wtr); |
| } else if (mode == DUMP_JITED) { |
| const char *name = NULL; |
| |
| if (info->ifindex) { |
| name = ifindex_to_bfd_params(info->ifindex, |
| info->netns_dev, |
| info->netns_ino, |
| &disasm_opt); |
| if (!name) |
| goto err_free; |
| } |
| |
| if (info->nr_jited_func_lens && info->jited_func_lens) { |
| struct kernel_sym *sym = NULL; |
| struct bpf_func_info *record; |
| char sym_name[SYM_MAX_NAME]; |
| unsigned char *img = buf; |
| __u64 *ksyms = NULL; |
| __u32 *lens; |
| __u32 i; |
| if (info->nr_jited_ksyms) { |
| kernel_syms_load(&dd); |
| ksyms = (__u64 *) info->jited_ksyms; |
| } |
| |
| if (json_output) |
| jsonw_start_array(json_wtr); |
| |
| lens = (__u32 *) info->jited_func_lens; |
| for (i = 0; i < info->nr_jited_func_lens; i++) { |
| if (ksyms) { |
| sym = kernel_syms_search(&dd, ksyms[i]); |
| if (sym) |
| sprintf(sym_name, "%s", sym->name); |
| else |
| sprintf(sym_name, "0x%016llx", ksyms[i]); |
| } else { |
| strcpy(sym_name, "unknown"); |
| } |
| |
| if (func_info) { |
| record = func_info + i * info->func_info_rec_size; |
| btf_dumper_type_only(btf, record->type_id, |
| func_sig, |
| sizeof(func_sig)); |
| } |
| |
| if (json_output) { |
| jsonw_start_object(json_wtr); |
| if (func_info && func_sig[0] != '\0') { |
| jsonw_name(json_wtr, "proto"); |
| jsonw_string(json_wtr, func_sig); |
| } |
| jsonw_name(json_wtr, "name"); |
| jsonw_string(json_wtr, sym_name); |
| jsonw_name(json_wtr, "insns"); |
| } else { |
| if (func_info && func_sig[0] != '\0') |
| printf("%s:\n", func_sig); |
| printf("%s:\n", sym_name); |
| } |
| |
| disasm_print_insn(img, lens[i], opcodes, |
| name, disasm_opt, btf, |
| prog_linfo, ksyms[i], i, |
| linum); |
| |
| img += lens[i]; |
| |
| if (json_output) |
| jsonw_end_object(json_wtr); |
| else |
| printf("\n"); |
| } |
| |
| if (json_output) |
| jsonw_end_array(json_wtr); |
| } else { |
| disasm_print_insn(buf, member_len, opcodes, name, |
| disasm_opt, btf, NULL, 0, 0, false); |
| } |
| } else if (visual) { |
| if (json_output) |
| jsonw_null(json_wtr); |
| else |
| dump_xlated_cfg(buf, member_len); |
| } else { |
| kernel_syms_load(&dd); |
| dd.nr_jited_ksyms = info->nr_jited_ksyms; |
| dd.jited_ksyms = (__u64 *) info->jited_ksyms; |
| dd.btf = btf; |
| dd.func_info = func_info; |
| dd.finfo_rec_size = info->func_info_rec_size; |
| dd.prog_linfo = prog_linfo; |
| |
| if (json_output) |
| dump_xlated_json(&dd, buf, member_len, opcodes, |
| linum); |
| else |
| dump_xlated_plain(&dd, buf, member_len, opcodes, |
| linum); |
| kernel_syms_destroy(&dd); |
| } |
| |
| free(info_linear); |
| return 0; |
| |
| err_free: |
| free(info_linear); |
| return -1; |
| } |
| |
| static int do_pin(int argc, char **argv) |
| { |
| int err; |
| |
| err = do_pin_any(argc, argv, bpf_prog_get_fd_by_id); |
| if (!err && json_output) |
| jsonw_null(json_wtr); |
| return err; |
| } |
| |
| struct map_replace { |
| int idx; |
| int fd; |
| char *name; |
| }; |
| |
| static int map_replace_compar(const void *p1, const void *p2) |
| { |
| const struct map_replace *a = p1, *b = p2; |
| |
| return a->idx - b->idx; |
| } |
| |
| static int parse_attach_detach_args(int argc, char **argv, int *progfd, |
| enum bpf_attach_type *attach_type, |
| int *mapfd) |
| { |
| if (!REQ_ARGS(3)) |
| return -EINVAL; |
| |
| *progfd = prog_parse_fd(&argc, &argv); |
| if (*progfd < 0) |
| return *progfd; |
| |
| *attach_type = parse_attach_type(*argv); |
| if (*attach_type == __MAX_BPF_ATTACH_TYPE) { |
| p_err("invalid attach/detach type"); |
| return -EINVAL; |
| } |
| |
| if (*attach_type == BPF_FLOW_DISSECTOR) { |
| *mapfd = -1; |
| return 0; |
| } |
| |
| NEXT_ARG(); |
| if (!REQ_ARGS(2)) |
| return -EINVAL; |
| |
| *mapfd = map_parse_fd(&argc, &argv); |
| if (*mapfd < 0) |
| return *mapfd; |
| |
| return 0; |
| } |
| |
| static int do_attach(int argc, char **argv) |
| { |
| enum bpf_attach_type attach_type; |
| int err, progfd; |
| int mapfd; |
| |
| err = parse_attach_detach_args(argc, argv, |
| &progfd, &attach_type, &mapfd); |
| if (err) |
| return err; |
| |
| err = bpf_prog_attach(progfd, mapfd, attach_type, 0); |
| if (err) { |
| p_err("failed prog attach to map"); |
| return -EINVAL; |
| } |
| |
| if (json_output) |
| jsonw_null(json_wtr); |
| return 0; |
| } |
| |
| static int do_detach(int argc, char **argv) |
| { |
| enum bpf_attach_type attach_type; |
| int err, progfd; |
| int mapfd; |
| |
| err = parse_attach_detach_args(argc, argv, |
| &progfd, &attach_type, &mapfd); |
| if (err) |
| return err; |
| |
| err = bpf_prog_detach2(progfd, mapfd, attach_type); |
| if (err) { |
| p_err("failed prog detach from map"); |
| return -EINVAL; |
| } |
| |
| if (json_output) |
| jsonw_null(json_wtr); |
| return 0; |
| } |
| |
| static int check_single_stdin(char *file_data_in, char *file_ctx_in) |
| { |
| if (file_data_in && file_ctx_in && |
| !strcmp(file_data_in, "-") && !strcmp(file_ctx_in, "-")) { |
| p_err("cannot use standard input for both data_in and ctx_in"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int get_run_data(const char *fname, void **data_ptr, unsigned int *size) |
| { |
| size_t block_size = 256; |
| size_t buf_size = block_size; |
| size_t nb_read = 0; |
| void *tmp; |
| FILE *f; |
| |
| if (!fname) { |
| *data_ptr = NULL; |
| *size = 0; |
| return 0; |
| } |
| |
| if (!strcmp(fname, "-")) |
| f = stdin; |
| else |
| f = fopen(fname, "r"); |
| if (!f) { |
| p_err("failed to open %s: %s", fname, strerror(errno)); |
| return -1; |
| } |
| |
| *data_ptr = malloc(block_size); |
| if (!*data_ptr) { |
| p_err("failed to allocate memory for data_in/ctx_in: %s", |
| strerror(errno)); |
| goto err_fclose; |
| } |
| |
| while ((nb_read += fread(*data_ptr + nb_read, 1, block_size, f))) { |
| if (feof(f)) |
| break; |
| if (ferror(f)) { |
| p_err("failed to read data_in/ctx_in from %s: %s", |
| fname, strerror(errno)); |
| goto err_free; |
| } |
| if (nb_read > buf_size - block_size) { |
| if (buf_size == UINT32_MAX) { |
| p_err("data_in/ctx_in is too long (max: %d)", |
| UINT32_MAX); |
| goto err_free; |
| } |
| /* No space for fread()-ing next chunk; realloc() */ |
| buf_size *= 2; |
| tmp = realloc(*data_ptr, buf_size); |
| if (!tmp) { |
| p_err("failed to reallocate data_in/ctx_in: %s", |
| strerror(errno)); |
| goto err_free; |
| } |
| *data_ptr = tmp; |
| } |
| } |
| if (f != stdin) |
| fclose(f); |
| |
| *size = nb_read; |
| return 0; |
| |
| err_free: |
| free(*data_ptr); |
| *data_ptr = NULL; |
| err_fclose: |
| if (f != stdin) |
| fclose(f); |
| return -1; |
| } |
| |
| static void hex_print(void *data, unsigned int size, FILE *f) |
| { |
| size_t i, j; |
| char c; |
| |
| for (i = 0; i < size; i += 16) { |
| /* Row offset */ |
| fprintf(f, "%07zx\t", i); |
| |
| /* Hexadecimal values */ |
| for (j = i; j < i + 16 && j < size; j++) |
| fprintf(f, "%02x%s", *(uint8_t *)(data + j), |
| j % 2 ? " " : ""); |
| for (; j < i + 16; j++) |
| fprintf(f, " %s", j % 2 ? " " : ""); |
| |
| /* ASCII values (if relevant), '.' otherwise */ |
| fprintf(f, "| "); |
| for (j = i; j < i + 16 && j < size; j++) { |
| c = *(char *)(data + j); |
| if (c < ' ' || c > '~') |
| c = '.'; |
| fprintf(f, "%c%s", c, j == i + 7 ? " " : ""); |
| } |
| |
| fprintf(f, "\n"); |
| } |
| } |
| |
| static int |
| print_run_output(void *data, unsigned int size, const char *fname, |
| const char *json_key) |
| { |
| size_t nb_written; |
| FILE *f; |
| |
| if (!fname) |
| return 0; |
| |
| if (!strcmp(fname, "-")) { |
| f = stdout; |
| if (json_output) { |
| jsonw_name(json_wtr, json_key); |
| print_data_json(data, size); |
| } else { |
| hex_print(data, size, f); |
| } |
| return 0; |
| } |
| |
| f = fopen(fname, "w"); |
| if (!f) { |
| p_err("failed to open %s: %s", fname, strerror(errno)); |
| return -1; |
| } |
| |
| nb_written = fwrite(data, 1, size, f); |
| fclose(f); |
| if (nb_written != size) { |
| p_err("failed to write output data/ctx: %s", strerror(errno)); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int alloc_run_data(void **data_ptr, unsigned int size_out) |
| { |
| *data_ptr = calloc(size_out, 1); |
| if (!*data_ptr) { |
| p_err("failed to allocate memory for output data/ctx: %s", |
| strerror(errno)); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int do_run(int argc, char **argv) |
| { |
| char *data_fname_in = NULL, *data_fname_out = NULL; |
| char *ctx_fname_in = NULL, *ctx_fname_out = NULL; |
| struct bpf_prog_test_run_attr test_attr = {0}; |
| const unsigned int default_size = SZ_32K; |
| void *data_in = NULL, *data_out = NULL; |
| void *ctx_in = NULL, *ctx_out = NULL; |
| unsigned int repeat = 1; |
| int fd, err; |
| |
| if (!REQ_ARGS(4)) |
| return -1; |
| |
| fd = prog_parse_fd(&argc, &argv); |
| if (fd < 0) |
| return -1; |
| |
| while (argc) { |
| if (detect_common_prefix(*argv, "data_in", "data_out", |
| "data_size_out", NULL)) |
| return -1; |
| if (detect_common_prefix(*argv, "ctx_in", "ctx_out", |
| "ctx_size_out", NULL)) |
| return -1; |
| |
| if (is_prefix(*argv, "data_in")) { |
| NEXT_ARG(); |
| if (!REQ_ARGS(1)) |
| return -1; |
| |
| data_fname_in = GET_ARG(); |
| if (check_single_stdin(data_fname_in, ctx_fname_in)) |
| return -1; |
| } else if (is_prefix(*argv, "data_out")) { |
| NEXT_ARG(); |
| if (!REQ_ARGS(1)) |
| return -1; |
| |
| data_fname_out = GET_ARG(); |
| } else if (is_prefix(*argv, "data_size_out")) { |
| char *endptr; |
| |
| NEXT_ARG(); |
| if (!REQ_ARGS(1)) |
| return -1; |
| |
| test_attr.data_size_out = strtoul(*argv, &endptr, 0); |
| if (*endptr) { |
| p_err("can't parse %s as output data size", |
| *argv); |
| return -1; |
| } |
| NEXT_ARG(); |
| } else if (is_prefix(*argv, "ctx_in")) { |
| NEXT_ARG(); |
| if (!REQ_ARGS(1)) |
| return -1; |
| |
| ctx_fname_in = GET_ARG(); |
| if (check_single_stdin(data_fname_in, ctx_fname_in)) |
| return -1; |
| } else if (is_prefix(*argv, "ctx_out")) { |
| NEXT_ARG(); |
| if (!REQ_ARGS(1)) |
| return -1; |
| |
| ctx_fname_out = GET_ARG(); |
| } else if (is_prefix(*argv, "ctx_size_out")) { |
| char *endptr; |
| |
| NEXT_ARG(); |
| if (!REQ_ARGS(1)) |
| return -1; |
| |
| test_attr.ctx_size_out = strtoul(*argv, &endptr, 0); |
| if (*endptr) { |
| p_err("can't parse %s as output context size", |
| *argv); |
| return -1; |
| } |
| NEXT_ARG(); |
| } else if (is_prefix(*argv, "repeat")) { |
| char *endptr; |
| |
| NEXT_ARG(); |
| if (!REQ_ARGS(1)) |
| return -1; |
| |
| repeat = strtoul(*argv, &endptr, 0); |
| if (*endptr) { |
| p_err("can't parse %s as repeat number", |
| *argv); |
| return -1; |
| } |
| NEXT_ARG(); |
| } else { |
| p_err("expected no more arguments, 'data_in', 'data_out', 'data_size_out', 'ctx_in', 'ctx_out', 'ctx_size_out' or 'repeat', got: '%s'?", |
| *argv); |
| return -1; |
| } |
| } |
| |
| err = get_run_data(data_fname_in, &data_in, &test_attr.data_size_in); |
| if (err) |
| return -1; |
| |
| if (data_in) { |
| if (!test_attr.data_size_out) |
| test_attr.data_size_out = default_size; |
| err = alloc_run_data(&data_out, test_attr.data_size_out); |
| if (err) |
| goto free_data_in; |
| } |
| |
| err = get_run_data(ctx_fname_in, &ctx_in, &test_attr.ctx_size_in); |
| if (err) |
| goto free_data_out; |
| |
| if (ctx_in) { |
| if (!test_attr.ctx_size_out) |
| test_attr.ctx_size_out = default_size; |
| err = alloc_run_data(&ctx_out, test_attr.ctx_size_out); |
| if (err) |
| goto free_ctx_in; |
| } |
| |
| test_attr.prog_fd = fd; |
| test_attr.repeat = repeat; |
| test_attr.data_in = data_in; |
| test_attr.data_out = data_out; |
| test_attr.ctx_in = ctx_in; |
| test_attr.ctx_out = ctx_out; |
| |
| err = bpf_prog_test_run_xattr(&test_attr); |
| if (err) { |
| p_err("failed to run program: %s", strerror(errno)); |
| goto free_ctx_out; |
| } |
| |
| err = 0; |
| |
| if (json_output) |
| jsonw_start_object(json_wtr); /* root */ |
| |
| /* Do not exit on errors occurring when printing output data/context, |
| * we still want to print return value and duration for program run. |
| */ |
| if (test_attr.data_size_out) |
| err += print_run_output(test_attr.data_out, |
| test_attr.data_size_out, |
| data_fname_out, "data_out"); |
| if (test_attr.ctx_size_out) |
| err += print_run_output(test_attr.ctx_out, |
| test_attr.ctx_size_out, |
| ctx_fname_out, "ctx_out"); |
| |
| if (json_output) { |
| jsonw_uint_field(json_wtr, "retval", test_attr.retval); |
| jsonw_uint_field(json_wtr, "duration", test_attr.duration); |
| jsonw_end_object(json_wtr); /* root */ |
| } else { |
| fprintf(stdout, "Return value: %u, duration%s: %uns\n", |
| test_attr.retval, |
| repeat > 1 ? " (average)" : "", test_attr.duration); |
| } |
| |
| free_ctx_out: |
| free(ctx_out); |
| free_ctx_in: |
| free(ctx_in); |
| free_data_out: |
| free(data_out); |
| free_data_in: |
| free(data_in); |
| |
| return err; |
| } |
| |
| static int load_with_options(int argc, char **argv, bool first_prog_only) |
| { |
| enum bpf_prog_type common_prog_type = BPF_PROG_TYPE_UNSPEC; |
| DECLARE_LIBBPF_OPTS(bpf_object_open_opts, open_opts, |
| .relaxed_maps = relaxed_maps, |
| ); |
| struct bpf_object_load_attr load_attr = { 0 }; |
| enum bpf_attach_type expected_attach_type; |
| struct map_replace *map_replace = NULL; |
| struct bpf_program *prog = NULL, *pos; |
| unsigned int old_map_fds = 0; |
| const char *pinmaps = NULL; |
| struct bpf_object *obj; |
| struct bpf_map *map; |
| const char *pinfile; |
| unsigned int i, j; |
| __u32 ifindex = 0; |
| const char *file; |
| int idx, err; |
| |
| |
| if (!REQ_ARGS(2)) |
| return -1; |
| file = GET_ARG(); |
| pinfile = GET_ARG(); |
| |
| while (argc) { |
| if (is_prefix(*argv, "type")) { |
| char *type; |
| |
| NEXT_ARG(); |
| |
| if (common_prog_type != BPF_PROG_TYPE_UNSPEC) { |
| p_err("program type already specified"); |
| goto err_free_reuse_maps; |
| } |
| if (!REQ_ARGS(1)) |
| goto err_free_reuse_maps; |
| |
| /* Put a '/' at the end of type to appease libbpf */ |
| type = malloc(strlen(*argv) + 2); |
| if (!type) { |
| p_err("mem alloc failed"); |
| goto err_free_reuse_maps; |
| } |
| *type = 0; |
| strcat(type, *argv); |
| strcat(type, "/"); |
| |
| err = libbpf_prog_type_by_name(type, &common_prog_type, |
| &expected_attach_type); |
| free(type); |
| if (err < 0) |
| goto err_free_reuse_maps; |
| |
| NEXT_ARG(); |
| } else if (is_prefix(*argv, "map")) { |
| void *new_map_replace; |
| char *endptr, *name; |
| int fd; |
| |
| NEXT_ARG(); |
| |
| if (!REQ_ARGS(4)) |
| goto err_free_reuse_maps; |
| |
| if (is_prefix(*argv, "idx")) { |
| NEXT_ARG(); |
| |
| idx = strtoul(*argv, &endptr, 0); |
| if (*endptr) { |
| p_err("can't parse %s as IDX", *argv); |
| goto err_free_reuse_maps; |
| } |
| name = NULL; |
| } else if (is_prefix(*argv, "name")) { |
| NEXT_ARG(); |
| |
| name = *argv; |
| idx = -1; |
| } else { |
| p_err("expected 'idx' or 'name', got: '%s'?", |
| *argv); |
| goto err_free_reuse_maps; |
| } |
| NEXT_ARG(); |
| |
| fd = map_parse_fd(&argc, &argv); |
| if (fd < 0) |
| goto err_free_reuse_maps; |
| |
| new_map_replace = reallocarray(map_replace, |
| old_map_fds + 1, |
| sizeof(*map_replace)); |
| if (!new_map_replace) { |
| p_err("mem alloc failed"); |
| goto err_free_reuse_maps; |
| } |
| map_replace = new_map_replace; |
| |
| map_replace[old_map_fds].idx = idx; |
| map_replace[old_map_fds].name = name; |
| map_replace[old_map_fds].fd = fd; |
| old_map_fds++; |
| } else if (is_prefix(*argv, "dev")) { |
| NEXT_ARG(); |
| |
| if (ifindex) { |
| p_err("offload device already specified"); |
| goto err_free_reuse_maps; |
| } |
| if (!REQ_ARGS(1)) |
| goto err_free_reuse_maps; |
| |
| ifindex = if_nametoindex(*argv); |
| if (!ifindex) { |
| p_err("unrecognized netdevice '%s': %s", |
| *argv, strerror(errno)); |
| goto err_free_reuse_maps; |
| } |
| NEXT_ARG(); |
| } else if (is_prefix(*argv, "pinmaps")) { |
| NEXT_ARG(); |
| |
| if (!REQ_ARGS(1)) |
| goto err_free_reuse_maps; |
| |
| pinmaps = GET_ARG(); |
| } else { |
| p_err("expected no more arguments, 'type', 'map' or 'dev', got: '%s'?", |
| *argv); |
| goto err_free_reuse_maps; |
| } |
| } |
| |
| set_max_rlimit(); |
| |
| obj = bpf_object__open_file(file, &open_opts); |
| if (IS_ERR_OR_NULL(obj)) { |
| p_err("failed to open object file"); |
| goto err_free_reuse_maps; |
| } |
| |
| bpf_object__for_each_program(pos, obj) { |
| enum bpf_prog_type prog_type = common_prog_type; |
| |
| if (prog_type == BPF_PROG_TYPE_UNSPEC) { |
| const char *sec_name = bpf_program__title(pos, false); |
| |
| err = libbpf_prog_type_by_name(sec_name, &prog_type, |
| &expected_attach_type); |
| if (err < 0) |
| goto err_close_obj; |
| } |
| |
| bpf_program__set_ifindex(pos, ifindex); |
| bpf_program__set_type(pos, prog_type); |
| bpf_program__set_expected_attach_type(pos, expected_attach_type); |
| } |
| |
| qsort(map_replace, old_map_fds, sizeof(*map_replace), |
| map_replace_compar); |
| |
| /* After the sort maps by name will be first on the list, because they |
| * have idx == -1. Resolve them. |
| */ |
| j = 0; |
| while (j < old_map_fds && map_replace[j].name) { |
| i = 0; |
| bpf_object__for_each_map(map, obj) { |
| if (!strcmp(bpf_map__name(map), map_replace[j].name)) { |
| map_replace[j].idx = i; |
| break; |
| } |
| i++; |
| } |
| if (map_replace[j].idx == -1) { |
| p_err("unable to find map '%s'", map_replace[j].name); |
| goto err_close_obj; |
| } |
| j++; |
| } |
| /* Resort if any names were resolved */ |
| if (j) |
| qsort(map_replace, old_map_fds, sizeof(*map_replace), |
| map_replace_compar); |
| |
| /* Set ifindex and name reuse */ |
| j = 0; |
| idx = 0; |
| bpf_object__for_each_map(map, obj) { |
| if (!bpf_map__is_offload_neutral(map)) |
| bpf_map__set_ifindex(map, ifindex); |
| |
| if (j < old_map_fds && idx == map_replace[j].idx) { |
| err = bpf_map__reuse_fd(map, map_replace[j++].fd); |
| if (err) { |
| p_err("unable to set up map reuse: %d", err); |
| goto err_close_obj; |
| } |
| |
| /* Next reuse wants to apply to the same map */ |
| if (j < old_map_fds && map_replace[j].idx == idx) { |
| p_err("replacement for map idx %d specified more than once", |
| idx); |
| goto err_close_obj; |
| } |
| } |
| |
| idx++; |
| } |
| if (j < old_map_fds) { |
| p_err("map idx '%d' not used", map_replace[j].idx); |
| goto err_close_obj; |
| } |
| |
| load_attr.obj = obj; |
| if (verifier_logs) |
| /* log_level1 + log_level2 + stats, but not stable UAPI */ |
| load_attr.log_level = 1 + 2 + 4; |
| |
| err = bpf_object__load_xattr(&load_attr); |
| if (err) { |
| p_err("failed to load object file"); |
| goto err_close_obj; |
| } |
| |
| err = mount_bpffs_for_pin(pinfile); |
| if (err) |
| goto err_close_obj; |
| |
| if (first_prog_only) { |
| prog = bpf_program__next(NULL, obj); |
| if (!prog) { |
| p_err("object file doesn't contain any bpf program"); |
| goto err_close_obj; |
| } |
| |
| err = bpf_obj_pin(bpf_program__fd(prog), pinfile); |
| if (err) { |
| p_err("failed to pin program %s", |
| bpf_program__title(prog, false)); |
| goto err_close_obj; |
| } |
| } else { |
| err = bpf_object__pin_programs(obj, pinfile); |
| if (err) { |
| p_err("failed to pin all programs"); |
| goto err_close_obj; |
| } |
| } |
| |
| if (pinmaps) { |
| err = bpf_object__pin_maps(obj, pinmaps); |
| if (err) { |
| p_err("failed to pin all maps"); |
| goto err_unpin; |
| } |
| } |
| |
| if (json_output) |
| jsonw_null(json_wtr); |
| |
| bpf_object__close(obj); |
| for (i = 0; i < old_map_fds; i++) |
| close(map_replace[i].fd); |
| free(map_replace); |
| |
| return 0; |
| |
| err_unpin: |
| if (first_prog_only) |
| unlink(pinfile); |
| else |
| bpf_object__unpin_programs(obj, pinfile); |
| err_close_obj: |
| bpf_object__close(obj); |
| err_free_reuse_maps: |
| for (i = 0; i < old_map_fds; i++) |
| close(map_replace[i].fd); |
| free(map_replace); |
| return -1; |
| } |
| |
| static int do_load(int argc, char **argv) |
| { |
| return load_with_options(argc, argv, true); |
| } |
| |
| static int do_loadall(int argc, char **argv) |
| { |
| return load_with_options(argc, argv, false); |
| } |
| |
| static int do_help(int argc, char **argv) |
| { |
| if (json_output) { |
| jsonw_null(json_wtr); |
| return 0; |
| } |
| |
| fprintf(stderr, |
| "Usage: %s %s { show | list } [PROG]\n" |
| " %s %s dump xlated PROG [{ file FILE | opcodes | visual | linum }]\n" |
| " %s %s dump jited PROG [{ file FILE | opcodes | linum }]\n" |
| " %s %s pin PROG FILE\n" |
| " %s %s { load | loadall } OBJ PATH \\\n" |
| " [type TYPE] [dev NAME] \\\n" |
| " [map { idx IDX | name NAME } MAP]\\\n" |
| " [pinmaps MAP_DIR]\n" |
| " %s %s attach PROG ATTACH_TYPE [MAP]\n" |
| " %s %s detach PROG ATTACH_TYPE [MAP]\n" |
| " %s %s run PROG \\\n" |
| " data_in FILE \\\n" |
| " [data_out FILE [data_size_out L]] \\\n" |
| " [ctx_in FILE [ctx_out FILE [ctx_size_out M]]] \\\n" |
| " [repeat N]\n" |
| " %s %s tracelog\n" |
| " %s %s help\n" |
| "\n" |
| " " HELP_SPEC_MAP "\n" |
| " " HELP_SPEC_PROGRAM "\n" |
| " TYPE := { socket | kprobe | kretprobe | classifier | action |\n" |
| " tracepoint | raw_tracepoint | xdp | perf_event | cgroup/skb |\n" |
| " cgroup/sock | cgroup/dev | lwt_in | lwt_out | lwt_xmit |\n" |
| " lwt_seg6local | sockops | sk_skb | sk_msg | lirc_mode2 |\n" |
| " sk_reuseport | flow_dissector | cgroup/sysctl |\n" |
| " cgroup/bind4 | cgroup/bind6 | cgroup/post_bind4 |\n" |
| " cgroup/post_bind6 | cgroup/connect4 | cgroup/connect6 |\n" |
| " cgroup/sendmsg4 | cgroup/sendmsg6 | cgroup/recvmsg4 |\n" |
| " cgroup/recvmsg6 | cgroup/getsockopt |\n" |
| " cgroup/setsockopt }\n" |
| " ATTACH_TYPE := { msg_verdict | stream_verdict | stream_parser |\n" |
| " flow_dissector }\n" |
| " " HELP_SPEC_OPTIONS "\n" |
| "", |
| bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], |
| bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], |
| bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], |
| bin_name, argv[-2]); |
| |
| return 0; |
| } |
| |
| static const struct cmd cmds[] = { |
| { "show", do_show }, |
| { "list", do_show }, |
| { "help", do_help }, |
| { "dump", do_dump }, |
| { "pin", do_pin }, |
| { "load", do_load }, |
| { "loadall", do_loadall }, |
| { "attach", do_attach }, |
| { "detach", do_detach }, |
| { "tracelog", do_tracelog }, |
| { "run", do_run }, |
| { 0 } |
| }; |
| |
| int do_prog(int argc, char **argv) |
| { |
| return cmd_select(cmds, argc, argv, do_help); |
| } |