Merge branch 'link_detach'

Andrii Nakryiko says:

====================
This patch set adds new BPF link operation, LINK_DETACH, allowing processes
with BPF link FD to force-detach it from respective BPF hook, similarly how
BPF link is auto-detached when such BPF hook (e.g., cgroup, net_device, netns,
etc) is removed. This facility allows admin to forcefully undo BPF link
attachment, while process that created BPF link in the first place is left
intact.

Once force-detached, BPF link stays valid in the kernel as long as there is at
least one FD open against it. It goes into defunct state, just like
auto-detached BPF link.

bpftool also got `link detach` command to allow triggering this in
non-programmatic fashion.

v1->v2:
- improve error reporting in `bpftool link detach` (Song).
====================

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2020-08-01 20:38:29 -07:00
commit 5a6b1a206d
20 changed files with 208 additions and 34 deletions

View File

@ -793,6 +793,7 @@ struct bpf_link {
struct bpf_link_ops {
void (*release)(struct bpf_link *link);
void (*dealloc)(struct bpf_link *link);
int (*detach)(struct bpf_link *link);
int (*update_prog)(struct bpf_link *link, struct bpf_prog *new_prog,
struct bpf_prog *old_prog);
void (*show_fdinfo)(const struct bpf_link *link, struct seq_file *seq);

View File

@ -117,6 +117,7 @@ enum bpf_cmd {
BPF_LINK_GET_NEXT_ID,
BPF_ENABLE_STATS,
BPF_ITER_CREATE,
BPF_LINK_DETACH,
};
enum bpf_map_type {
@ -634,6 +635,10 @@ union bpf_attr {
__u32 old_prog_fd;
} link_update;
struct {
__u32 link_fd;
} link_detach;
struct { /* struct used by BPF_ENABLE_STATS command */
__u32 type;
} enable_stats;

View File

@ -814,6 +814,7 @@ static void bpf_cgroup_link_release(struct bpf_link *link)
{
struct bpf_cgroup_link *cg_link =
container_of(link, struct bpf_cgroup_link, link);
struct cgroup *cg;
/* link might have been auto-detached by dying cgroup already,
* in that case our work is done here
@ -832,8 +833,12 @@ static void bpf_cgroup_link_release(struct bpf_link *link)
WARN_ON(__cgroup_bpf_detach(cg_link->cgroup, NULL, cg_link,
cg_link->type));
cg = cg_link->cgroup;
cg_link->cgroup = NULL;
mutex_unlock(&cgroup_mutex);
cgroup_put(cg_link->cgroup);
cgroup_put(cg);
}
static void bpf_cgroup_link_dealloc(struct bpf_link *link)
@ -844,6 +849,13 @@ static void bpf_cgroup_link_dealloc(struct bpf_link *link)
kfree(cg_link);
}
static int bpf_cgroup_link_detach(struct bpf_link *link)
{
bpf_cgroup_link_release(link);
return 0;
}
static void bpf_cgroup_link_show_fdinfo(const struct bpf_link *link,
struct seq_file *seq)
{
@ -883,6 +895,7 @@ static int bpf_cgroup_link_fill_link_info(const struct bpf_link *link,
static const struct bpf_link_ops bpf_cgroup_link_lops = {
.release = bpf_cgroup_link_release,
.dealloc = bpf_cgroup_link_dealloc,
.detach = bpf_cgroup_link_detach,
.update_prog = cgroup_bpf_replace,
.show_fdinfo = bpf_cgroup_link_show_fdinfo,
.fill_link_info = bpf_cgroup_link_fill_link_info,

View File

@ -142,9 +142,16 @@ static void bpf_netns_link_release(struct bpf_link *link)
bpf_prog_array_free(old_array);
out_unlock:
net_link->net = NULL;
mutex_unlock(&netns_bpf_mutex);
}
static int bpf_netns_link_detach(struct bpf_link *link)
{
bpf_netns_link_release(link);
return 0;
}
static void bpf_netns_link_dealloc(struct bpf_link *link)
{
struct bpf_netns_link *net_link =
@ -228,6 +235,7 @@ static void bpf_netns_link_show_fdinfo(const struct bpf_link *link,
static const struct bpf_link_ops bpf_netns_link_ops = {
.release = bpf_netns_link_release,
.dealloc = bpf_netns_link_dealloc,
.detach = bpf_netns_link_detach,
.update_prog = bpf_netns_link_update_prog,
.fill_link_info = bpf_netns_link_fill_info,
.show_fdinfo = bpf_netns_link_show_fdinfo,

View File

@ -3991,6 +3991,29 @@ out_put_link:
return ret;
}
#define BPF_LINK_DETACH_LAST_FIELD link_detach.link_fd
static int link_detach(union bpf_attr *attr)
{
struct bpf_link *link;
int ret;
if (CHECK_ATTR(BPF_LINK_DETACH))
return -EINVAL;
link = bpf_link_get_from_fd(attr->link_detach.link_fd);
if (IS_ERR(link))
return PTR_ERR(link);
if (link->ops->detach)
ret = link->ops->detach(link);
else
ret = -EOPNOTSUPP;
bpf_link_put(link);
return ret;
}
static int bpf_link_inc_not_zero(struct bpf_link *link)
{
return atomic64_fetch_add_unless(&link->refcnt, 1, 0) ? 0 : -ENOENT;
@ -4240,6 +4263,9 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
case BPF_ITER_CREATE:
err = bpf_iter_create(&attr);
break;
case BPF_LINK_DETACH:
err = link_detach(&attr);
break;
default:
err = -EINVAL;
break;

View File

@ -8979,12 +8979,20 @@ static void bpf_xdp_link_release(struct bpf_link *link)
/* if racing with net_device's tear down, xdp_link->dev might be
* already NULL, in which case link was already auto-detached
*/
if (xdp_link->dev)
if (xdp_link->dev) {
WARN_ON(dev_xdp_detach_link(xdp_link->dev, NULL, xdp_link));
xdp_link->dev = NULL;
}
rtnl_unlock();
}
static int bpf_xdp_link_detach(struct bpf_link *link)
{
bpf_xdp_link_release(link);
return 0;
}
static void bpf_xdp_link_dealloc(struct bpf_link *link)
{
struct bpf_xdp_link *xdp_link = container_of(link, struct bpf_xdp_link, link);
@ -9066,6 +9074,7 @@ out_unlock:
static const struct bpf_link_ops bpf_xdp_link_lops = {
.release = bpf_xdp_link_release,
.dealloc = bpf_xdp_link_dealloc,
.detach = bpf_xdp_link_detach,
.show_fdinfo = bpf_xdp_link_show_fdinfo,
.fill_link_info = bpf_xdp_link_fill_link_info,
.update_prog = bpf_xdp_link_update,

View File

@ -21,6 +21,7 @@ LINK COMMANDS
| **bpftool** **link { show | list }** [*LINK*]
| **bpftool** **link pin** *LINK* *FILE*
| **bpftool** **link detach *LINK*
| **bpftool** **link help**
|
| *LINK* := { **id** *LINK_ID* | **pinned** *FILE* }
@ -49,6 +50,13 @@ DESCRIPTION
contain a dot character ('.'), which is reserved for future
extensions of *bpffs*.
**bpftool link detach** *LINK*
Force-detach link *LINK*. BPF link and its underlying BPF
program will stay valid, but they will be detached from the
respective BPF hook and BPF link will transition into
a defunct state until last open file descriptor for that
link is closed.
**bpftool link help**
Print short help message.

View File

@ -1122,7 +1122,7 @@ _bpftool()
;;
link)
case $command in
show|list|pin)
show|list|pin|detach)
case $prev in
id)
_bpftool_get_link_ids
@ -1139,7 +1139,7 @@ _bpftool()
COMPREPLY=( $( compgen -W "$LINK_TYPE" -- "$cur" ) )
return 0
;;
pin)
pin|detach)
if [[ $prev == "$command" ]]; then
COMPREPLY=( $( compgen -W "$LINK_TYPE" -- "$cur" ) )
else

