Merge branch 'Link-based-attach-to-netns'
Jakub Sitnicki says: ==================== One of the pieces of feedback from recent review of BPF hooks for socket lookup [0] was that new program types should use bpf_link-based attachment. This series introduces new bpf_link type for attaching to network namespace. All link operations are supported. Errors returned from ops follow cgroup example. Patch 4 description goes into error semantics. The major change in v2 is a switch away from RCU to mutex-only synchronization. Andrii pointed out that it is not needed, and it makes sense to keep locking straightforward. Also, there were a couple of bugs in update_prog and fill_info initial implementation, one picked up by kbuild. Those are now fixed. Tests have been extended to cover them. Full changelog below. Series is organized as so: Patches 1-3 prepare a space in struct net to keep state for attached BPF programs, and massage the code in flow_dissector to make it attach type agnostic, to finally move it under kernel/bpf/. Patch 4, the most important one, introduces new bpf_link link type for attaching to network namespace. Patch 5 unifies the update error (ENOLINK) between BPF cgroup and netns. Patches 6-8 make libbpf and bpftool aware of the new link type. Patches 9-12 Add and extend tests to check that link low- and high-level API for operating on links to netns works as intended. Thanks to Alexei, Andrii, Lorenz, Marek, and Stanislav for feedback. -jkbs [0] https://lore.kernel.org/bpf/20200511185218.1422406-1-jakub@cloudflare.com/ Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com> Cc: Andrii Nakryiko <andrii.nakryiko@gmail.com> Cc: Lorenz Bauer <lmb@cloudflare.com> Cc: Marek Majkowski <marek@cloudflare.com> Cc: Stanislav Fomichev <sdf@google.com> v1 -> v2: - Switch to mutex-only synchronization. Don't rely on RCU grace period guarantee when accessing struct net from link release / update / fill_info, and when accessing bpf_link from pernet pre_exit callback. (Andrii) - Drop patch 1, no longer needed with mutex-only synchronization. - Don't leak uninitialized variable contents from fill_info callback when link is in defunct state. (kbuild) - Make fill_info treat the link as defunct (i.e. no attached netns) when struct net refcount is 0, but link has not been yet auto-detached. - Add missing BPF_LINK_TYPE define in bpf_types.h for new link type. - Fix link update_prog callback to update the prog that will run, and not just the link itself. - Return EEXIST on prog attach when link already exists, and on link create when prog is already attached directly. (Andrii) - Return EINVAL on prog detach when link is attached. (Andrii) - Fold __netns_bpf_link_attach into its only caller. (Stanislav) - Get rid of a wrapper around container_of() (Andrii) - Use rcu_dereference_protected instead of rcu_access_pointer on update-side. (Stanislav) - Make return-on-success from netns_bpf_link_create less confusing. (Andrii) - Adapt bpf_link for cgroup to return ENOLINK when updating a defunct link. (Andrii, Alexei) - Order new exported symbols in libbpf.map alphabetically (Andrii) - Keep libbpf's "failed to attach link" warning message clear as to what we failed to attach to (cgroup vs netns). (Andrii) - Extract helpers for printing link attach type. (bpftool, Andrii) - Switch flow_dissector tests to BPF skeleton and extend them to exercise link-based flow dissector attachment. (Andrii) - Harden flow dissector attachment tests with prog query checks after prog attach/detach, or link create/update/close. - Extend flow dissector tests to cover fill_info for defunct links. - Rebase onto recent bpf-next ==================== Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
cf51abcded
64
include/linux/bpf-netns.h
Normal file
64
include/linux/bpf-netns.h
Normal file
@ -0,0 +1,64 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _BPF_NETNS_H
|
||||
#define _BPF_NETNS_H
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <uapi/linux/bpf.h>
|
||||
|
||||
enum netns_bpf_attach_type {
|
||||
NETNS_BPF_INVALID = -1,
|
||||
NETNS_BPF_FLOW_DISSECTOR = 0,
|
||||
MAX_NETNS_BPF_ATTACH_TYPE
|
||||
};
|
||||
|
||||
static inline enum netns_bpf_attach_type
|
||||
to_netns_bpf_attach_type(enum bpf_attach_type attach_type)
|
||||
{
|
||||
switch (attach_type) {
|
||||
case BPF_FLOW_DISSECTOR:
|
||||
return NETNS_BPF_FLOW_DISSECTOR;
|
||||
default:
|
||||
return NETNS_BPF_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
/* Protects updates to netns_bpf */
|
||||
extern struct mutex netns_bpf_mutex;
|
||||
|
||||
union bpf_attr;
|
||||
struct bpf_prog;
|
||||
|
||||
#ifdef CONFIG_NET
|
||||
int netns_bpf_prog_query(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr);
|
||||
int netns_bpf_prog_attach(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog);
|
||||
int netns_bpf_prog_detach(const union bpf_attr *attr);
|
||||
int netns_bpf_link_create(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog);
|
||||
#else
|
||||
static inline int netns_bpf_prog_query(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int netns_bpf_prog_attach(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int netns_bpf_prog_detach(const union bpf_attr *attr)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int netns_bpf_link_create(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _BPF_NETNS_H */
|
@ -126,3 +126,6 @@ BPF_LINK_TYPE(BPF_LINK_TYPE_TRACING, tracing)
|
||||
BPF_LINK_TYPE(BPF_LINK_TYPE_CGROUP, cgroup)
|
||||
#endif
|
||||
BPF_LINK_TYPE(BPF_LINK_TYPE_ITER, iter)
|
||||
#ifdef CONFIG_NET
|
||||
BPF_LINK_TYPE(BPF_LINK_TYPE_NETNS, netns)
|
||||
#endif
|
||||
|
@ -1283,32 +1283,6 @@ void skb_flow_dissector_init(struct flow_dissector *flow_dissector,
|
||||
const struct flow_dissector_key *key,
|
||||
unsigned int key_count);
|
||||
|
||||
#ifdef CONFIG_NET
|
||||
int skb_flow_dissector_prog_query(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr);
|
||||
int skb_flow_dissector_bpf_prog_attach(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog);
|
||||
|
||||
int skb_flow_dissector_bpf_prog_detach(const union bpf_attr *attr);
|
||||
#else
|
||||
static inline int skb_flow_dissector_prog_query(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int skb_flow_dissector_bpf_prog_attach(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int skb_flow_dissector_bpf_prog_detach(const union bpf_attr *attr)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct bpf_flow_dissector;
|
||||
bool bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx,
|
||||
__be16 proto, int nhoff, int hlen, unsigned int flags);
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <linux/string.h>
|
||||
#include <uapi/linux/if_ether.h>
|
||||
|
||||
struct bpf_prog;
|
||||
struct net;
|
||||
struct sk_buff;
|
||||
|
||||
/**
|
||||
@ -369,4 +371,8 @@ flow_dissector_init_keys(struct flow_dissector_key_control *key_control,
|
||||
memset(key_basic, 0, sizeof(*key_basic));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
int flow_dissector_bpf_prog_attach(struct net *net, struct bpf_prog *prog);
|
||||
#endif /* CONFIG_BPF_SYSCALL */
|
||||
|
||||
#endif
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <net/netns/mpls.h>
|
||||
#include <net/netns/can.h>
|
||||
#include <net/netns/xdp.h>
|
||||
#include <net/netns/bpf.h>
|
||||
#include <linux/ns_common.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/skbuff.h>
|
||||
@ -162,7 +163,8 @@ struct net {
|
||||
#endif
|
||||
struct net_generic __rcu *gen;
|
||||
|
||||
struct bpf_prog __rcu *flow_dissector_prog;
|
||||
/* Used to store attached BPF programs */
|
||||
struct netns_bpf bpf;
|
||||
|
||||
/* Note : following structs are cache line aligned */
|
||||
#ifdef CONFIG_XFRM
|
||||
|
18
include/net/netns/bpf.h
Normal file
18
include/net/netns/bpf.h
Normal file
@ -0,0 +1,18 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* BPF programs attached to network namespace
|
||||
*/
|
||||
|
||||
#ifndef __NETNS_BPF_H__
|
||||
#define __NETNS_BPF_H__
|
||||
|
||||
#include <linux/bpf-netns.h>
|
||||
|
||||
struct bpf_prog;
|
||||
|
||||
struct netns_bpf {
|
||||
struct bpf_prog __rcu *progs[MAX_NETNS_BPF_ATTACH_TYPE];
|
||||
struct bpf_link *links[MAX_NETNS_BPF_ATTACH_TYPE];
|
||||
};
|
||||
|
||||
#endif /* __NETNS_BPF_H__ */
|
@ -237,6 +237,7 @@ enum bpf_link_type {
|
||||
BPF_LINK_TYPE_TRACING = 2,
|
||||
BPF_LINK_TYPE_CGROUP = 3,
|
||||
BPF_LINK_TYPE_ITER = 4,
|
||||
BPF_LINK_TYPE_NETNS = 5,
|
||||
|
||||
MAX_BPF_LINK_TYPE,
|
||||
};
|
||||
@ -3839,6 +3840,10 @@ struct bpf_link_info {
|
||||
__u64 cgroup_id;
|
||||
__u32 attach_type;
|
||||
} cgroup;
|
||||
struct {
|
||||
__u32 netns_ino;
|
||||
__u32 attach_type;
|
||||
} netns;
|
||||
};
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
|
@ -13,6 +13,7 @@ ifeq ($(CONFIG_NET),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += devmap.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += cpumap.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += offload.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += net_namespace.o
|
||||
endif
|
||||
ifeq ($(CONFIG_PERF_EVENTS),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += stackmap.o
|
||||
|
@ -595,7 +595,7 @@ static int cgroup_bpf_replace(struct bpf_link *link, struct bpf_prog *new_prog,
|
||||
mutex_lock(&cgroup_mutex);
|
||||
/* link might have been auto-released by dying cgroup, so fail */
|
||||
if (!cg_link->cgroup) {
|
||||
ret = -EINVAL;
|
||||
ret = -ENOLINK;
|
||||
goto out_unlock;
|
||||
}
|
||||
if (old_prog && link->prog != old_prog) {
|
||||
|
373
kernel/bpf/net_namespace.c
Normal file
373
kernel/bpf/net_namespace.c
Normal file
@ -0,0 +1,373 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/filter.h>
|
||||
#include <net/net_namespace.h>
|
||||
|
||||
/*
|
||||
* Functions to manage BPF programs attached to netns
|
||||
*/
|
||||
|
||||
struct bpf_netns_link {
|
||||
struct bpf_link link;
|
||||
enum bpf_attach_type type;
|
||||
enum netns_bpf_attach_type netns_type;
|
||||
|
||||
/* We don't hold a ref to net in order to auto-detach the link
|
||||
* when netns is going away. Instead we rely on pernet
|
||||
* pre_exit callback to clear this pointer. Must be accessed
|
||||
* with netns_bpf_mutex held.
|
||||
*/
|
||||
struct net *net;
|
||||
};
|
||||
|
||||
/* Protects updates to netns_bpf */
|
||||
DEFINE_MUTEX(netns_bpf_mutex);
|
||||
|
||||
/* Must be called with netns_bpf_mutex held. */
|
||||
static void __net_exit bpf_netns_link_auto_detach(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_netns_link *net_link =
|
||||
container_of(link, struct bpf_netns_link, link);
|
||||
|
||||
net_link->net = NULL;
|
||||
}
|
||||
|
||||
static void bpf_netns_link_release(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_netns_link *net_link =
|
||||
container_of(link, struct bpf_netns_link, link);
|
||||
enum netns_bpf_attach_type type = net_link->netns_type;
|
||||
struct net *net;
|
||||
|
||||
/* Link auto-detached by dying netns. */
|
||||
if (!net_link->net)
|
||||
return;
|
||||
|
||||
mutex_lock(&netns_bpf_mutex);
|
||||
|
||||
/* Recheck after potential sleep. We can race with cleanup_net
|
||||
* here, but if we see a non-NULL struct net pointer pre_exit
|
||||
* has not happened yet and will block on netns_bpf_mutex.
|
||||
*/
|
||||
net = net_link->net;
|
||||
if (!net)
|
||||
goto out_unlock;
|
||||
|
||||
net->bpf.links[type] = NULL;
|
||||
RCU_INIT_POINTER(net->bpf.progs[type], NULL);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&netns_bpf_mutex);
|
||||
}
|
||||
|
||||
static void bpf_netns_link_dealloc(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_netns_link *net_link =
|
||||
container_of(link, struct bpf_netns_link, link);
|
||||
|
||||
kfree(net_link);
|
||||
}
|
||||
|
||||
static int bpf_netns_link_update_prog(struct bpf_link *link,
|
||||
struct bpf_prog *new_prog,
|
||||
struct bpf_prog *old_prog)
|
||||
{
|
||||
struct bpf_netns_link *net_link =
|
||||
container_of(link, struct bpf_netns_link, link);
|
||||
enum netns_bpf_attach_type type = net_link->netns_type;
|
||||
struct net *net;
|
||||
int ret = 0;
|
||||
|
||||
if (old_prog && old_prog != link->prog)
|
||||
return -EPERM;
|
||||
if (new_prog->type != link->prog->type)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&netns_bpf_mutex);
|
||||
|
||||
net = net_link->net;
|
||||
if (!net || !check_net(net)) {
|
||||
/* Link auto-detached or netns dying */
|
||||
ret = -ENOLINK;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
old_prog = xchg(&link->prog, new_prog);
|
||||
rcu_assign_pointer(net->bpf.progs[type], new_prog);
|
||||
bpf_prog_put(old_prog);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&netns_bpf_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bpf_netns_link_fill_info(const struct bpf_link *link,
|
||||
struct bpf_link_info *info)
|
||||
{
|
||||
const struct bpf_netns_link *net_link =
|
||||
container_of(link, struct bpf_netns_link, link);
|
||||
unsigned int inum = 0;
|
||||
struct net *net;
|
||||
|
||||
mutex_lock(&netns_bpf_mutex);
|
||||
net = net_link->net;
|
||||
if (net && check_net(net))
|
||||
inum = net->ns.inum;
|
||||
mutex_unlock(&netns_bpf_mutex);
|
||||
|
||||
info->netns.netns_ino = inum;
|
||||
info->netns.attach_type = net_link->type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bpf_netns_link_show_fdinfo(const struct bpf_link *link,
|
||||
struct seq_file *seq)
|
||||
{
|
||||
struct bpf_link_info info = {};
|
||||
|
||||
bpf_netns_link_fill_info(link, &info);
|
||||
seq_printf(seq,
|
||||
"netns_ino:\t%u\n"
|
||||
"attach_type:\t%u\n",
|
||||
info.netns.netns_ino,
|
||||
info.netns.attach_type);
|
||||
}
|
||||
|
||||
static const struct bpf_link_ops bpf_netns_link_ops = {
|
||||
.release = bpf_netns_link_release,
|
||||
.dealloc = bpf_netns_link_dealloc,
|
||||
.update_prog = bpf_netns_link_update_prog,
|
||||
.fill_link_info = bpf_netns_link_fill_info,
|
||||
.show_fdinfo = bpf_netns_link_show_fdinfo,
|
||||
};
|
||||
|
||||
int netns_bpf_prog_query(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
__u32 __user *prog_ids = u64_to_user_ptr(attr->query.prog_ids);
|
||||
u32 prog_id, prog_cnt = 0, flags = 0;
|
||||
enum netns_bpf_attach_type type;
|
||||
struct bpf_prog *attached;
|
||||
struct net *net;
|
||||
|
||||
if (attr->query.query_flags)
|
||||
return -EINVAL;
|
||||
|
||||
type = to_netns_bpf_attach_type(attr->query.attach_type);
|
||||
if (type < 0)
|
||||
return -EINVAL;
|
||||
|
||||
net = get_net_ns_by_fd(attr->query.target_fd);
|
||||
if (IS_ERR(net))
|
||||
return PTR_ERR(net);
|
||||
|
||||
rcu_read_lock();
|
||||
attached = rcu_dereference(net->bpf.progs[type]);
|
||||
if (attached) {
|
||||
prog_cnt = 1;
|
||||
prog_id = attached->aux->id;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
put_net(net);
|
||||
|
||||
if (copy_to_user(&uattr->query.attach_flags, &flags, sizeof(flags)))
|
||||
return -EFAULT;
|
||||
if (copy_to_user(&uattr->query.prog_cnt, &prog_cnt, sizeof(prog_cnt)))
|
||||
return -EFAULT;
|
||||
|
||||
if (!attr->query.prog_cnt || !prog_ids || !prog_cnt)
|
||||
return 0;
|
||||
|
||||
if (copy_to_user(prog_ids, &prog_id, sizeof(u32)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int netns_bpf_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
|
||||
{
|
||||
enum netns_bpf_attach_type type;
|
||||
struct net *net;
|
||||
int ret;
|
||||
|
||||
type = to_netns_bpf_attach_type(attr->attach_type);
|
||||
if (type < 0)
|
||||
return -EINVAL;
|
||||
|
||||
net = current->nsproxy->net_ns;
|
||||
mutex_lock(&netns_bpf_mutex);
|
||||
|
||||
/* Attaching prog directly is not compatible with links */
|
||||
if (net->bpf.links[type]) {
|
||||
ret = -EEXIST;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case NETNS_BPF_FLOW_DISSECTOR:
|
||||
ret = flow_dissector_bpf_prog_attach(net, prog);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
out_unlock:
|
||||
mutex_unlock(&netns_bpf_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Must be called with netns_bpf_mutex held. */
|
||||
static int __netns_bpf_prog_detach(struct net *net,
|
||||
enum netns_bpf_attach_type type)
|
||||
{
|
||||
struct bpf_prog *attached;
|
||||
|
||||
/* Progs attached via links cannot be detached */
|
||||
if (net->bpf.links[type])
|
||||
return -EINVAL;
|
||||
|
||||
attached = rcu_dereference_protected(net->bpf.progs[type],
|
||||
lockdep_is_held(&netns_bpf_mutex));
|
||||
if (!attached)
|
||||
return -ENOENT;
|
||||
RCU_INIT_POINTER(net->bpf.progs[type], NULL);
|
||||
bpf_prog_put(attached);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int netns_bpf_prog_detach(const union bpf_attr *attr)
|
||||
{
|
||||
enum netns_bpf_attach_type type;
|
||||
int ret;
|
||||
|
||||
type = to_netns_bpf_attach_type(attr->attach_type);
|
||||
if (type < 0)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&netns_bpf_mutex);
|
||||
ret = __netns_bpf_prog_detach(current->nsproxy->net_ns, type);
|
||||
mutex_unlock(&netns_bpf_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int netns_bpf_link_attach(struct net *net, struct bpf_link *link,
|
||||
enum netns_bpf_attach_type type)
|
||||
{
|
||||
struct bpf_prog *prog;
|
||||
int err;
|
||||
|
||||
mutex_lock(&netns_bpf_mutex);
|
||||
|
||||
/* Allow attaching only one prog or link for now */
|
||||
if (net->bpf.links[type]) {
|
||||
err = -E2BIG;
|
||||
goto out_unlock;
|
||||
}
|
||||
/* Links are not compatible with attaching prog directly */
|
||||
prog = rcu_dereference_protected(net->bpf.progs[type],
|
||||
lockdep_is_held(&netns_bpf_mutex));
|
||||
if (prog) {
|
||||
err = -EEXIST;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case NETNS_BPF_FLOW_DISSECTOR:
|
||||
err = flow_dissector_bpf_prog_attach(net, link->prog);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (err)
|
||||
goto out_unlock;
|
||||
|
||||
net->bpf.links[type] = link;
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&netns_bpf_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
int netns_bpf_link_create(const union bpf_attr *attr, struct bpf_prog *prog)
|
||||
{
|
||||
enum netns_bpf_attach_type netns_type;
|
||||
struct bpf_link_primer link_primer;
|
||||
struct bpf_netns_link *net_link;
|
||||
enum bpf_attach_type type;
|
||||
struct net *net;
|
||||
int err;
|
||||
|
||||
if (attr->link_create.flags)
|
||||
return -EINVAL;
|
||||
|
||||
type = attr->link_create.attach_type;
|
||||
netns_type = to_netns_bpf_attach_type(type);
|
||||
if (netns_type < 0)
|
||||
return -EINVAL;
|
||||
|
||||
net = get_net_ns_by_fd(attr->link_create.target_fd);
|
||||
if (IS_ERR(net))
|
||||
return PTR_ERR(net);
|
||||
|
||||
net_link = kzalloc(sizeof(*net_link), GFP_USER);
|
||||
if (!net_link) {
|
||||
err = -ENOMEM;
|
||||
goto out_put_net;
|
||||
}
|
||||
bpf_link_init(&net_link->link, BPF_LINK_TYPE_NETNS,
|
||||
&bpf_netns_link_ops, prog);
|
||||
net_link->net = net;
|
||||
net_link->type = type;
|
||||
net_link->netns_type = netns_type;
|
||||
|
||||
err = bpf_link_prime(&net_link->link, &link_primer);
|
||||
if (err) {
|
||||
kfree(net_link);
|
||||
goto out_put_net;
|
||||
}
|
||||
|
||||
err = netns_bpf_link_attach(net, &net_link->link, netns_type);
|
||||
if (err) {
|
||||
bpf_link_cleanup(&link_primer);
|
||||
goto out_put_net;
|
||||
}
|
||||
|
||||
put_net(net);
|
||||
return bpf_link_settle(&link_primer);
|
||||
|
||||
out_put_net:
|
||||
put_net(net);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __net_exit netns_bpf_pernet_pre_exit(struct net *net)
|
||||
{
|
||||
enum netns_bpf_attach_type type;
|
||||
struct bpf_link *link;
|
||||
|
||||
mutex_lock(&netns_bpf_mutex);
|
||||
for (type = 0; type < MAX_NETNS_BPF_ATTACH_TYPE; type++) {
|
||||
link = net->bpf.links[type];
|
||||
if (link)
|
||||
bpf_netns_link_auto_detach(link);
|
||||
else
|
||||
__netns_bpf_prog_detach(net, type);
|
||||
}
|
||||
mutex_unlock(&netns_bpf_mutex);
|
||||
}
|
||||
|
||||
static struct pernet_operations netns_bpf_pernet_ops __net_initdata = {
|
||||
.pre_exit = netns_bpf_pernet_pre_exit,
|
||||
};
|
||||
|
||||
static int __init netns_bpf_init(void)
|
||||
{
|
||||
return register_pernet_subsys(&netns_bpf_pernet_ops);
|
||||
}
|
||||
|
||||
subsys_initcall(netns_bpf_init);
|
@ -27,6 +27,7 @@
|
||||
#include <uapi/linux/btf.h>
|
||||
#include <linux/bpf_lsm.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/bpf-netns.h>
|
||||
|
||||
#define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \
|
||||
@ -2868,7 +2869,7 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
||||
ret = lirc_prog_attach(attr, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_FLOW_DISSECTOR:
|
||||
ret = skb_flow_dissector_bpf_prog_attach(attr, prog);
|
||||
ret = netns_bpf_prog_attach(attr, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_CGROUP_DEVICE:
|
||||
case BPF_PROG_TYPE_CGROUP_SKB:
|
||||
@ -2908,7 +2909,7 @@ static int bpf_prog_detach(const union bpf_attr *attr)
|
||||
case BPF_PROG_TYPE_FLOW_DISSECTOR:
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
return skb_flow_dissector_bpf_prog_detach(attr);
|
||||
return netns_bpf_prog_detach(attr);
|
||||
case BPF_PROG_TYPE_CGROUP_DEVICE:
|
||||
case BPF_PROG_TYPE_CGROUP_SKB:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK:
|
||||
@ -2961,7 +2962,7 @@ static int bpf_prog_query(const union bpf_attr *attr,
|
||||
case BPF_LIRC_MODE2:
|
||||
return lirc_prog_query(attr, uattr);
|
||||
case BPF_FLOW_DISSECTOR:
|
||||
return skb_flow_dissector_prog_query(attr, uattr);
|
||||
return netns_bpf_prog_query(attr, uattr);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -3886,6 +3887,9 @@ static int link_create(union bpf_attr *attr)
|
||||
case BPF_PROG_TYPE_TRACING:
|
||||
ret = tracing_bpf_link_attach(attr, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_FLOW_DISSECTOR:
|
||||
ret = netns_bpf_link_create(attr, prog);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
@ -31,8 +31,7 @@
|
||||
#include <net/netfilter/nf_conntrack_core.h>
|
||||
#include <net/netfilter/nf_conntrack_labels.h>
|
||||
#endif
|
||||
|
||||
static DEFINE_MUTEX(flow_dissector_mutex);
|
||||
#include <linux/bpf-netns.h>
|
||||
|
||||
static void dissector_set_key(struct flow_dissector *flow_dissector,
|
||||
enum flow_dissector_key_id key_id)
|
||||
@ -70,54 +69,11 @@ void skb_flow_dissector_init(struct flow_dissector *flow_dissector,
|
||||
}
|
||||
EXPORT_SYMBOL(skb_flow_dissector_init);
|
||||
|
||||
int skb_flow_dissector_prog_query(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
int flow_dissector_bpf_prog_attach(struct net *net, struct bpf_prog *prog)
|
||||
{
|
||||
__u32 __user *prog_ids = u64_to_user_ptr(attr->query.prog_ids);
|
||||
u32 prog_id, prog_cnt = 0, flags = 0;
|
||||
enum netns_bpf_attach_type type = NETNS_BPF_FLOW_DISSECTOR;
|
||||
struct bpf_prog *attached;
|
||||
struct net *net;
|
||||
|
||||
if (attr->query.query_flags)
|
||||
return -EINVAL;
|
||||
|
||||
net = get_net_ns_by_fd(attr->query.target_fd);
|
||||
if (IS_ERR(net))
|
||||
return PTR_ERR(net);
|
||||
|
||||
rcu_read_lock();
|
||||
attached = rcu_dereference(net->flow_dissector_prog);
|
||||
if (attached) {
|
||||
prog_cnt = 1;
|
||||
prog_id = attached->aux->id;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
put_net(net);
|
||||
|
||||
if (copy_to_user(&uattr->query.attach_flags, &flags, sizeof(flags)))
|
||||
return -EFAULT;
|
||||
if (copy_to_user(&uattr->query.prog_cnt, &prog_cnt, sizeof(prog_cnt)))
|
||||
return -EFAULT;
|
||||
|
||||
if (!attr->query.prog_cnt || !prog_ids || !prog_cnt)
|
||||
return 0;
|
||||
|
||||
if (copy_to_user(prog_ids, &prog_id, sizeof(u32)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int skb_flow_dissector_bpf_prog_attach(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_prog *attached;
|
||||
struct net *net;
|
||||
int ret = 0;
|
||||
|
||||
net = current->nsproxy->net_ns;
|
||||
mutex_lock(&flow_dissector_mutex);
|
||||
|
||||
if (net == &init_net) {
|
||||
/* BPF flow dissector in the root namespace overrides
|
||||
@ -130,70 +86,29 @@ int skb_flow_dissector_bpf_prog_attach(const union bpf_attr *attr,
|
||||
for_each_net(ns) {
|
||||
if (ns == &init_net)
|
||||
continue;
|
||||
if (rcu_access_pointer(ns->flow_dissector_prog)) {
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
if (rcu_access_pointer(ns->bpf.progs[type]))
|
||||
return -EEXIST;
|
||||
}
|
||||
} else {
|
||||
/* Make sure root flow dissector is not attached
|
||||
* when attaching to the non-root namespace.
|
||||
*/
|
||||
if (rcu_access_pointer(init_net.flow_dissector_prog)) {
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
if (rcu_access_pointer(init_net.bpf.progs[type]))
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
attached = rcu_dereference_protected(net->flow_dissector_prog,
|
||||
lockdep_is_held(&flow_dissector_mutex));
|
||||
if (attached == prog) {
|
||||
attached = rcu_dereference_protected(net->bpf.progs[type],
|
||||
lockdep_is_held(&netns_bpf_mutex));
|
||||
if (attached == prog)
|
||||
/* The same program cannot be attached twice */
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
rcu_assign_pointer(net->flow_dissector_prog, prog);
|
||||
return -EINVAL;
|
||||
|
||||
rcu_assign_pointer(net->bpf.progs[type], prog);
|
||||
if (attached)
|
||||
bpf_prog_put(attached);
|
||||
out:
|
||||
mutex_unlock(&flow_dissector_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int flow_dissector_bpf_prog_detach(struct net *net)
|
||||
{
|
||||
struct bpf_prog *attached;
|
||||
|
||||
mutex_lock(&flow_dissector_mutex);
|
||||
attached = rcu_dereference_protected(net->flow_dissector_prog,
|
||||
lockdep_is_held(&flow_dissector_mutex));
|
||||
if (!attached) {
|
||||
mutex_unlock(&flow_dissector_mutex);
|
||||
return -ENOENT;
|
||||
}
|
||||
RCU_INIT_POINTER(net->flow_dissector_prog, NULL);
|
||||
bpf_prog_put(attached);
|
||||
mutex_unlock(&flow_dissector_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int skb_flow_dissector_bpf_prog_detach(const union bpf_attr *attr)
|
||||
{
|
||||
return flow_dissector_bpf_prog_detach(current->nsproxy->net_ns);
|
||||
}
|
||||
|
||||
static void __net_exit flow_dissector_pernet_pre_exit(struct net *net)
|
||||
{
|
||||
/* We're not racing with attach/detach because there are no
|
||||
* references to netns left when pre_exit gets called.
|
||||
*/
|
||||
if (rcu_access_pointer(net->flow_dissector_prog))
|
||||
flow_dissector_bpf_prog_detach(net);
|
||||
}
|
||||
|
||||
static struct pernet_operations flow_dissector_pernet_ops __net_initdata = {
|
||||
.pre_exit = flow_dissector_pernet_pre_exit,
|
||||
};
|
||||
#endif /* CONFIG_BPF_SYSCALL */
|
||||
|
||||
/**
|
||||
* __skb_flow_get_ports - extract the upper layer ports and return them
|
||||
@ -1044,11 +959,13 @@ bool __skb_flow_dissect(const struct net *net,
|
||||
|
||||
WARN_ON_ONCE(!net);
|
||||
if (net) {
|
||||
enum netns_bpf_attach_type type = NETNS_BPF_FLOW_DISSECTOR;
|
||||
|
||||
rcu_read_lock();
|
||||
attached = rcu_dereference(init_net.flow_dissector_prog);
|
||||
attached = rcu_dereference(init_net.bpf.progs[type]);
|
||||
|
||||
if (!attached)
|
||||
attached = rcu_dereference(net->flow_dissector_prog);
|
||||
attached = rcu_dereference(net->bpf.progs[type]);
|
||||
|
||||
if (attached) {
|
||||
struct bpf_flow_keys flow_keys;
|
||||
@ -1869,7 +1786,6 @@ static int __init init_default_flow_dissectors(void)
|
||||
skb_flow_dissector_init(&flow_keys_basic_dissector,
|
||||
flow_keys_basic_dissector_keys,
|
||||
ARRAY_SIZE(flow_keys_basic_dissector_keys));
|
||||
|
||||
return register_pernet_subsys(&flow_dissector_pernet_ops);
|
||||
return 0;
|
||||
}
|
||||
core_initcall(init_default_flow_dissectors);
|
||||
|
@ -17,6 +17,7 @@ static const char * const link_type_name[] = {
|
||||
[BPF_LINK_TYPE_TRACING] = "tracing",
|
||||
[BPF_LINK_TYPE_CGROUP] = "cgroup",
|
||||
[BPF_LINK_TYPE_ITER] = "iter",
|
||||
[BPF_LINK_TYPE_NETNS] = "netns",
|
||||
};
|
||||
|
||||
static int link_parse_fd(int *argc, char ***argv)
|
||||
@ -62,6 +63,15 @@ show_link_header_json(struct bpf_link_info *info, json_writer_t *wtr)
|
||||
jsonw_uint_field(json_wtr, "prog_id", info->prog_id);
|
||||
}
|
||||
|
||||
static void show_link_attach_type_json(__u32 attach_type, json_writer_t *wtr)
|
||||
{
|
||||
if (attach_type < ARRAY_SIZE(attach_type_name))
|
||||
jsonw_string_field(wtr, "attach_type",
|
||||
attach_type_name[attach_type]);
|
||||
else
|
||||
jsonw_uint_field(wtr, "attach_type", attach_type);
|
||||
}
|
||||
|
||||
static int get_prog_info(int prog_id, struct bpf_prog_info *info)
|
||||
{
|
||||
__u32 len = sizeof(*info);
|
||||
@ -105,22 +115,18 @@ static int show_link_close_json(int fd, struct bpf_link_info *info)
|
||||
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);
|
||||
show_link_attach_type_json(info->tracing.attach_type,
|
||||
json_wtr);
|
||||
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);
|
||||
show_link_attach_type_json(info->cgroup.attach_type, json_wtr);
|
||||
break;
|
||||
case BPF_LINK_TYPE_NETNS:
|
||||
jsonw_uint_field(json_wtr, "netns_ino",
|
||||
info->netns.netns_ino);
|
||||
show_link_attach_type_json(info->netns.attach_type, json_wtr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -153,6 +159,14 @@ static void show_link_header_plain(struct bpf_link_info *info)
|
||||
printf("prog %u ", info->prog_id);
|
||||
}
|
||||
|
||||
static void show_link_attach_type_plain(__u32 attach_type)
|
||||
{
|
||||
if (attach_type < ARRAY_SIZE(attach_type_name))
|
||||
printf("attach_type %s ", attach_type_name[attach_type]);
|
||||
else
|
||||
printf("attach_type %u ", attach_type);
|
||||
}
|
||||
|
||||
static int show_link_close_plain(int fd, struct bpf_link_info *info)
|
||||
{
|
||||
struct bpf_prog_info prog_info;
|
||||
@ -176,19 +190,15 @@ static int show_link_close_plain(int fd, struct bpf_link_info *info)
|
||||
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);
|
||||
show_link_attach_type_plain(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);
|
||||
show_link_attach_type_plain(info->cgroup.attach_type);
|
||||
break;
|
||||
case BPF_LINK_TYPE_NETNS:
|
||||
printf("\n\tnetns_ino %u ", info->netns.netns_ino);
|
||||
show_link_attach_type_plain(info->netns.attach_type);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -237,6 +237,7 @@ enum bpf_link_type {
|
||||
BPF_LINK_TYPE_TRACING = 2,
|
||||
BPF_LINK_TYPE_CGROUP = 3,
|
||||
BPF_LINK_TYPE_ITER = 4,
|
||||
BPF_LINK_TYPE_NETNS = 5,
|
||||
|
||||
MAX_BPF_LINK_TYPE,
|
||||
};
|
||||
@ -3839,6 +3840,10 @@ struct bpf_link_info {
|
||||
__u64 cgroup_id;
|
||||
__u32 attach_type;
|
||||
} cgroup;
|
||||
struct {
|
||||
__u32 netns_ino;
|
||||
__u32 attach_type;
|
||||
} netns;
|
||||
};
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
|
@ -7896,8 +7896,9 @@ static struct bpf_link *attach_iter(const struct bpf_sec_def *sec,
|
||||
return bpf_program__attach_iter(prog, NULL);
|
||||
}
|
||||
|
||||
struct bpf_link *
|
||||
bpf_program__attach_cgroup(struct bpf_program *prog, int cgroup_fd)
|
||||
static struct bpf_link *
|
||||
bpf_program__attach_fd(struct bpf_program *prog, int target_fd,
|
||||
const char *target_name)
|
||||
{
|
||||
enum bpf_attach_type attach_type;
|
||||
char errmsg[STRERR_BUFSIZE];
|
||||
@ -7917,12 +7918,12 @@ bpf_program__attach_cgroup(struct bpf_program *prog, int cgroup_fd)
|
||||
link->detach = &bpf_link__detach_fd;
|
||||
|
||||
attach_type = bpf_program__get_expected_attach_type(prog);
|
||||
link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, NULL);
|
||||
link_fd = bpf_link_create(prog_fd, target_fd, attach_type, NULL);
|
||||
if (link_fd < 0) {
|
||||
link_fd = -errno;
|
||||
free(link);
|
||||
pr_warn("program '%s': failed to attach to cgroup: %s\n",
|
||||
bpf_program__title(prog, false),
|
||||
pr_warn("program '%s': failed to attach to %s: %s\n",
|
||||
bpf_program__title(prog, false), target_name,
|
||||
libbpf_strerror_r(link_fd, errmsg, sizeof(errmsg)));
|
||||
return ERR_PTR(link_fd);
|
||||
}
|
||||
@ -7930,6 +7931,18 @@ bpf_program__attach_cgroup(struct bpf_program *prog, int cgroup_fd)
|
||||
return link;
|
||||
}
|
||||
|
||||
struct bpf_link *
|
||||
bpf_program__attach_cgroup(struct bpf_program *prog, int cgroup_fd)
|
||||
{
|
||||
return bpf_program__attach_fd(prog, cgroup_fd, "cgroup");
|
||||
}
|
||||
|
||||
struct bpf_link *
|
||||
bpf_program__attach_netns(struct bpf_program *prog, int netns_fd)
|
||||
{
|
||||
return bpf_program__attach_fd(prog, netns_fd, "netns");
|
||||
}
|
||||
|
||||
struct bpf_link *
|
||||
bpf_program__attach_iter(struct bpf_program *prog,
|
||||
const struct bpf_iter_attach_opts *opts)
|
||||
|
@ -253,6 +253,8 @@ LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_lsm(struct bpf_program *prog);
|
||||
LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_cgroup(struct bpf_program *prog, int cgroup_fd);
|
||||
LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_netns(struct bpf_program *prog, int netns_fd);
|
||||
|
||||
struct bpf_map;
|
||||
|
||||
|
@ -262,6 +262,7 @@ LIBBPF_0.0.9 {
|
||||
bpf_link_get_fd_by_id;
|
||||
bpf_link_get_next_id;
|
||||
bpf_program__attach_iter;
|
||||
bpf_program__attach_netns;
|
||||
perf_buffer__consume;
|
||||
ring_buffer__add;
|
||||
ring_buffer__consume;
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <linux/if_tun.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "bpf_flow.skel.h"
|
||||
|
||||
#ifndef IP_MF
|
||||
#define IP_MF 0x2000
|
||||
#endif
|
||||
@ -101,6 +103,7 @@ struct test {
|
||||
|
||||
#define VLAN_HLEN 4
|
||||
|
||||
static __u32 duration;
|
||||
struct test tests[] = {
|
||||
{
|
||||
.name = "ipv4",
|
||||
@ -444,17 +447,130 @@ static int ifup(const char *ifname)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_prog_array(struct bpf_object *obj, struct bpf_map *prog_array)
|
||||
{
|
||||
int i, err, map_fd, prog_fd;
|
||||
struct bpf_program *prog;
|
||||
char prog_name[32];
|
||||
|
||||
map_fd = bpf_map__fd(prog_array);
|
||||
if (map_fd < 0)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < bpf_map__def(prog_array)->max_entries; i++) {
|
||||
snprintf(prog_name, sizeof(prog_name), "flow_dissector/%i", i);
|
||||
|
||||
prog = bpf_object__find_program_by_title(obj, prog_name);
|
||||
if (!prog)
|
||||
return -1;
|
||||
|
||||
prog_fd = bpf_program__fd(prog);
|
||||
if (prog_fd < 0)
|
||||
return -1;
|
||||
|
||||
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
||||
if (err)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_tests_skb_less(int tap_fd, struct bpf_map *keys)
|
||||
{
|
||||
int i, err, keys_fd;
|
||||
|
||||
keys_fd = bpf_map__fd(keys);
|
||||
if (CHECK(keys_fd < 0, "bpf_map__fd", "err %d\n", keys_fd))
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
/* Keep in sync with 'flags' from eth_get_headlen. */
|
||||
__u32 eth_get_headlen_flags =
|
||||
BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG;
|
||||
struct bpf_prog_test_run_attr tattr = {};
|
||||
struct bpf_flow_keys flow_keys = {};
|
||||
__u32 key = (__u32)(tests[i].keys.sport) << 16 |
|
||||
tests[i].keys.dport;
|
||||
|
||||
/* For skb-less case we can't pass input flags; run
|
||||
* only the tests that have a matching set of flags.
|
||||
*/
|
||||
|
||||
if (tests[i].flags != eth_get_headlen_flags)
|
||||
continue;
|
||||
|
||||
err = tx_tap(tap_fd, &tests[i].pkt, sizeof(tests[i].pkt));
|
||||
CHECK(err < 0, "tx_tap", "err %d errno %d\n", err, errno);
|
||||
|
||||
err = bpf_map_lookup_elem(keys_fd, &key, &flow_keys);
|
||||
CHECK_ATTR(err, tests[i].name, "bpf_map_lookup_elem %d\n", err);
|
||||
|
||||
CHECK_ATTR(err, tests[i].name, "skb-less err %d\n", err);
|
||||
CHECK_FLOW_KEYS(tests[i].name, flow_keys, tests[i].keys);
|
||||
|
||||
err = bpf_map_delete_elem(keys_fd, &key);
|
||||
CHECK_ATTR(err, tests[i].name, "bpf_map_delete_elem %d\n", err);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_skb_less_prog_attach(struct bpf_flow *skel, int tap_fd)
|
||||
{
|
||||
int err, prog_fd;
|
||||
|
||||
prog_fd = bpf_program__fd(skel->progs._dissect);
|
||||
if (CHECK(prog_fd < 0, "bpf_program__fd", "err %d\n", prog_fd))
|
||||
return;
|
||||
|
||||
err = bpf_prog_attach(prog_fd, 0, BPF_FLOW_DISSECTOR, 0);
|
||||
if (CHECK(err, "bpf_prog_attach", "err %d errno %d\n", err, errno))
|
||||
return;
|
||||
|
||||
run_tests_skb_less(tap_fd, skel->maps.last_dissection);
|
||||
|
||||
err = bpf_prog_detach(prog_fd, BPF_FLOW_DISSECTOR);
|
||||
CHECK(err, "bpf_prog_detach", "err %d errno %d\n", err, errno);
|
||||
}
|
||||
|
||||
static void test_skb_less_link_create(struct bpf_flow *skel, int tap_fd)
|
||||
{
|
||||
struct bpf_link *link;
|
||||
int err, net_fd;
|
||||
|
||||
net_fd = open("/proc/self/ns/net", O_RDONLY);
|
||||
if (CHECK(net_fd < 0, "open(/proc/self/ns/net)", "err %d\n", errno))
|
||||
return;
|
||||
|
||||
link = bpf_program__attach_netns(skel->progs._dissect, net_fd);
|
||||
if (CHECK(IS_ERR(link), "attach_netns", "err %ld\n", PTR_ERR(link)))
|
||||
goto out_close;
|
||||
|
||||
run_tests_skb_less(tap_fd, skel->maps.last_dissection);
|
||||
|
||||
err = bpf_link__destroy(link);
|
||||
CHECK(err, "bpf_link__destroy", "err %d\n", err);
|
||||
out_close:
|
||||
close(net_fd);
|
||||
}
|
||||
|
||||
void test_flow_dissector(void)
|
||||
{
|
||||
int i, err, prog_fd, keys_fd = -1, tap_fd;
|
||||
struct bpf_object *obj;
|
||||
__u32 duration = 0;
|
||||
struct bpf_flow *skel;
|
||||
|
||||
err = bpf_flow_load(&obj, "./bpf_flow.o", "flow_dissector",
|
||||
"jmp_table", "last_dissection", &prog_fd, &keys_fd);
|
||||
if (CHECK_FAIL(err))
|
||||
skel = bpf_flow__open_and_load();
|
||||
if (CHECK(!skel, "skel", "failed to open/load skeleton\n"))
|
||||
return;
|
||||
|
||||
prog_fd = bpf_program__fd(skel->progs._dissect);
|
||||
if (CHECK(prog_fd < 0, "bpf_program__fd", "err %d\n", prog_fd))
|
||||
goto out_destroy_skel;
|
||||
keys_fd = bpf_map__fd(skel->maps.last_dissection);
|
||||
if (CHECK(keys_fd < 0, "bpf_map__fd", "err %d\n", keys_fd))
|
||||
goto out_destroy_skel;
|
||||
err = init_prog_array(skel->obj, skel->maps.jmp_table);
|
||||
if (CHECK(err, "init_prog_array", "err %d\n", err))
|
||||
goto out_destroy_skel;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
struct bpf_flow_keys flow_keys;
|
||||
struct bpf_prog_test_run_attr tattr = {
|
||||
@ -487,43 +603,17 @@ void test_flow_dissector(void)
|
||||
* via BPF map in this case.
|
||||
*/
|
||||
|
||||
err = bpf_prog_attach(prog_fd, 0, BPF_FLOW_DISSECTOR, 0);
|
||||
CHECK(err, "bpf_prog_attach", "err %d errno %d\n", err, errno);
|
||||
|
||||
tap_fd = create_tap("tap0");
|
||||
CHECK(tap_fd < 0, "create_tap", "tap_fd %d errno %d\n", tap_fd, errno);
|
||||
err = ifup("tap0");
|
||||
CHECK(err, "ifup", "err %d errno %d\n", err, errno);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
/* Keep in sync with 'flags' from eth_get_headlen. */
|
||||
__u32 eth_get_headlen_flags =
|
||||
BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG;
|
||||
struct bpf_prog_test_run_attr tattr = {};
|
||||
struct bpf_flow_keys flow_keys = {};
|
||||
__u32 key = (__u32)(tests[i].keys.sport) << 16 |
|
||||
tests[i].keys.dport;
|
||||
/* Test direct prog attachment */
|
||||
test_skb_less_prog_attach(skel, tap_fd);
|
||||
/* Test indirect prog attachment via link */
|
||||
test_skb_less_link_create(skel, tap_fd);
|
||||
|
||||
/* For skb-less case we can't pass input flags; run
|
||||
* only the tests that have a matching set of flags.
|
||||
*/
|
||||
|
||||
if (tests[i].flags != eth_get_headlen_flags)
|
||||
continue;
|
||||
|
||||
err = tx_tap(tap_fd, &tests[i].pkt, sizeof(tests[i].pkt));
|
||||
CHECK(err < 0, "tx_tap", "err %d errno %d\n", err, errno);
|
||||
|
||||
err = bpf_map_lookup_elem(keys_fd, &key, &flow_keys);
|
||||
CHECK_ATTR(err, tests[i].name, "bpf_map_lookup_elem %d\n", err);
|
||||
|
||||
CHECK_ATTR(err, tests[i].name, "skb-less err %d\n", err);
|
||||
CHECK_FLOW_KEYS(tests[i].name, flow_keys, tests[i].keys);
|
||||
|
||||
err = bpf_map_delete_elem(keys_fd, &key);
|
||||
CHECK_ATTR(err, tests[i].name, "bpf_map_delete_elem %d\n", err);
|
||||
}
|
||||
|
||||
bpf_prog_detach(prog_fd, BPF_FLOW_DISSECTOR);
|
||||
bpf_object__close(obj);
|
||||
close(tap_fd);
|
||||
out_destroy_skel:
|
||||
bpf_flow__destroy(skel);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/bpf.h>
|
||||
@ -18,21 +19,30 @@
|
||||
|
||||
#include "test_progs.h"
|
||||
|
||||
static bool is_attached(int netns)
|
||||
static int init_net = -1;
|
||||
|
||||
static __u32 query_attached_prog_id(int netns)
|
||||
{
|
||||
__u32 cnt;
|
||||
__u32 prog_ids[1] = {};
|
||||
__u32 prog_cnt = ARRAY_SIZE(prog_ids);
|
||||
int err;
|
||||
|
||||
err = bpf_prog_query(netns, BPF_FLOW_DISSECTOR, 0, NULL, NULL, &cnt);
|
||||
err = bpf_prog_query(netns, BPF_FLOW_DISSECTOR, 0, NULL,
|
||||
prog_ids, &prog_cnt);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_prog_query");
|
||||
return true; /* fail-safe */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cnt > 0;
|
||||
return prog_cnt == 1 ? prog_ids[0] : 0;
|
||||
}
|
||||
|
||||
static int load_prog(void)
|
||||
static bool prog_is_attached(int netns)
|
||||
{
|
||||
return query_attached_prog_id(netns) > 0;
|
||||
}
|
||||
|
||||
static int load_prog(enum bpf_prog_type type)
|
||||
{
|
||||
struct bpf_insn prog[] = {
|
||||
BPF_MOV64_IMM(BPF_REG_0, BPF_OK),
|
||||
@ -40,61 +50,566 @@ static int load_prog(void)
|
||||
};
|
||||
int fd;
|
||||
|
||||
fd = bpf_load_program(BPF_PROG_TYPE_FLOW_DISSECTOR, prog,
|
||||
ARRAY_SIZE(prog), "GPL", 0, NULL, 0);
|
||||
fd = bpf_load_program(type, prog, ARRAY_SIZE(prog), "GPL", 0, NULL, 0);
|
||||
if (CHECK_FAIL(fd < 0))
|
||||
perror("bpf_load_program");
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void do_flow_dissector_reattach(void)
|
||||
static __u32 query_prog_id(int prog)
|
||||
{
|
||||
int prog_fd[2] = { -1, -1 };
|
||||
struct bpf_prog_info info = {};
|
||||
__u32 info_len = sizeof(info);
|
||||
int err;
|
||||
|
||||
prog_fd[0] = load_prog();
|
||||
if (prog_fd[0] < 0)
|
||||
return;
|
||||
|
||||
prog_fd[1] = load_prog();
|
||||
if (prog_fd[1] < 0)
|
||||
goto out_close;
|
||||
|
||||
err = bpf_prog_attach(prog_fd[0], 0, BPF_FLOW_DISSECTOR, 0);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_prog_attach-0");
|
||||
goto out_close;
|
||||
err = bpf_obj_get_info_by_fd(prog, &info, &info_len);
|
||||
if (CHECK_FAIL(err || info_len != sizeof(info))) {
|
||||
perror("bpf_obj_get_info_by_fd");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return info.id;
|
||||
}
|
||||
|
||||
static int unshare_net(int old_net)
|
||||
{
|
||||
int err, new_net;
|
||||
|
||||
err = unshare(CLONE_NEWNET);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("unshare(CLONE_NEWNET)");
|
||||
return -1;
|
||||
}
|
||||
new_net = open("/proc/self/ns/net", O_RDONLY);
|
||||
if (CHECK_FAIL(new_net < 0)) {
|
||||
perror("open(/proc/self/ns/net)");
|
||||
setns(old_net, CLONE_NEWNET);
|
||||
return -1;
|
||||
}
|
||||
return new_net;
|
||||
}
|
||||
|
||||
static void test_prog_attach_prog_attach(int netns, int prog1, int prog2)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bpf_prog_attach(prog1, 0, BPF_FLOW_DISSECTOR, 0);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_prog_attach(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect success when attaching a different program */
|
||||
err = bpf_prog_attach(prog_fd[1], 0, BPF_FLOW_DISSECTOR, 0);
|
||||
err = bpf_prog_attach(prog2, 0, BPF_FLOW_DISSECTOR, 0);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_prog_attach-1");
|
||||
perror("bpf_prog_attach(prog2) #1");
|
||||
goto out_detach;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog2));
|
||||
|
||||
/* Expect failure when attaching the same program twice */
|
||||
err = bpf_prog_attach(prog_fd[1], 0, BPF_FLOW_DISSECTOR, 0);
|
||||
err = bpf_prog_attach(prog2, 0, BPF_FLOW_DISSECTOR, 0);
|
||||
if (CHECK_FAIL(!err || errno != EINVAL))
|
||||
perror("bpf_prog_attach-2");
|
||||
perror("bpf_prog_attach(prog2) #2");
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog2));
|
||||
|
||||
out_detach:
|
||||
err = bpf_prog_detach(0, BPF_FLOW_DISSECTOR);
|
||||
if (CHECK_FAIL(err))
|
||||
perror("bpf_prog_detach");
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_create_link_create(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts);
|
||||
int link1, link2;
|
||||
|
||||
link1 = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect failure creating link when another link exists */
|
||||
errno = 0;
|
||||
link2 = bpf_link_create(prog2, netns, BPF_FLOW_DISSECTOR, &opts);
|
||||
if (CHECK_FAIL(link2 != -1 || errno != E2BIG))
|
||||
perror("bpf_prog_attach(prog2) expected E2BIG");
|
||||
if (link2 != -1)
|
||||
close(link2);
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
close(link1);
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_prog_attach_link_create(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts);
|
||||
int err, link;
|
||||
|
||||
err = bpf_prog_attach(prog1, -1, BPF_FLOW_DISSECTOR, 0);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_prog_attach(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect failure creating link when prog attached */
|
||||
errno = 0;
|
||||
link = bpf_link_create(prog2, netns, BPF_FLOW_DISSECTOR, &opts);
|
||||
if (CHECK_FAIL(link != -1 || errno != EEXIST))
|
||||
perror("bpf_link_create(prog2) expected EEXIST");
|
||||
if (link != -1)
|
||||
close(link);
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
err = bpf_prog_detach(-1, BPF_FLOW_DISSECTOR);
|
||||
if (CHECK_FAIL(err))
|
||||
perror("bpf_prog_detach");
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_create_prog_attach(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts);
|
||||
int err, link;
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect failure attaching prog when link exists */
|
||||
errno = 0;
|
||||
err = bpf_prog_attach(prog2, -1, BPF_FLOW_DISSECTOR, 0);
|
||||
if (CHECK_FAIL(!err || errno != EEXIST))
|
||||
perror("bpf_prog_attach(prog2) expected EEXIST");
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
close(link);
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_create_prog_detach(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts);
|
||||
int err, link;
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect failure detaching prog when link exists */
|
||||
errno = 0;
|
||||
err = bpf_prog_detach(-1, BPF_FLOW_DISSECTOR);
|
||||
if (CHECK_FAIL(!err || errno != EINVAL))
|
||||
perror("bpf_prog_detach expected EINVAL");
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
close(link);
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_prog_attach_detach_query(int netns, int prog1, int prog2)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bpf_prog_attach(prog1, 0, BPF_FLOW_DISSECTOR, 0);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_prog_attach(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
err = bpf_prog_detach(0, BPF_FLOW_DISSECTOR);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_prog_detach");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Expect no prog attached after successful detach */
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_create_close_query(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts);
|
||||
int link;
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
close(link);
|
||||
/* Expect no prog attached after closing last link FD */
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_update_no_old_prog(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, create_opts);
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_update_opts, update_opts);
|
||||
int err, link;
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &create_opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect success replacing the prog when old prog not specified */
|
||||
update_opts.flags = 0;
|
||||
update_opts.old_prog_fd = 0;
|
||||
err = bpf_link_update(link, prog2, &update_opts);
|
||||
if (CHECK_FAIL(err))
|
||||
perror("bpf_link_update");
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog2));
|
||||
|
||||
close(link);
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_update_replace_old_prog(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, create_opts);
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_update_opts, update_opts);
|
||||
int err, link;
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &create_opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect success F_REPLACE and old prog specified to succeed */
|
||||
update_opts.flags = BPF_F_REPLACE;
|
||||
update_opts.old_prog_fd = prog1;
|
||||
err = bpf_link_update(link, prog2, &update_opts);
|
||||
if (CHECK_FAIL(err))
|
||||
perror("bpf_link_update");
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog2));
|
||||
|
||||
close(link);
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_update_invalid_opts(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, create_opts);
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_update_opts, update_opts);
|
||||
int err, link;
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &create_opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect update to fail w/ old prog FD but w/o F_REPLACE*/
|
||||
errno = 0;
|
||||
update_opts.flags = 0;
|
||||
update_opts.old_prog_fd = prog1;
|
||||
err = bpf_link_update(link, prog2, &update_opts);
|
||||
if (CHECK_FAIL(!err || errno != EINVAL)) {
|
||||
perror("bpf_link_update expected EINVAL");
|
||||
goto out_close;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect update to fail on old prog FD mismatch */
|
||||
errno = 0;
|
||||
update_opts.flags = BPF_F_REPLACE;
|
||||
update_opts.old_prog_fd = prog2;
|
||||
err = bpf_link_update(link, prog2, &update_opts);
|
||||
if (CHECK_FAIL(!err || errno != EPERM)) {
|
||||
perror("bpf_link_update expected EPERM");
|
||||
goto out_close;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect update to fail for invalid old prog FD */
|
||||
errno = 0;
|
||||
update_opts.flags = BPF_F_REPLACE;
|
||||
update_opts.old_prog_fd = -1;
|
||||
err = bpf_link_update(link, prog2, &update_opts);
|
||||
if (CHECK_FAIL(!err || errno != EBADF)) {
|
||||
perror("bpf_link_update expected EBADF");
|
||||
goto out_close;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect update to fail with invalid flags */
|
||||
errno = 0;
|
||||
update_opts.flags = BPF_F_ALLOW_MULTI;
|
||||
update_opts.old_prog_fd = 0;
|
||||
err = bpf_link_update(link, prog2, &update_opts);
|
||||
if (CHECK_FAIL(!err || errno != EINVAL))
|
||||
perror("bpf_link_update expected EINVAL");
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
out_close:
|
||||
close(prog_fd[1]);
|
||||
close(prog_fd[0]);
|
||||
close(link);
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_update_invalid_prog(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, create_opts);
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_update_opts, update_opts);
|
||||
int err, link, prog3;
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &create_opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
/* Expect failure when new prog FD is not valid */
|
||||
errno = 0;
|
||||
update_opts.flags = 0;
|
||||
update_opts.old_prog_fd = 0;
|
||||
err = bpf_link_update(link, -1, &update_opts);
|
||||
if (CHECK_FAIL(!err || errno != EBADF)) {
|
||||
perror("bpf_link_update expected EINVAL");
|
||||
goto out_close_link;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
prog3 = load_prog(BPF_PROG_TYPE_SOCKET_FILTER);
|
||||
if (prog3 < 0)
|
||||
goto out_close_link;
|
||||
|
||||
/* Expect failure when new prog FD type doesn't match */
|
||||
errno = 0;
|
||||
update_opts.flags = 0;
|
||||
update_opts.old_prog_fd = 0;
|
||||
err = bpf_link_update(link, prog3, &update_opts);
|
||||
if (CHECK_FAIL(!err || errno != EINVAL))
|
||||
perror("bpf_link_update expected EINVAL");
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
close(prog3);
|
||||
out_close_link:
|
||||
close(link);
|
||||
CHECK_FAIL(prog_is_attached(netns));
|
||||
}
|
||||
|
||||
static void test_link_update_netns_gone(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, create_opts);
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_update_opts, update_opts);
|
||||
int err, link, old_net;
|
||||
|
||||
old_net = netns;
|
||||
netns = unshare_net(old_net);
|
||||
if (netns < 0)
|
||||
return;
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &create_opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
return;
|
||||
}
|
||||
CHECK_FAIL(query_attached_prog_id(netns) != query_prog_id(prog1));
|
||||
|
||||
close(netns);
|
||||
err = setns(old_net, CLONE_NEWNET);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("setns(CLONE_NEWNET)");
|
||||
close(link);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Expect failure when netns destroyed */
|
||||
errno = 0;
|
||||
update_opts.flags = 0;
|
||||
update_opts.old_prog_fd = 0;
|
||||
err = bpf_link_update(link, prog2, &update_opts);
|
||||
if (CHECK_FAIL(!err || errno != ENOLINK))
|
||||
perror("bpf_link_update");
|
||||
|
||||
close(link);
|
||||
}
|
||||
|
||||
static void test_link_get_info(int netns, int prog1, int prog2)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_create_opts, create_opts);
|
||||
DECLARE_LIBBPF_OPTS(bpf_link_update_opts, update_opts);
|
||||
struct bpf_link_info info = {};
|
||||
struct stat netns_stat = {};
|
||||
__u32 info_len, link_id;
|
||||
int err, link, old_net;
|
||||
|
||||
old_net = netns;
|
||||
netns = unshare_net(old_net);
|
||||
if (netns < 0)
|
||||
return;
|
||||
|
||||
err = fstat(netns, &netns_stat);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("stat(netns)");
|
||||
goto out_resetns;
|
||||
}
|
||||
|
||||
link = bpf_link_create(prog1, netns, BPF_FLOW_DISSECTOR, &create_opts);
|
||||
if (CHECK_FAIL(link < 0)) {
|
||||
perror("bpf_link_create(prog1)");
|
||||
goto out_resetns;
|
||||
}
|
||||
|
||||
info_len = sizeof(info);
|
||||
err = bpf_obj_get_info_by_fd(link, &info, &info_len);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_obj_get_info");
|
||||
goto out_unlink;
|
||||
}
|
||||
CHECK_FAIL(info_len != sizeof(info));
|
||||
|
||||
/* Expect link info to be sane and match prog and netns details */
|
||||
CHECK_FAIL(info.type != BPF_LINK_TYPE_NETNS);
|
||||
CHECK_FAIL(info.id == 0);
|
||||
CHECK_FAIL(info.prog_id != query_prog_id(prog1));
|
||||
CHECK_FAIL(info.netns.netns_ino != netns_stat.st_ino);
|
||||
CHECK_FAIL(info.netns.attach_type != BPF_FLOW_DISSECTOR);
|
||||
|
||||
update_opts.flags = 0;
|
||||
update_opts.old_prog_fd = 0;
|
||||
err = bpf_link_update(link, prog2, &update_opts);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_link_update(prog2)");
|
||||
goto out_unlink;
|
||||
}
|
||||
|
||||
link_id = info.id;
|
||||
info_len = sizeof(info);
|
||||
err = bpf_obj_get_info_by_fd(link, &info, &info_len);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_obj_get_info");
|
||||
goto out_unlink;
|
||||
}
|
||||
CHECK_FAIL(info_len != sizeof(info));
|
||||
|
||||
/* Expect no info change after update except in prog id */
|
||||
CHECK_FAIL(info.type != BPF_LINK_TYPE_NETNS);
|
||||
CHECK_FAIL(info.id != link_id);
|
||||
CHECK_FAIL(info.prog_id != query_prog_id(prog2));
|
||||
CHECK_FAIL(info.netns.netns_ino != netns_stat.st_ino);
|
||||
CHECK_FAIL(info.netns.attach_type != BPF_FLOW_DISSECTOR);
|
||||
|
||||
/* Leave netns link is attached to and close last FD to it */
|
||||
err = setns(old_net, CLONE_NEWNET);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("setns(NEWNET)");
|
||||
goto out_unlink;
|
||||
}
|
||||
close(netns);
|
||||
old_net = -1;
|
||||
netns = -1;
|
||||
|
||||
info_len = sizeof(info);
|
||||
err = bpf_obj_get_info_by_fd(link, &info, &info_len);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("bpf_obj_get_info");
|
||||
goto out_unlink;
|
||||
}
|
||||
CHECK_FAIL(info_len != sizeof(info));
|
||||
|
||||
/* Expect netns_ino to change to 0 */
|
||||
CHECK_FAIL(info.type != BPF_LINK_TYPE_NETNS);
|
||||
CHECK_FAIL(info.id != link_id);
|
||||
CHECK_FAIL(info.prog_id != query_prog_id(prog2));
|
||||
CHECK_FAIL(info.netns.netns_ino != 0);
|
||||
CHECK_FAIL(info.netns.attach_type != BPF_FLOW_DISSECTOR);
|
||||
|
||||
out_unlink:
|
||||
close(link);
|
||||
out_resetns:
|
||||
if (old_net != -1)
|
||||
setns(old_net, CLONE_NEWNET);
|
||||
if (netns != -1)
|
||||
close(netns);
|
||||
}
|
||||
|
||||
static void run_tests(int netns)
|
||||
{
|
||||
struct test {
|
||||
const char *test_name;
|
||||
void (*test_func)(int netns, int prog1, int prog2);
|
||||
} tests[] = {
|
||||
{ "prog attach, prog attach",
|
||||
test_prog_attach_prog_attach },
|
||||
{ "link create, link create",
|
||||
test_link_create_link_create },
|
||||
{ "prog attach, link create",
|
||||
test_prog_attach_link_create },
|
||||
{ "link create, prog attach",
|
||||
test_link_create_prog_attach },
|
||||
{ "link create, prog detach",
|
||||
test_link_create_prog_detach },
|
||||
{ "prog attach, detach, query",
|
||||
test_prog_attach_detach_query },
|
||||
{ "link create, close, query",
|
||||
test_link_create_close_query },
|
||||
{ "link update no old prog",
|
||||
test_link_update_no_old_prog },
|
||||
{ "link update with replace old prog",
|
||||
test_link_update_replace_old_prog },
|
||||
{ "link update invalid opts",
|
||||
test_link_update_invalid_opts },
|
||||
{ "link update invalid prog",
|
||||
test_link_update_invalid_prog },
|
||||
{ "link update netns gone",
|
||||
test_link_update_netns_gone },
|
||||
{ "link get info",
|
||||
test_link_get_info },
|
||||
};
|
||||
int i, progs[2] = { -1, -1 };
|
||||
char test_name[80];
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(progs); i++) {
|
||||
progs[i] = load_prog(BPF_PROG_TYPE_FLOW_DISSECTOR);
|
||||
if (progs[i] < 0)
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
snprintf(test_name, sizeof(test_name),
|
||||
"flow dissector %s%s",
|
||||
tests[i].test_name,
|
||||
netns == init_net ? " (init_net)" : "");
|
||||
if (test__start_subtest(test_name))
|
||||
tests[i].test_func(netns, progs[0], progs[1]);
|
||||
}
|
||||
out_close:
|
||||
for (i = 0; i < ARRAY_SIZE(progs); i++) {
|
||||
if (progs[i] != -1)
|
||||
CHECK_FAIL(close(progs[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void test_flow_dissector_reattach(void)
|
||||
{
|
||||
int init_net, self_net, err;
|
||||
int err, new_net, saved_net;
|
||||
|
||||
self_net = open("/proc/self/ns/net", O_RDONLY);
|
||||
if (CHECK_FAIL(self_net < 0)) {
|
||||
saved_net = open("/proc/self/ns/net", O_RDONLY);
|
||||
if (CHECK_FAIL(saved_net < 0)) {
|
||||
perror("open(/proc/self/ns/net");
|
||||
return;
|
||||
}
|
||||
@ -111,30 +626,29 @@ void test_flow_dissector_reattach(void)
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
if (is_attached(init_net)) {
|
||||
if (prog_is_attached(init_net)) {
|
||||
test__skip();
|
||||
printf("Can't test with flow dissector attached to init_net\n");
|
||||
goto out_setns;
|
||||
}
|
||||
|
||||
/* First run tests in root network namespace */
|
||||
do_flow_dissector_reattach();
|
||||
run_tests(init_net);
|
||||
|
||||
/* Then repeat tests in a non-root namespace */
|
||||
err = unshare(CLONE_NEWNET);
|
||||
if (CHECK_FAIL(err)) {
|
||||
perror("unshare(CLONE_NEWNET)");
|
||||
new_net = unshare_net(init_net);
|
||||
if (new_net < 0)
|
||||
goto out_setns;
|
||||
}
|
||||
do_flow_dissector_reattach();
|
||||
run_tests(new_net);
|
||||
close(new_net);
|
||||
|
||||
out_setns:
|
||||
/* Move back to netns we started in. */
|
||||
err = setns(self_net, CLONE_NEWNET);
|
||||
err = setns(saved_net, CLONE_NEWNET);
|
||||
if (CHECK_FAIL(err))
|
||||
perror("setns(/proc/self/ns/net)");
|
||||
|
||||
out_close:
|
||||
close(init_net);
|
||||
close(self_net);
|
||||
close(saved_net);
|
||||
}
|
||||
|
@ -20,20 +20,20 @@
|
||||
#include <bpf/bpf_endian.h>
|
||||
|
||||
int _version SEC("version") = 1;
|
||||
#define PROG(F) SEC(#F) int bpf_func_##F
|
||||
#define PROG(F) PROG_(F, _##F)
|
||||
#define PROG_(NUM, NAME) SEC("flow_dissector/"#NUM) int bpf_func##NAME
|
||||
|
||||
/* These are the identifiers of the BPF programs that will be used in tail
|
||||
* calls. Name is limited to 16 characters, with the terminating character and
|
||||
* bpf_func_ above, we have only 6 to work with, anything after will be cropped.
|
||||
*/
|
||||
enum {
|
||||
IP,
|
||||
IPV6,
|
||||
IPV6OP, /* Destination/Hop-by-Hop Options IPv6 Extension header */
|
||||
IPV6FR, /* Fragmentation IPv6 Extension Header */
|
||||
MPLS,
|
||||
VLAN,
|
||||
};
|
||||
#define IP 0
|
||||
#define IPV6 1
|
||||
#define IPV6OP 2 /* Destination/Hop-by-Hop Options IPv6 Ext. Header */
|
||||
#define IPV6FR 3 /* Fragmentation IPv6 Extension Header */
|
||||
#define MPLS 4
|
||||
#define VLAN 5
|
||||
#define MAX_PROG 6
|
||||
|
||||
#define IP_MF 0x2000
|
||||
#define IP_OFFSET 0x1FFF
|
||||
@ -59,7 +59,7 @@ struct frag_hdr {
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
|
||||
__uint(max_entries, 8);
|
||||
__uint(max_entries, MAX_PROG);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(__u32));
|
||||
} jmp_table SEC(".maps");
|
||||
|
Loading…
Reference in New Issue
Block a user