Re: [PATCH bpf-next v5 4/7] libbpf: Add support for associating BPF program with struct_ops

Andrii Nakryiko
2025-11-04T23:27:09Z

On Tue, Nov 4, 2025 at 9:27 AM Amery Hung <ameryhung@gmail.com> wrote:
>
> Add low-level wrapper and libbpf API for BPF_PROG_ASSOC_STRUCT_OPS
> command in the bpf() syscall.
>
> Signed-off-by: Amery Hung <ameryhung@gmail.com>
> ---
> tools/lib/bpf/bpf.c | 19 +++++++++++++++++++
> tools/lib/bpf/bpf.h | 21 +++++++++++++++++++++
> tools/lib/bpf/libbpf.c | 30 ++++++++++++++++++++++++++++++
> tools/lib/bpf/libbpf.h | 16 ++++++++++++++++
> tools/lib/bpf/libbpf.map | 2 ++
> 5 files changed, 88 insertions(+)
>
> diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
> index b66f5fbfbbb2..21b57a629916 100644
> --- a/tools/lib/bpf/bpf.c
> +++ b/tools/lib/bpf/bpf.c
> @@ -1397,3 +1397,22 @@ int bpf_prog_stream_read(int prog_fd, __u32 stream_id, void *buf, __u32 buf_len,
> err = sys_bpf(BPF_PROG_STREAM_READ_BY_FD, &attr, attr_sz);
> return libbpf_err_errno(err);
> }
> +
> +int bpf_prog_assoc_struct_ops(int prog_fd, int map_fd,
> + struct bpf_prog_assoc_struct_ops_opts *opts)
> +{
> + const size_t attr_sz = offsetofend(union bpf_attr, prog_assoc_struct_ops);
> + union bpf_attr attr;
> + int err;
> +
> + if (!OPTS_VALID(opts, bpf_prog_assoc_struct_ops_opts))
> + return libbpf_err(-EINVAL);
> +
> + memset(&attr, 0, attr_sz);
> + attr.prog_assoc_struct_ops.map_fd = map_fd;
> + attr.prog_assoc_struct_ops.prog_fd = prog_fd;
> + attr.prog_assoc_struct_ops.flags = OPTS_GET(opts, flags, 0);
> +
> + err = sys_bpf(BPF_PROG_ASSOC_STRUCT_OPS, &attr, attr_sz);
> + return libbpf_err_errno(err);
> +}
> diff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h
> index e983a3e40d61..1f9c28d27795 100644
> --- a/tools/lib/bpf/bpf.h
> +++ b/tools/lib/bpf/bpf.h
> @@ -733,6 +733,27 @@ struct bpf_prog_stream_read_opts {
> LIBBPF_API int bpf_prog_stream_read(int prog_fd, __u32 stream_id, void *buf, __u32 buf_len,
> struct bpf_prog_stream_read_opts *opts);
>
> +struct bpf_prog_assoc_struct_ops_opts {
> + size_t sz;
> + __u32 flags;
> + size_t :0;
> +};
> +#define bpf_prog_assoc_struct_ops_opts__last_field flags
> +
> +/**
> + * @brief **bpf_prog_assoc_struct_ops** associates a BPF program with a
> + * struct_ops map.
> + *
> + * @param prog_fd FD for the BPF program
> + * @param map_fd FD for the struct_ops map to be associated with the BPF program
> + * @param opts optional options, can be NULL
> + *
> + * @return 0 on success; negative error code, otherwise (errno is also set to
> + * the error code)
> + */
> +LIBBPF_API int bpf_prog_assoc_struct_ops(int prog_fd, int map_fd,
> + struct bpf_prog_assoc_struct_ops_opts *opts);
> +
> #ifdef __cplusplus
> } /* extern "C" */
> #endif
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index fbe74686c97d..260e1feaa665 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -13891,6 +13891,36 @@ int bpf_program__set_attach_target(struct bpf_program *prog,
> return 0;
> }
>
> +int bpf_program__assoc_struct_ops(struct bpf_program *prog, struct bpf_map *map,
> + struct bpf_prog_assoc_struct_ops_opts *opts)
> +{
> + int prog_fd;
> +
> + prog_fd = bpf_program__fd(prog);
> + if (prog_fd < 0) {
> + pr_warn("prog '%s': can't associate BPF program without FD (was it loaded?)\n",
> + prog->name);
> + return -EINVAL;
> + }
> +
> + if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) {
> + pr_warn("prog '%s': can't associate struct_ops program\n", prog->name);
> + return -EINVAL;
> + }
> +
> + if (map->fd < 0) {

heh, this is a bug. we use create_placeholder_fd() to create fixed FDs
associated with maps, and later we replace them with the real
underlying BPF map kernel objects. It's all details, but the point is
that this won't detect map that wasn't created. Use bpf_map__fd()
instead, it handles that correctly.

> + pr_warn("map '%s': can't associate BPF map without FD (was it created?)\n", map->name);
> + return -EINVAL;
> + }
> +
> + if (!bpf_map__is_struct_ops(map)) {
> + pr_warn("map '%s': can't associate non-struct_ops map\n", map->name);
> + return -EINVAL;
> + }
> +
> + return bpf_prog_assoc_struct_ops(prog_fd, map->fd, opts);
> +}
> +
> int parse_cpu_mask_str(const char *s, bool **mask, int *mask_sz)
> {
> int err = 0, n, len, start, end = -1;
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index 5118d0a90e24..45720b7c2aaa 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -1003,6 +1003,22 @@ LIBBPF_API int
> bpf_program__set_attach_target(struct bpf_program *prog, int attach_prog_fd,
> const char *attach_func_name);
>
> +struct bpf_prog_assoc_struct_ops_opts; /* defined in bpf.h */
> +
> +/**
> + * @brief **bpf_program__assoc_struct_ops()** associates a BPF program with a
> + * struct_ops map.
> + *
> + * @param prog BPF program
> + * @param map struct_ops map to be associated with the BPF program
> + * @param opts optional options, can be NULL
> + *
> + * @return error code; or 0 if no error occurred.

we normally specify returns like so:

@return 0, on success; negative error code, otherwise

keep it consistent?

> + */
> +LIBBPF_API int
> +bpf_program__assoc_struct_ops(struct bpf_program *prog, struct bpf_map *map,
> + struct bpf_prog_assoc_struct_ops_opts *opts);
> +
> /**
> * @brief **bpf_object__find_map_by_name()** returns BPF map of
> * the given name, if it exists within the passed BPF object
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index 8ed8749907d4..84fb90a016c9 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -451,4 +451,6 @@ LIBBPF_1.7.0 {
> global:
> bpf_map__set_exclusive_program;
> bpf_map__exclusive_program;
> + bpf_prog_assoc_struct_ops;
> + bpf_program__assoc_struct_ops;
> } LIBBPF_1.6.0;
> --
> 2.47.3
>