bpftool: Add bpf_link show and pin support
Add `bpftool link show` and `bpftool link pin` commands. Example plain output for `link show` (with showing pinned paths): [vmuser@archvm bpf]$ sudo ~/local/linux/tools/bpf/bpftool/bpftool -f link 1: tracing prog 12 prog_type tracing attach_type fentry pinned /sys/fs/bpf/my_test_link pinned /sys/fs/bpf/my_test_link2 2: tracing prog 13 prog_type tracing attach_type fentry 3: tracing prog 14 prog_type tracing attach_type fentry 4: tracing prog 15 prog_type tracing attach_type fentry 5: tracing prog 16 prog_type tracing attach_type fentry 6: tracing prog 17 prog_type tracing attach_type fentry 7: raw_tracepoint prog 21 tp 'sys_enter' 8: cgroup prog 25 cgroup_id 584 attach_type egress 9: cgroup prog 25 cgroup_id 599 attach_type egress 10: cgroup prog 25 cgroup_id 614 attach_type egress 11: cgroup prog 25 cgroup_id 629 attach_type egress Signed-off-by: Andrii Nakryiko <andriin@fb.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Reviewed-by: Quentin Monnet <quentin@isovalent.com> Link: https://lore.kernel.org/bpf/20200429001614.1544-9-andriin@fb.com
This commit is contained in:
parent
50325b1761
commit
c5481f9a95
|
@ -262,6 +262,8 @@ int get_fd_type(int fd)
|
||||||
return BPF_OBJ_MAP;
|
return BPF_OBJ_MAP;
|
||||||
else if (strstr(buf, "bpf-prog"))
|
else if (strstr(buf, "bpf-prog"))
|
||||||
return BPF_OBJ_PROG;
|
return BPF_OBJ_PROG;
|
||||||
|
else if (strstr(buf, "bpf-link"))
|
||||||
|
return BPF_OBJ_LINK;
|
||||||
|
|
||||||
return BPF_OBJ_UNKNOWN;
|
return BPF_OBJ_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||||
|
/* Copyright (C) 2020 Facebook */
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <bpf/bpf.h>
|
||||||
|
|
||||||
|
#include "json_writer.h"
|
||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
static const char * const link_type_name[] = {
|
||||||
|
[BPF_LINK_TYPE_UNSPEC] = "unspec",
|
||||||
|
[BPF_LINK_TYPE_RAW_TRACEPOINT] = "raw_tracepoint",
|
||||||
|
[BPF_LINK_TYPE_TRACING] = "tracing",
|
||||||
|
[BPF_LINK_TYPE_CGROUP] = "cgroup",
|
||||||
|
};
|
||||||
|
|
||||||
|
static int link_parse_fd(int *argc, char ***argv)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
return bpf_link_get_fd_by_id(id);
|
||||||
|
} else if (is_prefix(**argv, "pinned")) {
|
||||||
|
char *path;
|
||||||
|
|
||||||
|
NEXT_ARGP();
|
||||||
|
|
||||||
|
path = **argv;
|
||||||
|
NEXT_ARGP();
|
||||||
|
|
||||||
|
return open_obj_pinned_any(path, BPF_OBJ_LINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
p_err("expected 'id' or 'pinned', got: '%s'?", **argv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_link_header_json(struct bpf_link_info *info, json_writer_t *wtr)
|
||||||
|
{
|
||||||
|
jsonw_uint_field(wtr, "id", info->id);
|
||||||
|
if (info->type < ARRAY_SIZE(link_type_name))
|
||||||
|
jsonw_string_field(wtr, "type", link_type_name[info->type]);
|
||||||
|
else
|
||||||
|
jsonw_uint_field(wtr, "type", info->type);
|
||||||
|
|
||||||
|
jsonw_uint_field(json_wtr, "prog_id", info->prog_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_prog_info(int prog_id, struct bpf_prog_info *info)
|
||||||
|
{
|
||||||
|
__u32 len = sizeof(*info);
|
||||||
|
int err, prog_fd;
|
||||||
|
|
||||||
|
prog_fd = bpf_prog_get_fd_by_id(prog_id);
|
||||||
|
if (prog_fd < 0)
|
||||||
|
return prog_fd;
|
||||||
|
|
||||||
|
memset(info, 0, sizeof(*info));
|
||||||
|
err = bpf_obj_get_info_by_fd(prog_fd, info, &len);
|
||||||
|
if (err)
|
||||||
|
p_err("can't get prog info: %s", strerror(errno));
|
||||||
|
close(prog_fd);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int show_link_close_json(int fd, struct bpf_link_info *info)
|
||||||
|
{
|
||||||
|
struct bpf_prog_info prog_info;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
jsonw_start_object(json_wtr);
|
||||||
|
|
||||||
|
show_link_header_json(info, json_wtr);
|
||||||
|
|
||||||
|
switch (info->type) {
|
||||||
|
case BPF_LINK_TYPE_RAW_TRACEPOINT:
|
||||||
|
jsonw_string_field(json_wtr, "tp_name",
|
||||||
|
(const char *)info->raw_tracepoint.tp_name);
|
||||||
|
break;
|
||||||
|
case BPF_LINK_TYPE_TRACING:
|
||||||
|
err = get_prog_info(info->prog_id, &prog_info);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (prog_info.type < ARRAY_SIZE(prog_type_name))
|
||||||
|
jsonw_string_field(json_wtr, "prog_type",
|
||||||
|
prog_type_name[prog_info.type]);
|
||||||
|
else
|
||||||
|
jsonw_uint_field(json_wtr, "prog_type",
|
||||||
|
prog_info.type);
|
||||||
|
|
||||||
|
if (info->tracing.attach_type < ARRAY_SIZE(attach_type_name))
|
||||||
|
jsonw_string_field(json_wtr, "attach_type",
|
||||||
|
attach_type_name[info->tracing.attach_type]);
|
||||||
|
else
|
||||||
|
jsonw_uint_field(json_wtr, "attach_type",
|
||||||
|
info->tracing.attach_type);
|
||||||
|
break;
|
||||||
|
case BPF_LINK_TYPE_CGROUP:
|
||||||
|
jsonw_lluint_field(json_wtr, "cgroup_id",
|
||||||
|
info->cgroup.cgroup_id);
|
||||||
|
if (info->cgroup.attach_type < ARRAY_SIZE(attach_type_name))
|
||||||
|
jsonw_string_field(json_wtr, "attach_type",
|
||||||
|
attach_type_name[info->cgroup.attach_type]);
|
||||||
|
else
|
||||||
|
jsonw_uint_field(json_wtr, "attach_type",
|
||||||
|
info->cgroup.attach_type);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hash_empty(link_table.table)) {
|
||||||
|
struct pinned_obj *obj;
|
||||||
|
|
||||||
|
jsonw_name(json_wtr, "pinned");
|
||||||
|
jsonw_start_array(json_wtr);
|
||||||
|
hash_for_each_possible(link_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);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_link_header_plain(struct bpf_link_info *info)
|
||||||
|
{
|
||||||
|
printf("%u: ", info->id);
|
||||||
|
if (info->type < ARRAY_SIZE(link_type_name))
|
||||||
|
printf("%s ", link_type_name[info->type]);
|
||||||
|
else
|
||||||
|
printf("type %u ", info->type);
|
||||||
|
|
||||||
|
printf("prog %u ", info->prog_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int show_link_close_plain(int fd, struct bpf_link_info *info)
|
||||||
|
{
|
||||||
|
struct bpf_prog_info prog_info;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
show_link_header_plain(info);
|
||||||
|
|
||||||
|
switch (info->type) {
|
||||||
|
case BPF_LINK_TYPE_RAW_TRACEPOINT:
|
||||||
|
printf("\n\ttp '%s' ",
|
||||||
|
(const char *)info->raw_tracepoint.tp_name);
|
||||||
|
break;
|
||||||
|
case BPF_LINK_TYPE_TRACING:
|
||||||
|
err = get_prog_info(info->prog_id, &prog_info);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (prog_info.type < ARRAY_SIZE(prog_type_name))
|
||||||
|
printf("\n\tprog_type %s ",
|
||||||
|
prog_type_name[prog_info.type]);
|
||||||
|
else
|
||||||
|
printf("\n\tprog_type %u ", prog_info.type);
|
||||||
|
|
||||||
|
if (info->tracing.attach_type < ARRAY_SIZE(attach_type_name))
|
||||||
|
printf("attach_type %s ",
|
||||||
|
attach_type_name[info->tracing.attach_type]);
|
||||||
|
else
|
||||||
|
printf("attach_type %u ", info->tracing.attach_type);
|
||||||
|
break;
|
||||||
|
case BPF_LINK_TYPE_CGROUP:
|
||||||
|
printf("\n\tcgroup_id %zu ", (size_t)info->cgroup.cgroup_id);
|
||||||
|
if (info->cgroup.attach_type < ARRAY_SIZE(attach_type_name))
|
||||||
|
printf("attach_type %s ",
|
||||||
|
attach_type_name[info->cgroup.attach_type]);
|
||||||
|
else
|
||||||
|
printf("attach_type %u ", info->cgroup.attach_type);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hash_empty(link_table.table)) {
|
||||||
|
struct pinned_obj *obj;
|
||||||
|
|
||||||
|
hash_for_each_possible(link_table.table, obj, hash, info->id) {
|
||||||
|
if (obj->id == info->id)
|
||||||
|
printf("\n\tpinned %s", obj->path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_show_link(int fd)
|
||||||
|
{
|
||||||
|
struct bpf_link_info info;
|
||||||
|
__u32 len = sizeof(info);
|
||||||
|
char raw_tp_name[256];
|
||||||
|
int err;
|
||||||
|
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
again:
|
||||||
|
err = bpf_obj_get_info_by_fd(fd, &info, &len);
|
||||||
|
if (err) {
|
||||||
|
p_err("can't get link info: %s",
|
||||||
|
strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (info.type == BPF_LINK_TYPE_RAW_TRACEPOINT &&
|
||||||
|
!info.raw_tracepoint.tp_name) {
|
||||||
|
info.raw_tracepoint.tp_name = (unsigned long)&raw_tp_name;
|
||||||
|
info.raw_tracepoint.tp_name_len = sizeof(raw_tp_name);
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_output)
|
||||||
|
show_link_close_json(fd, &info);
|
||||||
|
else
|
||||||
|
show_link_close_plain(fd, &info);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_show(int argc, char **argv)
|
||||||
|
{
|
||||||
|
__u32 id = 0;
|
||||||
|
int err, fd;
|
||||||
|
|
||||||
|
if (show_pinned)
|
||||||
|
build_pinned_obj_table(&link_table, BPF_OBJ_LINK);
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
fd = link_parse_fd(&argc, &argv);
|
||||||
|
if (fd < 0)
|
||||||
|
return fd;
|
||||||
|
return do_show_link(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc)
|
||||||
|
return BAD_ARG();
|
||||||
|
|
||||||
|
if (json_output)
|
||||||
|
jsonw_start_array(json_wtr);
|
||||||
|
while (true) {
|
||||||
|
err = bpf_link_get_next_id(id, &id);
|
||||||
|
if (err) {
|
||||||
|
if (errno == ENOENT)
|
||||||
|
break;
|
||||||
|
p_err("can't get next link: %s%s", strerror(errno),
|
||||||
|
errno == EINVAL ? " -- kernel too old?" : "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = bpf_link_get_fd_by_id(id);
|
||||||
|
if (fd < 0) {
|
||||||
|
if (errno == ENOENT)
|
||||||
|
continue;
|
||||||
|
p_err("can't get link by id (%u): %s",
|
||||||
|
id, strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = do_show_link(fd);
|
||||||
|
if (err)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (json_output)
|
||||||
|
jsonw_end_array(json_wtr);
|
||||||
|
|
||||||
|
return errno == ENOENT ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_pin(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = do_pin_any(argc, argv, link_parse_fd);
|
||||||
|
if (!err && json_output)
|
||||||
|
jsonw_null(json_wtr);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_help(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (json_output) {
|
||||||
|
jsonw_null(json_wtr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr,
|
||||||
|
"Usage: %1$s %2$s { show | list } [LINK]\n"
|
||||||
|
" %1$s %2$s pin LINK FILE\n"
|
||||||
|
" %1$s %2$s help\n"
|
||||||
|
"\n"
|
||||||
|
" " HELP_SPEC_LINK "\n"
|
||||||
|
" " HELP_SPEC_PROGRAM "\n"
|
||||||
|
" " HELP_SPEC_OPTIONS "\n"
|
||||||
|
"",
|
||||||
|
bin_name, argv[-2]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct cmd cmds[] = {
|
||||||
|
{ "show", do_show },
|
||||||
|
{ "list", do_show },
|
||||||
|
{ "help", do_help },
|
||||||
|
{ "pin", do_pin },
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
int do_link(int argc, char **argv)
|
||||||
|
{
|
||||||
|
return cmd_select(cmds, argc, argv, do_help);
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ bool verifier_logs;
|
||||||
bool relaxed_maps;
|
bool relaxed_maps;
|
||||||
struct pinned_obj_table prog_table;
|
struct pinned_obj_table prog_table;
|
||||||
struct pinned_obj_table map_table;
|
struct pinned_obj_table map_table;
|
||||||
|
struct pinned_obj_table link_table;
|
||||||
|
|
||||||
static void __noreturn clean_and_exit(int i)
|
static void __noreturn clean_and_exit(int i)
|
||||||
{
|
{
|
||||||
|
@ -58,7 +59,7 @@ static int do_help(int argc, char **argv)
|
||||||
" %s batch file FILE\n"
|
" %s batch file FILE\n"
|
||||||
" %s version\n"
|
" %s version\n"
|
||||||
"\n"
|
"\n"
|
||||||
" OBJECT := { prog | map | cgroup | perf | net | feature | btf | gen | struct_ops }\n"
|
" OBJECT := { prog | map | link | cgroup | perf | net | feature | btf | gen | struct_ops }\n"
|
||||||
" " HELP_SPEC_OPTIONS "\n"
|
" " HELP_SPEC_OPTIONS "\n"
|
||||||
"",
|
"",
|
||||||
bin_name, bin_name, bin_name);
|
bin_name, bin_name, bin_name);
|
||||||
|
@ -215,6 +216,7 @@ static const struct cmd cmds[] = {
|
||||||
{ "batch", do_batch },
|
{ "batch", do_batch },
|
||||||
{ "prog", do_prog },
|
{ "prog", do_prog },
|
||||||
{ "map", do_map },
|
{ "map", do_map },
|
||||||
|
{ "link", do_link },
|
||||||
{ "cgroup", do_cgroup },
|
{ "cgroup", do_cgroup },
|
||||||
{ "perf", do_perf },
|
{ "perf", do_perf },
|
||||||
{ "net", do_net },
|
{ "net", do_net },
|
||||||
|
@ -364,6 +366,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
hash_init(prog_table.table);
|
hash_init(prog_table.table);
|
||||||
hash_init(map_table.table);
|
hash_init(map_table.table);
|
||||||
|
hash_init(link_table.table);
|
||||||
|
|
||||||
opterr = 0;
|
opterr = 0;
|
||||||
while ((opt = getopt_long(argc, argv, "Vhpjfmnd",
|
while ((opt = getopt_long(argc, argv, "Vhpjfmnd",
|
||||||
|
@ -422,6 +425,7 @@ int main(int argc, char **argv)
|
||||||
if (show_pinned) {
|
if (show_pinned) {
|
||||||
delete_pinned_obj_table(&prog_table);
|
delete_pinned_obj_table(&prog_table);
|
||||||
delete_pinned_obj_table(&map_table);
|
delete_pinned_obj_table(&map_table);
|
||||||
|
delete_pinned_obj_table(&link_table);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
"\t {-m|--mapcompat} | {-n|--nomount} }"
|
"\t {-m|--mapcompat} | {-n|--nomount} }"
|
||||||
#define HELP_SPEC_MAP \
|
#define HELP_SPEC_MAP \
|
||||||
"MAP := { id MAP_ID | pinned FILE | name MAP_NAME }"
|
"MAP := { id MAP_ID | pinned FILE | name MAP_NAME }"
|
||||||
|
#define HELP_SPEC_LINK \
|
||||||
|
"LINK := { id LINK_ID | pinned FILE }"
|
||||||
|
|
||||||
static const char * const prog_type_name[] = {
|
static const char * const prog_type_name[] = {
|
||||||
[BPF_PROG_TYPE_UNSPEC] = "unspec",
|
[BPF_PROG_TYPE_UNSPEC] = "unspec",
|
||||||
|
@ -122,6 +124,7 @@ enum bpf_obj_type {
|
||||||
BPF_OBJ_UNKNOWN,
|
BPF_OBJ_UNKNOWN,
|
||||||
BPF_OBJ_PROG,
|
BPF_OBJ_PROG,
|
||||||
BPF_OBJ_MAP,
|
BPF_OBJ_MAP,
|
||||||
|
BPF_OBJ_LINK,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const char *bin_name;
|
extern const char *bin_name;
|
||||||
|
@ -134,6 +137,7 @@ extern bool verifier_logs;
|
||||||
extern bool relaxed_maps;
|
extern bool relaxed_maps;
|
||||||
extern struct pinned_obj_table prog_table;
|
extern struct pinned_obj_table prog_table;
|
||||||
extern struct pinned_obj_table map_table;
|
extern struct pinned_obj_table map_table;
|
||||||
|
extern struct pinned_obj_table link_table;
|
||||||
|
|
||||||
void __printf(1, 2) p_err(const char *fmt, ...);
|
void __printf(1, 2) p_err(const char *fmt, ...);
|
||||||
void __printf(1, 2) p_info(const char *fmt, ...);
|
void __printf(1, 2) p_info(const char *fmt, ...);
|
||||||
|
@ -185,6 +189,7 @@ int do_pin_fd(int fd, const char *name);
|
||||||
|
|
||||||
int do_prog(int argc, char **arg);
|
int do_prog(int argc, char **arg);
|
||||||
int do_map(int argc, char **arg);
|
int do_map(int argc, char **arg);
|
||||||
|
int do_link(int argc, char **arg);
|
||||||
int do_event_pipe(int argc, char **argv);
|
int do_event_pipe(int argc, char **argv);
|
||||||
int do_cgroup(int argc, char **arg);
|
int do_cgroup(int argc, char **arg);
|
||||||
int do_perf(int argc, char **arg);
|
int do_perf(int argc, char **arg);
|
||||||
|
|
Loading…
Reference in New Issue