mirror of
https://github.com/torvalds/linux.git
synced 2026-06-01 19:13:47 +02:00
Merge branch 'bpf-implement-mprog-api-on-top-of-existing-cgroup-progs'
Yonghong Song says: ==================== bpf: Implement mprog API on top of existing cgroup progs Current cgroup prog ordering is appending at attachment time. This is not ideal. In some cases, users want specific ordering at a particular cgroup level. For example, in Meta, we have a case where three different applications all have cgroup/setsockopt progs and they require specific ordering. Current approach is to use a bpfchainer where one bpf prog contains multiple global functions and each global function can be freplaced by a prog for a specific application. The ordering of global functions decides the ordering of those application specific bpf progs. Using bpfchainer is a centralized approach and is not desirable as one of applications acts as a daemon. The decentralized attachment approach is more favorable for those applications. To address this, the existing mprog API ([2]) seems an ideal solution with supporting BPF_F_BEFORE and BPF_F_AFTER flags on top of existing cgroup bpf implementation. More specifically, the support is added for prog/link attachment with BPF_F_BEFORE and BPF_F_AFTER. The kernel mprog interface ([2]) is not used and the implementation is directly done in cgroup bpf code base. The mprog 'revision' is also implemented in attach/detach/replace, so users can query revision number to check the change of cgroup prog list. The patch set contains 5 patches. Patch 1 adds revision support for cgroup bpf progs. Patch 2 implements mprog API implementation for prog/link attach and revision update. Patch 3 adds a new libbpf API to do cgroup link attach with flags like BPF_F_BEFORE/BPF_F_AFTER. Patches 4 and 5 add two tests to validate the implementation. [1] https://lore.kernel.org/r/20250224230116.283071-1-yonghong.song@linux.dev [2] https://lore.kernel.org/r/20230719140858.13224-2-daniel@iogearbox.net Changelogs: v4 -> v5: - v4: https://lore.kernel.org/bpf/20250530173812.1823479-1-yonghong.song@linux.dev/ - Remove early prog/link checking based flags and id_or_fd as later code will do checking as well. - Do proper cgroup flag checking for bpf_prog_attach(). v3 -> v4: - v3: https://lore.kernel.org/bpf/20250517162720.4077882-1-yonghong.song@linux.dev/ - Refactor some to make BPF_F_BEFORE/BPF_F_AFTER handling easier to understand. - Perviously, I degraded 'link' to 'prog' for later mprog handling. This is not correct. Similar to mprog.c, we should be check 'link' instead link->prog since it is possible two different links may have the same underlying prog and we do not want to miss supporting such use case. v2 -> v3: - v2: https://lore.kernel.org/bpf/20250508223524.487875-1-yonghong.song@linux.dev/ - Big change to replace get_anchor_prog() to get_prog_list() so the 'struct bpf_prog_list *' is returned directly. - Support 'BPF_F_BEFORE | BPF_F_AFTER' attachment if the prog list is empty and flags do not have 'BPF_F_LINK | BPF_F_ID' and id_or_fd is 0. - Add BPF_F_LINK support. - Patch 4 is added to reuse id_from_prog_fd() and id_from_link_fd(). v1 -> v2: - v1: https://lore.kernel.org/bpf/20250411011523.1838771-1-yonghong.song@linux.dev/ - Change cgroup_bpf.revisions from atomic64_t to u64. - Added missing bpf_prog_put in various places. - Rename get_cmp_prog() to get_anchor_prog(). The implementation tries to find the anchor prog regardless of whether id_or_fd is non-NULL or not. - Rename bpf_cgroup_prog_attached() to is_cgroup_prog_type() and handle BPF_PROG_TYPE_LSM properly (with BPF_LSM_CGROUP attach type). - I kept 'id || id_or_fd' condition as the condition 'id' is also used in mprog.c so I assume it is okay in cgroup.c as well. ==================== Link: https://patch.msgid.link/20250606163131.2428225-1-yonghong.song@linux.dev Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
commit
4d2815a1cc
|
|
@ -63,6 +63,7 @@ struct cgroup_bpf {
|
|||
*/
|
||||
struct hlist_head progs[MAX_CGROUP_BPF_ATTACH_TYPE];
|
||||
u8 flags[MAX_CGROUP_BPF_ATTACH_TYPE];
|
||||
u64 revisions[MAX_CGROUP_BPF_ATTACH_TYPE];
|
||||
|
||||
/* list of cgroup shared storages */
|
||||
struct list_head storages;
|
||||
|
|
|
|||
|
|
@ -1794,6 +1794,13 @@ union bpf_attr {
|
|||
};
|
||||
__u64 expected_revision;
|
||||
} netkit;
|
||||
struct {
|
||||
union {
|
||||
__u32 relative_fd;
|
||||
__u32 relative_id;
|
||||
};
|
||||
__u64 expected_revision;
|
||||
} cgroup;
|
||||
};
|
||||
} link_create;
|
||||
|
||||
|
|
|
|||
|
|
@ -658,6 +658,116 @@ static struct bpf_prog_list *find_attach_entry(struct hlist_head *progs,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct bpf_link *bpf_get_anchor_link(u32 flags, u32 id_or_fd)
|
||||
{
|
||||
struct bpf_link *link = ERR_PTR(-EINVAL);
|
||||
|
||||
if (flags & BPF_F_ID)
|
||||
link = bpf_link_by_id(id_or_fd);
|
||||
else if (id_or_fd)
|
||||
link = bpf_link_get_from_fd(id_or_fd);
|
||||
return link;
|
||||
}
|
||||
|
||||
static struct bpf_prog *bpf_get_anchor_prog(u32 flags, u32 id_or_fd)
|
||||
{
|
||||
struct bpf_prog *prog = ERR_PTR(-EINVAL);
|
||||
|
||||
if (flags & BPF_F_ID)
|
||||
prog = bpf_prog_by_id(id_or_fd);
|
||||
else if (id_or_fd)
|
||||
prog = bpf_prog_get(id_or_fd);
|
||||
return prog;
|
||||
}
|
||||
|
||||
static struct bpf_prog_list *get_prog_list(struct hlist_head *progs, struct bpf_prog *prog,
|
||||
struct bpf_cgroup_link *link, u32 flags, u32 id_or_fd)
|
||||
{
|
||||
bool is_link = flags & BPF_F_LINK, is_id = flags & BPF_F_ID;
|
||||
struct bpf_prog_list *pltmp, *pl = ERR_PTR(-EINVAL);
|
||||
bool preorder = flags & BPF_F_PREORDER;
|
||||
struct bpf_link *anchor_link = NULL;
|
||||
struct bpf_prog *anchor_prog = NULL;
|
||||
bool is_before, is_after;
|
||||
|
||||
is_before = flags & BPF_F_BEFORE;
|
||||
is_after = flags & BPF_F_AFTER;
|
||||
if (is_link || is_id || id_or_fd) {
|
||||
/* flags must have either BPF_F_BEFORE or BPF_F_AFTER */
|
||||
if (is_before == is_after)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if ((is_link && !link) || (!is_link && !prog))
|
||||
return ERR_PTR(-EINVAL);
|
||||
} else if (!hlist_empty(progs)) {
|
||||
/* flags cannot have both BPF_F_BEFORE and BPF_F_AFTER */
|
||||
if (is_before && is_after)
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
if (is_link) {
|
||||
anchor_link = bpf_get_anchor_link(flags, id_or_fd);
|
||||
if (IS_ERR(anchor_link))
|
||||
return ERR_PTR(PTR_ERR(anchor_link));
|
||||
} else if (is_id || id_or_fd) {
|
||||
anchor_prog = bpf_get_anchor_prog(flags, id_or_fd);
|
||||
if (IS_ERR(anchor_prog))
|
||||
return ERR_PTR(PTR_ERR(anchor_prog));
|
||||
}
|
||||
|
||||
if (!anchor_prog && !anchor_link) {
|
||||
/* if there is no anchor_prog/anchor_link, then BPF_F_PREORDER
|
||||
* doesn't matter since either prepend or append to a combined
|
||||
* list of progs will end up with correct result.
|
||||
*/
|
||||
hlist_for_each_entry(pltmp, progs, node) {
|
||||
if (is_before)
|
||||
return pltmp;
|
||||
if (pltmp->node.next)
|
||||
continue;
|
||||
return pltmp;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hlist_for_each_entry(pltmp, progs, node) {
|
||||
if ((anchor_prog && anchor_prog == pltmp->prog) ||
|
||||
(anchor_link && anchor_link == &pltmp->link->link)) {
|
||||
if (!!(pltmp->flags & BPF_F_PREORDER) != preorder)
|
||||
goto out;
|
||||
pl = pltmp;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
pl = ERR_PTR(-ENOENT);
|
||||
out:
|
||||
if (anchor_link)
|
||||
bpf_link_put(anchor_link);
|
||||
else
|
||||
bpf_prog_put(anchor_prog);
|
||||
return pl;
|
||||
}
|
||||
|
||||
static int insert_pl_to_hlist(struct bpf_prog_list *pl, struct hlist_head *progs,
|
||||
struct bpf_prog *prog, struct bpf_cgroup_link *link,
|
||||
u32 flags, u32 id_or_fd)
|
||||
{
|
||||
struct bpf_prog_list *pltmp;
|
||||
|
||||
pltmp = get_prog_list(progs, prog, link, flags, id_or_fd);
|
||||
if (IS_ERR(pltmp))
|
||||
return PTR_ERR(pltmp);
|
||||
|
||||
if (!pltmp)
|
||||
hlist_add_head(&pl->node, progs);
|
||||
else if (flags & BPF_F_BEFORE)
|
||||
hlist_add_before(&pl->node, &pltmp->node);
|
||||
else
|
||||
hlist_add_behind(&pl->node, &pltmp->node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* __cgroup_bpf_attach() - Attach the program or the link to a cgroup, and
|
||||
* propagate the change to descendants
|
||||
|
|
@ -667,6 +777,8 @@ static struct bpf_prog_list *find_attach_entry(struct hlist_head *progs,
|
|||
* @replace_prog: Previously attached program to replace if BPF_F_REPLACE is set
|
||||
* @type: Type of attach operation
|
||||
* @flags: Option flags
|
||||
* @id_or_fd: Relative prog id or fd
|
||||
* @revision: bpf_prog_list revision
|
||||
*
|
||||
* Exactly one of @prog or @link can be non-null.
|
||||
* Must be called with cgroup_mutex held.
|
||||
|
|
@ -674,7 +786,8 @@ static struct bpf_prog_list *find_attach_entry(struct hlist_head *progs,
|
|||
static int __cgroup_bpf_attach(struct cgroup *cgrp,
|
||||
struct bpf_prog *prog, struct bpf_prog *replace_prog,
|
||||
struct bpf_cgroup_link *link,
|
||||
enum bpf_attach_type type, u32 flags)
|
||||
enum bpf_attach_type type, u32 flags, u32 id_or_fd,
|
||||
u64 revision)
|
||||
{
|
||||
u32 saved_flags = (flags & (BPF_F_ALLOW_OVERRIDE | BPF_F_ALLOW_MULTI));
|
||||
struct bpf_prog *old_prog = NULL;
|
||||
|
|
@ -690,6 +803,9 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
|
|||
((flags & BPF_F_REPLACE) && !(flags & BPF_F_ALLOW_MULTI)))
|
||||
/* invalid combination */
|
||||
return -EINVAL;
|
||||
if ((flags & BPF_F_REPLACE) && (flags & (BPF_F_BEFORE | BPF_F_AFTER)))
|
||||
/* only either replace or insertion with before/after */
|
||||
return -EINVAL;
|
||||
if (link && (prog || replace_prog))
|
||||
/* only either link or prog/replace_prog can be specified */
|
||||
return -EINVAL;
|
||||
|
|
@ -700,6 +816,8 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
|
|||
atype = bpf_cgroup_atype_find(type, new_prog->aux->attach_btf_id);
|
||||
if (atype < 0)
|
||||
return -EINVAL;
|
||||
if (revision && revision != cgrp->bpf.revisions[atype])
|
||||
return -ESTALE;
|
||||
|
||||
progs = &cgrp->bpf.progs[atype];
|
||||
|
||||
|
|
@ -728,22 +846,18 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
|
|||
if (pl) {
|
||||
old_prog = pl->prog;
|
||||
} else {
|
||||
struct hlist_node *last = NULL;
|
||||
|
||||
pl = kmalloc(sizeof(*pl), GFP_KERNEL);
|
||||
if (!pl) {
|
||||
bpf_cgroup_storages_free(new_storage);
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (hlist_empty(progs))
|
||||
hlist_add_head(&pl->node, progs);
|
||||
else
|
||||
hlist_for_each(last, progs) {
|
||||
if (last->next)
|
||||
continue;
|
||||
hlist_add_behind(&pl->node, last);
|
||||
break;
|
||||
}
|
||||
|
||||
err = insert_pl_to_hlist(pl, progs, prog, link, flags, id_or_fd);
|
||||
if (err) {
|
||||
kfree(pl);
|
||||
bpf_cgroup_storages_free(new_storage);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
pl->prog = prog;
|
||||
|
|
@ -762,6 +876,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
|
|||
if (err)
|
||||
goto cleanup_trampoline;
|
||||
|
||||
cgrp->bpf.revisions[atype] += 1;
|
||||
if (old_prog) {
|
||||
if (type == BPF_LSM_CGROUP)
|
||||
bpf_trampoline_unlink_cgroup_shim(old_prog);
|
||||
|
|
@ -793,12 +908,13 @@ static int cgroup_bpf_attach(struct cgroup *cgrp,
|
|||
struct bpf_prog *prog, struct bpf_prog *replace_prog,
|
||||
struct bpf_cgroup_link *link,
|
||||
enum bpf_attach_type type,
|
||||
u32 flags)
|
||||
u32 flags, u32 id_or_fd, u64 revision)
|
||||
{
|
||||
int ret;
|
||||
|
||||
cgroup_lock();
|
||||
ret = __cgroup_bpf_attach(cgrp, prog, replace_prog, link, type, flags);
|
||||
ret = __cgroup_bpf_attach(cgrp, prog, replace_prog, link, type, flags,
|
||||
id_or_fd, revision);
|
||||
cgroup_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -886,6 +1002,7 @@ static int __cgroup_bpf_replace(struct cgroup *cgrp,
|
|||
if (!found)
|
||||
return -ENOENT;
|
||||
|
||||
cgrp->bpf.revisions[atype] += 1;
|
||||
old_prog = xchg(&link->link.prog, new_prog);
|
||||
replace_effective_prog(cgrp, atype, link);
|
||||
bpf_prog_put(old_prog);
|
||||
|
|
@ -1011,12 +1128,14 @@ static void purge_effective_progs(struct cgroup *cgrp, struct bpf_prog *prog,
|
|||
* @prog: A program to detach or NULL
|
||||
* @link: A link to detach or NULL
|
||||
* @type: Type of detach operation
|
||||
* @revision: bpf_prog_list revision
|
||||
*
|
||||
* At most one of @prog or @link can be non-NULL.
|
||||
* Must be called with cgroup_mutex held.
|
||||
*/
|
||||
static int __cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
struct bpf_cgroup_link *link, enum bpf_attach_type type)
|
||||
struct bpf_cgroup_link *link, enum bpf_attach_type type,
|
||||
u64 revision)
|
||||
{
|
||||
enum cgroup_bpf_attach_type atype;
|
||||
struct bpf_prog *old_prog;
|
||||
|
|
@ -1034,6 +1153,9 @@ static int __cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
|||
if (atype < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (revision && revision != cgrp->bpf.revisions[atype])
|
||||
return -ESTALE;
|
||||
|
||||
progs = &cgrp->bpf.progs[atype];
|
||||
flags = cgrp->bpf.flags[atype];
|
||||
|
||||
|
|
@ -1059,6 +1181,7 @@ static int __cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
|||
|
||||
/* now can actually delete it from this cgroup list */
|
||||
hlist_del(&pl->node);
|
||||
cgrp->bpf.revisions[atype] += 1;
|
||||
|
||||
kfree(pl);
|
||||
if (hlist_empty(progs))
|
||||
|
|
@ -1074,12 +1197,12 @@ static int __cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
|||
}
|
||||
|
||||
static int cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
enum bpf_attach_type type)
|
||||
enum bpf_attach_type type, u64 revision)
|
||||
{
|
||||
int ret;
|
||||
|
||||
cgroup_lock();
|
||||
ret = __cgroup_bpf_detach(cgrp, prog, NULL, type);
|
||||
ret = __cgroup_bpf_detach(cgrp, prog, NULL, type, revision);
|
||||
cgroup_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1097,6 +1220,7 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
|
|||
struct bpf_prog_array *effective;
|
||||
int cnt, ret = 0, i;
|
||||
int total_cnt = 0;
|
||||
u64 revision = 0;
|
||||
u32 flags;
|
||||
|
||||
if (effective_query && prog_attach_flags)
|
||||
|
|
@ -1134,6 +1258,10 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
|
|||
return -EFAULT;
|
||||
if (copy_to_user(&uattr->query.prog_cnt, &total_cnt, sizeof(total_cnt)))
|
||||
return -EFAULT;
|
||||
if (!effective_query && from_atype == to_atype)
|
||||
revision = cgrp->bpf.revisions[from_atype];
|
||||
if (copy_to_user(&uattr->query.revision, &revision, sizeof(revision)))
|
||||
return -EFAULT;
|
||||
if (attr->query.prog_cnt == 0 || !prog_ids || !total_cnt)
|
||||
/* return early if user requested only program count + flags */
|
||||
return 0;
|
||||
|
|
@ -1216,7 +1344,8 @@ int cgroup_bpf_prog_attach(const union bpf_attr *attr,
|
|||
}
|
||||
|
||||
ret = cgroup_bpf_attach(cgrp, prog, replace_prog, NULL,
|
||||
attr->attach_type, attr->attach_flags);
|
||||
attr->attach_type, attr->attach_flags,
|
||||
attr->relative_fd, attr->expected_revision);
|
||||
|
||||
if (replace_prog)
|
||||
bpf_prog_put(replace_prog);
|
||||
|
|
@ -1238,7 +1367,7 @@ int cgroup_bpf_prog_detach(const union bpf_attr *attr, enum bpf_prog_type ptype)
|
|||
if (IS_ERR(prog))
|
||||
prog = NULL;
|
||||
|
||||
ret = cgroup_bpf_detach(cgrp, prog, attr->attach_type);
|
||||
ret = cgroup_bpf_detach(cgrp, prog, attr->attach_type, attr->expected_revision);
|
||||
if (prog)
|
||||
bpf_prog_put(prog);
|
||||
|
||||
|
|
@ -1267,7 +1396,7 @@ 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_link->type, 0));
|
||||
if (cg_link->type == BPF_LSM_CGROUP)
|
||||
bpf_trampoline_unlink_cgroup_shim(cg_link->link.prog);
|
||||
|
||||
|
|
@ -1339,6 +1468,13 @@ static const struct bpf_link_ops bpf_cgroup_link_lops = {
|
|||
.fill_link_info = bpf_cgroup_link_fill_link_info,
|
||||
};
|
||||
|
||||
#define BPF_F_LINK_ATTACH_MASK \
|
||||
(BPF_F_ID | \
|
||||
BPF_F_BEFORE | \
|
||||
BPF_F_AFTER | \
|
||||
BPF_F_PREORDER | \
|
||||
BPF_F_LINK)
|
||||
|
||||
int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_link_primer link_primer;
|
||||
|
|
@ -1346,7 +1482,7 @@ int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
|
|||
struct cgroup *cgrp;
|
||||
int err;
|
||||
|
||||
if (attr->link_create.flags)
|
||||
if (attr->link_create.flags & (~BPF_F_LINK_ATTACH_MASK))
|
||||
return -EINVAL;
|
||||
|
||||
cgrp = cgroup_get_from_fd(attr->link_create.target_fd);
|
||||
|
|
@ -1370,7 +1506,9 @@ int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
|
|||
}
|
||||
|
||||
err = cgroup_bpf_attach(cgrp, NULL, NULL, link,
|
||||
link->type, BPF_F_ALLOW_MULTI);
|
||||
link->type, BPF_F_ALLOW_MULTI | attr->link_create.flags,
|
||||
attr->link_create.cgroup.relative_fd,
|
||||
attr->link_create.cgroup.expected_revision);
|
||||
if (err) {
|
||||
bpf_link_cleanup(&link_primer);
|
||||
goto out_put_cgroup;
|
||||
|
|
|
|||
|
|
@ -4186,6 +4186,25 @@ static int bpf_prog_attach_check_attach_type(const struct bpf_prog *prog,
|
|||
}
|
||||
}
|
||||
|
||||
static bool is_cgroup_prog_type(enum bpf_prog_type ptype, enum bpf_attach_type atype,
|
||||
bool check_atype)
|
||||
{
|
||||
switch (ptype) {
|
||||
case BPF_PROG_TYPE_CGROUP_DEVICE:
|
||||
case BPF_PROG_TYPE_CGROUP_SKB:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
|
||||
case BPF_PROG_TYPE_CGROUP_SYSCTL:
|
||||
case BPF_PROG_TYPE_SOCK_OPS:
|
||||
return true;
|
||||
case BPF_PROG_TYPE_LSM:
|
||||
return check_atype ? atype == BPF_LSM_CGROUP : true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#define BPF_PROG_ATTACH_LAST_FIELD expected_revision
|
||||
|
||||
#define BPF_F_ATTACH_MASK_BASE \
|
||||
|
|
@ -4216,6 +4235,9 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
|||
if (bpf_mprog_supported(ptype)) {
|
||||
if (attr->attach_flags & ~BPF_F_ATTACH_MASK_MPROG)
|
||||
return -EINVAL;
|
||||
} else if (is_cgroup_prog_type(ptype, 0, false)) {
|
||||
if (attr->attach_flags & ~(BPF_F_ATTACH_MASK_BASE | BPF_F_ATTACH_MASK_MPROG))
|
||||
return -EINVAL;
|
||||
} else {
|
||||
if (attr->attach_flags & ~BPF_F_ATTACH_MASK_BASE)
|
||||
return -EINVAL;
|
||||
|
|
@ -4233,6 +4255,11 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (is_cgroup_prog_type(ptype, prog->expected_attach_type, true)) {
|
||||
ret = cgroup_bpf_prog_attach(attr, ptype, prog);
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (ptype) {
|
||||
case BPF_PROG_TYPE_SK_SKB:
|
||||
case BPF_PROG_TYPE_SK_MSG:
|
||||
|
|
@ -4244,20 +4271,6 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
|||
case BPF_PROG_TYPE_FLOW_DISSECTOR:
|
||||
ret = netns_bpf_prog_attach(attr, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_CGROUP_DEVICE:
|
||||
case BPF_PROG_TYPE_CGROUP_SKB:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
|
||||
case BPF_PROG_TYPE_CGROUP_SYSCTL:
|
||||
case BPF_PROG_TYPE_SOCK_OPS:
|
||||
case BPF_PROG_TYPE_LSM:
|
||||
if (ptype == BPF_PROG_TYPE_LSM &&
|
||||
prog->expected_attach_type != BPF_LSM_CGROUP)
|
||||
ret = -EINVAL;
|
||||
else
|
||||
ret = cgroup_bpf_prog_attach(attr, ptype, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_SCHED_CLS:
|
||||
if (attr->attach_type == BPF_TCX_INGRESS ||
|
||||
attr->attach_type == BPF_TCX_EGRESS)
|
||||
|
|
@ -4268,7 +4281,7 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
|||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
if (ret)
|
||||
bpf_prog_put(prog);
|
||||
return ret;
|
||||
|
|
@ -4296,6 +4309,9 @@ static int bpf_prog_detach(const union bpf_attr *attr)
|
|||
if (IS_ERR(prog))
|
||||
return PTR_ERR(prog);
|
||||
}
|
||||
} else if (is_cgroup_prog_type(ptype, 0, false)) {
|
||||
if (attr->attach_flags || attr->relative_fd)
|
||||
return -EINVAL;
|
||||
} else if (attr->attach_flags ||
|
||||
attr->relative_fd ||
|
||||
attr->expected_revision) {
|
||||
|
|
|
|||
|
|
@ -2074,6 +2074,11 @@ static void init_cgroup_housekeeping(struct cgroup *cgrp)
|
|||
for_each_subsys(ss, ssid)
|
||||
INIT_LIST_HEAD(&cgrp->e_csets[ssid]);
|
||||
|
||||
#ifdef CONFIG_CGROUP_BPF
|
||||
for (int i = 0; i < ARRAY_SIZE(cgrp->bpf.revisions); i++)
|
||||
cgrp->bpf.revisions[i] = 1;
|
||||
#endif
|
||||
|
||||
init_waitqueue_head(&cgrp->offline_waitq);
|
||||
INIT_WORK(&cgrp->release_agent_work, cgroup1_release_agent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1794,6 +1794,13 @@ union bpf_attr {
|
|||
};
|
||||
__u64 expected_revision;
|
||||
} netkit;
|
||||
struct {
|
||||
union {
|
||||
__u32 relative_fd;
|
||||
__u32 relative_id;
|
||||
};
|
||||
__u64 expected_revision;
|
||||
} cgroup;
|
||||
};
|
||||
} link_create;
|
||||
|
||||
|
|
|
|||
|
|
@ -837,6 +837,50 @@ int bpf_link_create(int prog_fd, int target_fd,
|
|||
if (!OPTS_ZEROED(opts, netkit))
|
||||
return libbpf_err(-EINVAL);
|
||||
break;
|
||||
case BPF_CGROUP_INET_INGRESS:
|
||||
case BPF_CGROUP_INET_EGRESS:
|
||||
case BPF_CGROUP_INET_SOCK_CREATE:
|
||||
case BPF_CGROUP_INET_SOCK_RELEASE:
|
||||
case BPF_CGROUP_INET4_BIND:
|
||||
case BPF_CGROUP_INET6_BIND:
|
||||
case BPF_CGROUP_INET4_POST_BIND:
|
||||
case BPF_CGROUP_INET6_POST_BIND:
|
||||
case BPF_CGROUP_INET4_CONNECT:
|
||||
case BPF_CGROUP_INET6_CONNECT:
|
||||
case BPF_CGROUP_UNIX_CONNECT:
|
||||
case BPF_CGROUP_INET4_GETPEERNAME:
|
||||
case BPF_CGROUP_INET6_GETPEERNAME:
|
||||
case BPF_CGROUP_UNIX_GETPEERNAME:
|
||||
case BPF_CGROUP_INET4_GETSOCKNAME:
|
||||
case BPF_CGROUP_INET6_GETSOCKNAME:
|
||||
case BPF_CGROUP_UNIX_GETSOCKNAME:
|
||||
case BPF_CGROUP_UDP4_SENDMSG:
|
||||
case BPF_CGROUP_UDP6_SENDMSG:
|
||||
case BPF_CGROUP_UNIX_SENDMSG:
|
||||
case BPF_CGROUP_UDP4_RECVMSG:
|
||||
case BPF_CGROUP_UDP6_RECVMSG:
|
||||
case BPF_CGROUP_UNIX_RECVMSG:
|
||||
case BPF_CGROUP_SOCK_OPS:
|
||||
case BPF_CGROUP_DEVICE:
|
||||
case BPF_CGROUP_SYSCTL:
|
||||
case BPF_CGROUP_GETSOCKOPT:
|
||||
case BPF_CGROUP_SETSOCKOPT:
|
||||
case BPF_LSM_CGROUP:
|
||||
relative_fd = OPTS_GET(opts, cgroup.relative_fd, 0);
|
||||
relative_id = OPTS_GET(opts, cgroup.relative_id, 0);
|
||||
if (relative_fd && relative_id)
|
||||
return libbpf_err(-EINVAL);
|
||||
if (relative_id) {
|
||||
attr.link_create.cgroup.relative_id = relative_id;
|
||||
attr.link_create.flags |= BPF_F_ID;
|
||||
} else {
|
||||
attr.link_create.cgroup.relative_fd = relative_fd;
|
||||
}
|
||||
attr.link_create.cgroup.expected_revision =
|
||||
OPTS_GET(opts, cgroup.expected_revision, 0);
|
||||
if (!OPTS_ZEROED(opts, cgroup))
|
||||
return libbpf_err(-EINVAL);
|
||||
break;
|
||||
default:
|
||||
if (!OPTS_ZEROED(opts, flags))
|
||||
return libbpf_err(-EINVAL);
|
||||
|
|
|
|||
|
|
@ -438,6 +438,11 @@ struct bpf_link_create_opts {
|
|||
__u32 relative_id;
|
||||
__u64 expected_revision;
|
||||
} netkit;
|
||||
struct {
|
||||
__u32 relative_fd;
|
||||
__u32 relative_id;
|
||||
__u64 expected_revision;
|
||||
} cgroup;
|
||||
};
|
||||
size_t :0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12837,6 +12837,34 @@ struct bpf_link *bpf_program__attach_xdp(const struct bpf_program *prog, int ifi
|
|||
return bpf_program_attach_fd(prog, ifindex, "xdp", NULL);
|
||||
}
|
||||
|
||||
struct bpf_link *
|
||||
bpf_program__attach_cgroup_opts(const struct bpf_program *prog, int cgroup_fd,
|
||||
const struct bpf_cgroup_opts *opts)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_link_create_opts, link_create_opts);
|
||||
__u32 relative_id;
|
||||
int relative_fd;
|
||||
|
||||
if (!OPTS_VALID(opts, bpf_cgroup_opts))
|
||||
return libbpf_err_ptr(-EINVAL);
|
||||
|
||||
relative_id = OPTS_GET(opts, relative_id, 0);
|
||||
relative_fd = OPTS_GET(opts, relative_fd, 0);
|
||||
|
||||
if (relative_fd && relative_id) {
|
||||
pr_warn("prog '%s': relative_fd and relative_id cannot be set at the same time\n",
|
||||
prog->name);
|
||||
return libbpf_err_ptr(-EINVAL);
|
||||
}
|
||||
|
||||
link_create_opts.cgroup.expected_revision = OPTS_GET(opts, expected_revision, 0);
|
||||
link_create_opts.cgroup.relative_fd = relative_fd;
|
||||
link_create_opts.cgroup.relative_id = relative_id;
|
||||
link_create_opts.flags = OPTS_GET(opts, flags, 0);
|
||||
|
||||
return bpf_program_attach_fd(prog, cgroup_fd, "cgroup", &link_create_opts);
|
||||
}
|
||||
|
||||
struct bpf_link *
|
||||
bpf_program__attach_tcx(const struct bpf_program *prog, int ifindex,
|
||||
const struct bpf_tcx_opts *opts)
|
||||
|
|
|
|||
|
|
@ -877,6 +877,21 @@ LIBBPF_API struct bpf_link *
|
|||
bpf_program__attach_netkit(const struct bpf_program *prog, int ifindex,
|
||||
const struct bpf_netkit_opts *opts);
|
||||
|
||||
struct bpf_cgroup_opts {
|
||||
/* size of this struct, for forward/backward compatibility */
|
||||
size_t sz;
|
||||
__u32 flags;
|
||||
__u32 relative_fd;
|
||||
__u32 relative_id;
|
||||
__u64 expected_revision;
|
||||
size_t :0;
|
||||
};
|
||||
#define bpf_cgroup_opts__last_field expected_revision
|
||||
|
||||
LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_cgroup_opts(const struct bpf_program *prog, int cgroup_fd,
|
||||
const struct bpf_cgroup_opts *opts);
|
||||
|
||||
struct bpf_map;
|
||||
|
||||
LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map);
|
||||
|
|
|
|||
|
|
@ -437,6 +437,7 @@ LIBBPF_1.6.0 {
|
|||
bpf_linker__add_fd;
|
||||
bpf_linker__new_fd;
|
||||
bpf_object__prepare;
|
||||
bpf_program__attach_cgroup_opts;
|
||||
bpf_program__func_info;
|
||||
bpf_program__func_info_cnt;
|
||||
bpf_program__line_info;
|
||||
|
|
|
|||
617
tools/testing/selftests/bpf/prog_tests/cgroup_mprog_opts.c
Normal file
617
tools/testing/selftests/bpf/prog_tests/cgroup_mprog_opts.c
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
|
||||
#include <test_progs.h>
|
||||
#include "cgroup_helpers.h"
|
||||
#include "cgroup_mprog.skel.h"
|
||||
|
||||
static void assert_mprog_count(int cg, int atype, int expected)
|
||||
{
|
||||
__u32 count = 0, attach_flags = 0;
|
||||
int err;
|
||||
|
||||
err = bpf_prog_query(cg, atype, 0, &attach_flags,
|
||||
NULL, &count);
|
||||
ASSERT_EQ(count, expected, "count");
|
||||
ASSERT_EQ(err, 0, "prog_query");
|
||||
}
|
||||
|
||||
static void test_prog_attach_detach(int atype)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_attach_opts, opta);
|
||||
LIBBPF_OPTS(bpf_prog_detach_opts, optd);
|
||||
LIBBPF_OPTS(bpf_prog_query_opts, optq);
|
||||
__u32 fd1, fd2, fd3, fd4, id1, id2, id3, id4;
|
||||
struct cgroup_mprog *skel;
|
||||
__u32 prog_ids[10];
|
||||
int cg, err;
|
||||
|
||||
cg = test__join_cgroup("/prog_attach_detach");
|
||||
if (!ASSERT_GE(cg, 0, "join_cgroup /prog_attach_detach"))
|
||||
return;
|
||||
|
||||
skel = cgroup_mprog__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
fd1 = bpf_program__fd(skel->progs.getsockopt_1);
|
||||
fd2 = bpf_program__fd(skel->progs.getsockopt_2);
|
||||
fd3 = bpf_program__fd(skel->progs.getsockopt_3);
|
||||
fd4 = bpf_program__fd(skel->progs.getsockopt_4);
|
||||
|
||||
id1 = id_from_prog_fd(fd1);
|
||||
id2 = id_from_prog_fd(fd2);
|
||||
id3 = id_from_prog_fd(fd3);
|
||||
id4 = id_from_prog_fd(fd4);
|
||||
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_BEFORE | BPF_F_AFTER,
|
||||
.expected_revision = 1,
|
||||
);
|
||||
|
||||
/* ordering: [fd1] */
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup;
|
||||
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_BEFORE,
|
||||
.expected_revision = 2,
|
||||
);
|
||||
|
||||
/* ordering: [fd2, fd1] */
|
||||
err = bpf_prog_attach_opts(fd2, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup1;
|
||||
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_AFTER,
|
||||
.relative_fd = fd2,
|
||||
.expected_revision = 3,
|
||||
);
|
||||
|
||||
/* ordering: [fd2, fd3, fd1] */
|
||||
err = bpf_prog_attach_opts(fd3, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup2;
|
||||
|
||||
assert_mprog_count(cg, atype, 3);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI,
|
||||
.expected_revision = 4,
|
||||
);
|
||||
|
||||
/* ordering: [fd2, fd3, fd1, fd4] */
|
||||
err = bpf_prog_attach_opts(fd4, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup3;
|
||||
|
||||
assert_mprog_count(cg, atype, 4);
|
||||
|
||||
/* retrieve optq.prog_cnt */
|
||||
err = bpf_prog_query_opts(cg, atype, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup4;
|
||||
|
||||
/* optq.prog_cnt will be used in below query */
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
optq.prog_ids = prog_ids;
|
||||
err = bpf_prog_query_opts(cg, atype, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup4;
|
||||
|
||||
ASSERT_EQ(optq.count, 4, "count");
|
||||
ASSERT_EQ(optq.revision, 5, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], id2, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], id3, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.prog_ids[2], id1, "prog_ids[2]");
|
||||
ASSERT_EQ(optq.prog_ids[3], id4, "prog_ids[3]");
|
||||
ASSERT_EQ(optq.prog_ids[4], 0, "prog_ids[4]");
|
||||
ASSERT_EQ(optq.link_ids, NULL, "link_ids");
|
||||
|
||||
cleanup4:
|
||||
optd.expected_revision = 5;
|
||||
err = bpf_prog_detach_opts(fd4, cg, atype, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count(cg, atype, 3);
|
||||
|
||||
cleanup3:
|
||||
LIBBPF_OPTS_RESET(optd);
|
||||
err = bpf_prog_detach_opts(fd3, cg, atype, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
/* Check revision after two detach operations */
|
||||
err = bpf_prog_query_opts(cg, atype, &optq);
|
||||
ASSERT_OK(err, "prog_query");
|
||||
ASSERT_EQ(optq.revision, 7, "revision");
|
||||
|
||||
cleanup2:
|
||||
err = bpf_prog_detach_opts(fd2, cg, atype, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
cleanup1:
|
||||
err = bpf_prog_detach_opts(fd1, cg, atype, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
cleanup:
|
||||
cgroup_mprog__destroy(skel);
|
||||
close(cg);
|
||||
}
|
||||
|
||||
static void test_link_attach_detach(int atype)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_cgroup_opts, opta);
|
||||
LIBBPF_OPTS(bpf_cgroup_opts, optd);
|
||||
LIBBPF_OPTS(bpf_prog_query_opts, optq);
|
||||
struct bpf_link *link1, *link2, *link3, *link4;
|
||||
__u32 fd1, fd2, fd3, fd4, id1, id2, id3, id4;
|
||||
struct cgroup_mprog *skel;
|
||||
__u32 prog_ids[10];
|
||||
int cg, err;
|
||||
|
||||
cg = test__join_cgroup("/link_attach_detach");
|
||||
if (!ASSERT_GE(cg, 0, "join_cgroup /link_attach_detach"))
|
||||
return;
|
||||
|
||||
skel = cgroup_mprog__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
fd1 = bpf_program__fd(skel->progs.getsockopt_1);
|
||||
fd2 = bpf_program__fd(skel->progs.getsockopt_2);
|
||||
fd3 = bpf_program__fd(skel->progs.getsockopt_3);
|
||||
fd4 = bpf_program__fd(skel->progs.getsockopt_4);
|
||||
|
||||
id1 = id_from_prog_fd(fd1);
|
||||
id2 = id_from_prog_fd(fd2);
|
||||
id3 = id_from_prog_fd(fd3);
|
||||
id4 = id_from_prog_fd(fd4);
|
||||
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.expected_revision = 1,
|
||||
);
|
||||
|
||||
/* ordering: [fd1] */
|
||||
link1 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_1, cg, &opta);
|
||||
if (!ASSERT_OK_PTR(link1, "link_attach"))
|
||||
goto cleanup;
|
||||
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_BEFORE | BPF_F_LINK,
|
||||
.relative_id = id_from_link_fd(bpf_link__fd(link1)),
|
||||
.expected_revision = 2,
|
||||
);
|
||||
|
||||
/* ordering: [fd2, fd1] */
|
||||
link2 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_2, cg, &opta);
|
||||
if (!ASSERT_OK_PTR(link2, "link_attach"))
|
||||
goto cleanup1;
|
||||
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_AFTER | BPF_F_LINK,
|
||||
.relative_fd = bpf_link__fd(link2),
|
||||
.expected_revision = 3,
|
||||
);
|
||||
|
||||
/* ordering: [fd2, fd3, fd1] */
|
||||
link3 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_3, cg, &opta);
|
||||
if (!ASSERT_OK_PTR(link3, "link_attach"))
|
||||
goto cleanup2;
|
||||
|
||||
assert_mprog_count(cg, atype, 3);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.expected_revision = 4,
|
||||
);
|
||||
|
||||
/* ordering: [fd2, fd3, fd1, fd4] */
|
||||
link4 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_4, cg, &opta);
|
||||
if (!ASSERT_OK_PTR(link4, "link_attach"))
|
||||
goto cleanup3;
|
||||
|
||||
assert_mprog_count(cg, atype, 4);
|
||||
|
||||
/* retrieve optq.prog_cnt */
|
||||
err = bpf_prog_query_opts(cg, atype, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup4;
|
||||
|
||||
/* optq.prog_cnt will be used in below query */
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
optq.prog_ids = prog_ids;
|
||||
err = bpf_prog_query_opts(cg, atype, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup4;
|
||||
|
||||
ASSERT_EQ(optq.count, 4, "count");
|
||||
ASSERT_EQ(optq.revision, 5, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], id2, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], id3, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.prog_ids[2], id1, "prog_ids[2]");
|
||||
ASSERT_EQ(optq.prog_ids[3], id4, "prog_ids[3]");
|
||||
ASSERT_EQ(optq.prog_ids[4], 0, "prog_ids[4]");
|
||||
ASSERT_EQ(optq.link_ids, NULL, "link_ids");
|
||||
|
||||
cleanup4:
|
||||
bpf_link__destroy(link4);
|
||||
assert_mprog_count(cg, atype, 3);
|
||||
|
||||
cleanup3:
|
||||
bpf_link__destroy(link3);
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
/* Check revision after two detach operations */
|
||||
err = bpf_prog_query_opts(cg, atype, &optq);
|
||||
ASSERT_OK(err, "prog_query");
|
||||
ASSERT_EQ(optq.revision, 7, "revision");
|
||||
|
||||
cleanup2:
|
||||
bpf_link__destroy(link2);
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
cleanup1:
|
||||
bpf_link__destroy(link1);
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
cleanup:
|
||||
cgroup_mprog__destroy(skel);
|
||||
close(cg);
|
||||
}
|
||||
|
||||
static void test_preorder_prog_attach_detach(int atype)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_attach_opts, opta);
|
||||
LIBBPF_OPTS(bpf_prog_detach_opts, optd);
|
||||
__u32 fd1, fd2, fd3, fd4;
|
||||
struct cgroup_mprog *skel;
|
||||
int cg, err;
|
||||
|
||||
cg = test__join_cgroup("/preorder_prog_attach_detach");
|
||||
if (!ASSERT_GE(cg, 0, "join_cgroup /preorder_prog_attach_detach"))
|
||||
return;
|
||||
|
||||
skel = cgroup_mprog__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
fd1 = bpf_program__fd(skel->progs.getsockopt_1);
|
||||
fd2 = bpf_program__fd(skel->progs.getsockopt_2);
|
||||
fd3 = bpf_program__fd(skel->progs.getsockopt_3);
|
||||
fd4 = bpf_program__fd(skel->progs.getsockopt_4);
|
||||
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI,
|
||||
.expected_revision = 1,
|
||||
);
|
||||
|
||||
/* ordering: [fd1] */
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup;
|
||||
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_PREORDER,
|
||||
.expected_revision = 2,
|
||||
);
|
||||
|
||||
/* ordering: [fd1, fd2] */
|
||||
err = bpf_prog_attach_opts(fd2, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup1;
|
||||
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_AFTER,
|
||||
.relative_fd = fd2,
|
||||
.expected_revision = 3,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd3, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, -EINVAL, "prog_attach"))
|
||||
goto cleanup2;
|
||||
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_AFTER | BPF_F_PREORDER,
|
||||
.relative_fd = fd2,
|
||||
.expected_revision = 3,
|
||||
);
|
||||
|
||||
/* ordering: [fd1, fd2, fd3] */
|
||||
err = bpf_prog_attach_opts(fd3, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup2;
|
||||
|
||||
assert_mprog_count(cg, atype, 3);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI,
|
||||
.expected_revision = 4,
|
||||
);
|
||||
|
||||
/* ordering: [fd2, fd3, fd1, fd4] */
|
||||
err = bpf_prog_attach_opts(fd4, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup3;
|
||||
|
||||
assert_mprog_count(cg, atype, 4);
|
||||
|
||||
err = bpf_prog_detach_opts(fd4, cg, atype, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count(cg, atype, 3);
|
||||
|
||||
cleanup3:
|
||||
err = bpf_prog_detach_opts(fd3, cg, atype, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
cleanup2:
|
||||
err = bpf_prog_detach_opts(fd2, cg, atype, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
cleanup1:
|
||||
err = bpf_prog_detach_opts(fd1, cg, atype, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
cleanup:
|
||||
cgroup_mprog__destroy(skel);
|
||||
close(cg);
|
||||
}
|
||||
|
||||
static void test_preorder_link_attach_detach(int atype)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_cgroup_opts, opta);
|
||||
struct bpf_link *link1, *link2, *link3, *link4;
|
||||
struct cgroup_mprog *skel;
|
||||
__u32 fd2;
|
||||
int cg;
|
||||
|
||||
cg = test__join_cgroup("/preorder_link_attach_detach");
|
||||
if (!ASSERT_GE(cg, 0, "join_cgroup /preorder_link_attach_detach"))
|
||||
return;
|
||||
|
||||
skel = cgroup_mprog__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
fd2 = bpf_program__fd(skel->progs.getsockopt_2);
|
||||
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.expected_revision = 1,
|
||||
);
|
||||
|
||||
/* ordering: [fd1] */
|
||||
link1 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_1, cg, &opta);
|
||||
if (!ASSERT_OK_PTR(link1, "link_attach"))
|
||||
goto cleanup;
|
||||
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_PREORDER,
|
||||
.expected_revision = 2,
|
||||
);
|
||||
|
||||
/* ordering: [fd1, fd2] */
|
||||
link2 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_2, cg, &opta);
|
||||
if (!ASSERT_OK_PTR(link2, "link_attach"))
|
||||
goto cleanup1;
|
||||
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_AFTER,
|
||||
.relative_fd = fd2,
|
||||
.expected_revision = 3,
|
||||
);
|
||||
|
||||
link3 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_3, cg, &opta);
|
||||
if (!ASSERT_ERR_PTR(link3, "link_attach"))
|
||||
goto cleanup2;
|
||||
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_AFTER | BPF_F_PREORDER | BPF_F_LINK,
|
||||
.relative_fd = bpf_link__fd(link2),
|
||||
.expected_revision = 3,
|
||||
);
|
||||
|
||||
/* ordering: [fd1, fd2, fd3] */
|
||||
link3 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_3, cg, &opta);
|
||||
if (!ASSERT_OK_PTR(link3, "link_attach"))
|
||||
goto cleanup2;
|
||||
|
||||
assert_mprog_count(cg, atype, 3);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.expected_revision = 4,
|
||||
);
|
||||
|
||||
/* ordering: [fd2, fd3, fd1, fd4] */
|
||||
link4 = bpf_program__attach_cgroup_opts(skel->progs.getsockopt_4, cg, &opta);
|
||||
if (!ASSERT_OK_PTR(link4, "prog_attach"))
|
||||
goto cleanup3;
|
||||
|
||||
assert_mprog_count(cg, atype, 4);
|
||||
|
||||
bpf_link__destroy(link4);
|
||||
assert_mprog_count(cg, atype, 3);
|
||||
|
||||
cleanup3:
|
||||
bpf_link__destroy(link3);
|
||||
assert_mprog_count(cg, atype, 2);
|
||||
|
||||
cleanup2:
|
||||
bpf_link__destroy(link2);
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
cleanup1:
|
||||
bpf_link__destroy(link1);
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
cleanup:
|
||||
cgroup_mprog__destroy(skel);
|
||||
close(cg);
|
||||
}
|
||||
|
||||
static void test_invalid_attach_detach(int atype)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_attach_opts, opta);
|
||||
__u32 fd1, fd2, id2;
|
||||
struct cgroup_mprog *skel;
|
||||
int cg, err;
|
||||
|
||||
cg = test__join_cgroup("/invalid_attach_detach");
|
||||
if (!ASSERT_GE(cg, 0, "join_cgroup /invalid_attach_detach"))
|
||||
return;
|
||||
|
||||
skel = cgroup_mprog__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
fd1 = bpf_program__fd(skel->progs.getsockopt_1);
|
||||
fd2 = bpf_program__fd(skel->progs.getsockopt_2);
|
||||
|
||||
id2 = id_from_prog_fd(fd2);
|
||||
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_BEFORE | BPF_F_AFTER,
|
||||
.relative_id = id2,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -EINVAL, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_BEFORE | BPF_F_ID,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -ENOENT, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_AFTER | BPF_F_ID,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -ENOENT, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_BEFORE | BPF_F_AFTER,
|
||||
.relative_id = id2,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -EINVAL, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_LINK,
|
||||
.relative_id = id2,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -EINVAL, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI,
|
||||
.relative_id = id2,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -EINVAL, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_BEFORE,
|
||||
.relative_fd = fd1,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -ENOENT, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_AFTER,
|
||||
.relative_fd = fd1,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -ENOENT, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 0);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup;
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_AFTER,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -EINVAL, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_ALLOW_MULTI | BPF_F_REPLACE | BPF_F_AFTER,
|
||||
.replace_prog_fd = fd1,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, cg, atype, &opta);
|
||||
ASSERT_EQ(err, -EINVAL, "prog_attach");
|
||||
assert_mprog_count(cg, atype, 1);
|
||||
cleanup:
|
||||
cgroup_mprog__destroy(skel);
|
||||
close(cg);
|
||||
}
|
||||
|
||||
void test_cgroup_mprog_opts(void)
|
||||
{
|
||||
if (test__start_subtest("prog_attach_detach"))
|
||||
test_prog_attach_detach(BPF_CGROUP_GETSOCKOPT);
|
||||
if (test__start_subtest("link_attach_detach"))
|
||||
test_link_attach_detach(BPF_CGROUP_GETSOCKOPT);
|
||||
if (test__start_subtest("preorder_prog_attach_detach"))
|
||||
test_preorder_prog_attach_detach(BPF_CGROUP_GETSOCKOPT);
|
||||
if (test__start_subtest("preorder_link_attach_detach"))
|
||||
test_preorder_link_attach_detach(BPF_CGROUP_GETSOCKOPT);
|
||||
if (test__start_subtest("invalid_attach_detach"))
|
||||
test_invalid_attach_detach(BPF_CGROUP_GETSOCKOPT);
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
|
||||
#include <test_progs.h>
|
||||
#include "cgroup_helpers.h"
|
||||
#include "cgroup_preorder.skel.h"
|
||||
|
||||
static int run_getsockopt_test(int cg_parent, int sock_fd, bool has_relative_fd)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_attach_opts, opts);
|
||||
enum bpf_attach_type prog_p_atype, prog_p2_atype;
|
||||
int prog_p_fd, prog_p2_fd;
|
||||
struct cgroup_preorder *skel = NULL;
|
||||
struct bpf_program *prog;
|
||||
__u8 *result, buf;
|
||||
socklen_t optlen;
|
||||
int err = 0;
|
||||
|
||||
skel = cgroup_preorder__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "cgroup_preorder__open_and_load"))
|
||||
return 0;
|
||||
|
||||
LIBBPF_OPTS_RESET(opts);
|
||||
opts.flags = BPF_F_ALLOW_MULTI;
|
||||
prog = skel->progs.parent;
|
||||
prog_p_fd = bpf_program__fd(prog);
|
||||
prog_p_atype = bpf_program__expected_attach_type(prog);
|
||||
err = bpf_prog_attach_opts(prog_p_fd, cg_parent, prog_p_atype, &opts);
|
||||
if (!ASSERT_OK(err, "bpf_prog_attach_opts-parent"))
|
||||
goto close_skel;
|
||||
|
||||
opts.flags = BPF_F_ALLOW_MULTI | BPF_F_BEFORE;
|
||||
if (has_relative_fd)
|
||||
opts.relative_fd = prog_p_fd;
|
||||
prog = skel->progs.parent_2;
|
||||
prog_p2_fd = bpf_program__fd(prog);
|
||||
prog_p2_atype = bpf_program__expected_attach_type(prog);
|
||||
err = bpf_prog_attach_opts(prog_p2_fd, cg_parent, prog_p2_atype, &opts);
|
||||
if (!ASSERT_OK(err, "bpf_prog_attach_opts-parent_2"))
|
||||
goto detach_parent;
|
||||
|
||||
err = getsockopt(sock_fd, SOL_IP, IP_TOS, &buf, &optlen);
|
||||
if (!ASSERT_OK(err, "getsockopt"))
|
||||
goto detach_parent_2;
|
||||
|
||||
result = skel->bss->result;
|
||||
ASSERT_TRUE(result[0] == 4 && result[1] == 3, "result values");
|
||||
|
||||
detach_parent_2:
|
||||
ASSERT_OK(bpf_prog_detach2(prog_p2_fd, cg_parent, prog_p2_atype),
|
||||
"bpf_prog_detach2-parent_2");
|
||||
detach_parent:
|
||||
ASSERT_OK(bpf_prog_detach2(prog_p_fd, cg_parent, prog_p_atype),
|
||||
"bpf_prog_detach2-parent");
|
||||
close_skel:
|
||||
cgroup_preorder__destroy(skel);
|
||||
return err;
|
||||
}
|
||||
|
||||
void test_cgroup_mprog_ordering(void)
|
||||
{
|
||||
int cg_parent = -1, sock_fd = -1;
|
||||
|
||||
cg_parent = test__join_cgroup("/parent");
|
||||
if (!ASSERT_GE(cg_parent, 0, "join_cgroup /parent"))
|
||||
goto out;
|
||||
|
||||
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (!ASSERT_GE(sock_fd, 0, "socket"))
|
||||
goto out;
|
||||
|
||||
ASSERT_OK(run_getsockopt_test(cg_parent, sock_fd, false), "getsockopt_test_1");
|
||||
ASSERT_OK(run_getsockopt_test(cg_parent, sock_fd, true), "getsockopt_test_2");
|
||||
|
||||
out:
|
||||
close(sock_fd);
|
||||
close(cg_parent);
|
||||
}
|
||||
|
|
@ -8,34 +8,6 @@
|
|||
# define loopback 1
|
||||
#endif
|
||||
|
||||
static inline __u32 id_from_prog_fd(int fd)
|
||||
{
|
||||
struct bpf_prog_info prog_info = {};
|
||||
__u32 prog_info_len = sizeof(prog_info);
|
||||
int err;
|
||||
|
||||
err = bpf_obj_get_info_by_fd(fd, &prog_info, &prog_info_len);
|
||||
if (!ASSERT_OK(err, "id_from_prog_fd"))
|
||||
return 0;
|
||||
|
||||
ASSERT_NEQ(prog_info.id, 0, "prog_info.id");
|
||||
return prog_info.id;
|
||||
}
|
||||
|
||||
static inline __u32 id_from_link_fd(int fd)
|
||||
{
|
||||
struct bpf_link_info link_info = {};
|
||||
__u32 link_info_len = sizeof(link_info);
|
||||
int err;
|
||||
|
||||
err = bpf_link_get_info_by_fd(fd, &link_info, &link_info_len);
|
||||
if (!ASSERT_OK(err, "id_from_link_fd"))
|
||||
return 0;
|
||||
|
||||
ASSERT_NEQ(link_info.id, 0, "link_info.id");
|
||||
return link_info.id;
|
||||
}
|
||||
|
||||
static inline __u32 ifindex_from_link_fd(int fd)
|
||||
{
|
||||
struct bpf_link_info link_info = {};
|
||||
|
|
|
|||
30
tools/testing/selftests/bpf/progs/cgroup_mprog.c
Normal file
30
tools/testing/selftests/bpf/progs/cgroup_mprog.c
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
SEC("cgroup/getsockopt")
|
||||
int getsockopt_1(struct bpf_sockopt *ctx)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
SEC("cgroup/getsockopt")
|
||||
int getsockopt_2(struct bpf_sockopt *ctx)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
SEC("cgroup/getsockopt")
|
||||
int getsockopt_3(struct bpf_sockopt *ctx)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
SEC("cgroup/getsockopt")
|
||||
int getsockopt_4(struct bpf_sockopt *ctx)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -460,6 +460,34 @@ static inline void *u64_to_ptr(__u64 ptr)
|
|||
return (void *) (unsigned long) ptr;
|
||||
}
|
||||
|
||||
static inline __u32 id_from_prog_fd(int fd)
|
||||
{
|
||||
struct bpf_prog_info prog_info = {};
|
||||
__u32 prog_info_len = sizeof(prog_info);
|
||||
int err;
|
||||
|
||||
err = bpf_obj_get_info_by_fd(fd, &prog_info, &prog_info_len);
|
||||
if (!ASSERT_OK(err, "id_from_prog_fd"))
|
||||
return 0;
|
||||
|
||||
ASSERT_NEQ(prog_info.id, 0, "prog_info.id");
|
||||
return prog_info.id;
|
||||
}
|
||||
|
||||
static inline __u32 id_from_link_fd(int fd)
|
||||
{
|
||||
struct bpf_link_info link_info = {};
|
||||
__u32 link_info_len = sizeof(link_info);
|
||||
int err;
|
||||
|
||||
err = bpf_link_get_info_by_fd(fd, &link_info, &link_info_len);
|
||||
if (!ASSERT_OK(err, "id_from_link_fd"))
|
||||
return 0;
|
||||
|
||||
ASSERT_NEQ(link_info.id, 0, "link_info.id");
|
||||
return link_info.id;
|
||||
}
|
||||
|
||||
int bpf_find_map(const char *test, struct bpf_object *obj, const char *name);
|
||||
int compare_map_keys(int map1_fd, int map2_fd);
|
||||
int compare_stack_ips(int smap_fd, int amap_fd, int stack_trace_len);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user