View File

@ -22,6 +22,8 @@ static const char * const link_type_name[] = {
static int link_parse_fd(int *argc, char ***argv)
{
int fd;
if (is_prefix(**argv, "id")) {
unsigned int id;
char *endptr;
@ -35,7 +37,10 @@ static int link_parse_fd(int *argc, char ***argv)
}
NEXT_ARGP();
return bpf_link_get_fd_by_id(id);
fd = bpf_link_get_fd_by_id(id);
if (fd < 0)
p_err("failed to get link with ID %d: %s", id, strerror(errno));
return fd;
} else if (is_prefix(**argv, "pinned")) {
char *path;
@ -316,6 +321,34 @@ static int do_pin(int argc, char **argv)
return err;
}
static int do_detach(int argc, char **argv)
{
int err, fd;
if (argc != 2) {
p_err("link specifier is invalid or missing\n");
return 1;
}
fd = link_parse_fd(&argc, &argv);
if (fd < 0)
return 1;
err = bpf_link_detach(fd);
if (err)
err = -errno;
close(fd);
if (err) {
p_err("failed link detach: %s", strerror(-err));
return 1;
}
if (json_output)
jsonw_null(json_wtr);
return 0;
}
static int do_help(int argc, char **argv)
{
if (json_output) {
@ -326,6 +359,7 @@ static int do_help(int argc, char **argv)
fprintf(stderr,
"Usage: %1$s %2$s { show | list } [LINK]\n"
" %1$s %2$s pin LINK FILE\n"
" %1$s %2$s detach LINK\n"
" %1$s %2$s help\n"
"\n"
" " HELP_SPEC_LINK "\n"
@ -341,6 +375,7 @@ static const struct cmd cmds[] = {
{ "list", do_show },
{ "help", do_help },
{ "pin", do_pin },
{ "detach", do_detach },
{ 0 }
};

View File

@ -117,6 +117,7 @@ enum bpf_cmd {
BPF_LINK_GET_NEXT_ID,
BPF_ENABLE_STATS,
BPF_ITER_CREATE,
BPF_LINK_DETACH,
};
enum bpf_map_type {
@ -634,6 +635,10 @@ union bpf_attr {
__u32 old_prog_fd;
} link_update;
struct {
__u32 link_fd;
} link_detach;
struct { /* struct used by BPF_ENABLE_STATS command */
__u32 type;
} enable_stats;

View File

@ -603,6 +603,16 @@ int bpf_link_create(int prog_fd, int target_fd,
return sys_bpf(BPF_LINK_CREATE, &attr, sizeof(attr));
}
int bpf_link_detach(int link_fd)
{
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.link_detach.link_fd = link_fd;
return sys_bpf(BPF_LINK_DETACH, &attr, sizeof(attr));
}
int bpf_link_update(int link_fd, int new_prog_fd,
const struct bpf_link_update_opts *opts)
{

View File

@ -178,6 +178,8 @@ LIBBPF_API int bpf_link_create(int prog_fd, int target_fd,
enum bpf_attach_type attach_type,
const struct bpf_link_create_opts *opts);
LIBBPF_API int bpf_link_detach(int link_fd);
struct bpf_link_update_opts {
size_t sz; /* size of this struct for forward/backward compatibility */
__u32 flags; /* extra flags */

View File

@ -7748,6 +7748,11 @@ struct bpf_link *bpf_link__open(const char *path)
return link;
}
int bpf_link__detach(struct bpf_link *link)
{
return bpf_link_detach(link->fd) ? -errno : 0;
}
int bpf_link__pin(struct bpf_link *link, const char *path)
{
int err;

View File

@ -229,6 +229,7 @@ LIBBPF_API int bpf_link__unpin(struct bpf_link *link);
LIBBPF_API int bpf_link__update_program(struct bpf_link *link,
struct bpf_program *prog);
LIBBPF_API void bpf_link__disconnect(struct bpf_link *link);
LIBBPF_API int bpf_link__detach(struct bpf_link *link);
LIBBPF_API int bpf_link__destroy(struct bpf_link *link);
LIBBPF_API struct bpf_link *

View File

@ -273,6 +273,8 @@ LIBBPF_0.0.9 {
LIBBPF_0.1.0 {
global:
bpf_link__detach;
bpf_link_detach;
bpf_map__ifindex;
bpf_map__key_size;
bpf_map__map_flags;

View File

@ -2,6 +2,7 @@
#include <test_progs.h>
#include "cgroup_helpers.h"
#include "testing_helpers.h"
#include "test_cgroup_link.skel.h"
static __u32 duration = 0;
@ -37,7 +38,8 @@ void test_cgroup_link(void)
int last_cg = ARRAY_SIZE(cgs) - 1, cg_nr = ARRAY_SIZE(cgs);
DECLARE_LIBBPF_OPTS(bpf_link_update_opts, link_upd_opts);
struct bpf_link *links[ARRAY_SIZE(cgs)] = {}, *tmp_link;
__u32 prog_ids[ARRAY_SIZE(cgs)], prog_cnt = 0, attach_flags;
__u32 prog_ids[ARRAY_SIZE(cgs)], prog_cnt = 0, attach_flags, prog_id;
struct bpf_link_info info;
int i = 0, err, prog_fd;
bool detach_legacy = false;
@ -219,6 +221,22 @@ void test_cgroup_link(void)
/* BPF programs should still get called */
ping_and_check(0, cg_nr);
prog_id = link_info_prog_id(links[0], &info);
CHECK(prog_id == 0, "link_info", "failed\n");
CHECK(info.cgroup.cgroup_id == 0, "cgroup_id", "unexpected %llu\n", info.cgroup.cgroup_id);
err = bpf_link__detach(links[0]);
if (CHECK(err, "link_detach", "failed %d\n", err))
goto cleanup;
/* cgroup_id should be zero in link_info */
prog_id = link_info_prog_id(links[0], &info);
CHECK(prog_id == 0, "link_info", "failed\n");
CHECK(info.cgroup.cgroup_id != 0, "cgroup_id", "unexpected %llu\n", info.cgroup.cgroup_id);
/* First BPF program shouldn't be called anymore */
ping_and_check(0, cg_nr - 1);
/* leave cgroup and remove them, don't detach programs */
cleanup_cgroup_environment();

View File

@ -34,6 +34,7 @@
#include "bpf_util.h"
#include "cgroup_helpers.h"
#include "network_helpers.h"
#include "testing_helpers.h"
#include "test_sk_lookup.skel.h"
/* External (address, port) pairs the client sends packets to. */
@ -469,34 +470,10 @@ static int update_lookup_map(struct bpf_map *map, int index, int sock_fd)
return 0;
}
static __u32 link_info_prog_id(struct bpf_link *link)
{
struct bpf_link_info info = {};
__u32 info_len = sizeof(info);
int link_fd, err;
link_fd = bpf_link__fd(link);
if (CHECK(link_fd < 0, "bpf_link__fd", "failed\n")) {
errno = -link_fd;
log_err("bpf_link__fd failed");
return 0;
}
err = bpf_obj_get_info_by_fd(link_fd, &info, &info_len);
if (CHECK(err, "bpf_obj_get_info_by_fd", "failed\n")) {
log_err("bpf_obj_get_info_by_fd");
return 0;
}
if (CHECK(info_len != sizeof(info), "bpf_obj_get_info_by_fd",
"unexpected info len %u\n", info_len))
return 0;
return info.prog_id;
}
static void query_lookup_prog(struct test_sk_lookup *skel)
{
struct bpf_link *link[3] = {};
struct bpf_link_info info;
__u32 attach_flags = 0;
__u32 prog_ids[3] = {};
__u32 prog_cnt = 3;
@ -534,18 +511,36 @@ static void query_lookup_prog(struct test_sk_lookup *skel)
if (CHECK(prog_cnt != 3, "bpf_prog_query",
"wrong program count on query: %u", prog_cnt))
goto detach;
prog_id = link_info_prog_id(link[0]);
prog_id = link_info_prog_id(link[0], &info);
CHECK(prog_ids[0] != prog_id, "bpf_prog_query",
"invalid program #0 id on query: %u != %u\n",
prog_ids[0], prog_id);
prog_id = link_info_prog_id(link[1]);
CHECK(info.netns.netns_ino == 0, "netns_ino",
"unexpected netns_ino: %u\n", info.netns.netns_ino);
prog_id = link_info_prog_id(link[1], &info);
CHECK(prog_ids[1] != prog_id, "bpf_prog_query",
"invalid program #1 id on query: %u != %u\n",
prog_ids[1], prog_id);
prog_id = link_info_prog_id(link[2]);
CHECK(info.netns.netns_ino == 0, "netns_ino",
"unexpected netns_ino: %u\n", info.netns.netns_ino);
prog_id = link_info_prog_id(link[2], &info);
CHECK(prog_ids[2] != prog_id, "bpf_prog_query",
"invalid program #2 id on query: %u != %u\n",
prog_ids[2], prog_id);
CHECK(info.netns.netns_ino == 0, "netns_ino",
"unexpected netns_ino: %u\n", info.netns.netns_ino);
err = bpf_link__detach(link[0]);
if (CHECK(err, "link_detach", "failed %d\n", err))
goto detach;
/* prog id is still there, but netns_ino is zeroed out */
prog_id = link_info_prog_id(link[0], &info);
CHECK(prog_ids[0] != prog_id, "bpf_prog_query",
"invalid program #0 id on query: %u != %u\n",
prog_ids[0], prog_id);
CHECK(info.netns.netns_ino != 0, "netns_ino",
"unexpected netns_ino: %u\n", info.netns.netns_ino);
detach:
if (link[2])

View File

@ -131,6 +131,20 @@ void test_xdp_link(void)
CHECK(link_info.xdp.ifindex != IFINDEX_LO, "link_ifindex",
"got %u != exp %u\n", link_info.xdp.ifindex, IFINDEX_LO);
err = bpf_link__detach(link);
if (CHECK(err, "link_detach", "failed %d\n", err))
goto cleanup;
memset(&link_info, 0, sizeof(link_info));
err = bpf_obj_get_info_by_fd(bpf_link__fd(link), &link_info, &link_info_len);
if (CHECK(err, "link_info", "failed: %d\n", err))
goto cleanup;
CHECK(link_info.prog_id != id1, "link_prog_id",
"got %u != exp %u\n", link_info.prog_id, id1);
/* ifindex should be zeroed out */
CHECK(link_info.xdp.ifindex != 0, "link_ifindex",
"got %u != exp %u\n", link_info.xdp.ifindex, 0);
cleanup:
test_xdp_link__destroy(skel1);
test_xdp_link__destroy(skel2);

View File

@ -64,3 +64,17 @@ int parse_num_list(const char *s, bool **num_set, int *num_set_len)
return 0;
}
__u32 link_info_prog_id(const struct bpf_link *link, struct bpf_link_info *info)
{
__u32 info_len = sizeof(*info);
int err;
memset(info, 0, sizeof(*info));
err = bpf_obj_get_info_by_fd(bpf_link__fd(link), info, &info_len);
if (err) {
printf("failed to get link info: %d\n", -errno);
return 0;
}
return info->prog_id;
}

View File

@ -1,5 +1,8 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (C) 2020 Facebook, Inc. */
#include <stdbool.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
int parse_num_list(const char *s, bool **set, int *set_len);
__u32 link_info_prog_id(const struct bpf_link *link, struct bpf_link_info *info);