diff --git a/src/core/bpf-foreign.c b/src/core/bpf-foreign.c new file mode 100644 index 0000000000..98655bda3c --- /dev/null +++ b/src/core/bpf-foreign.c @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "bpf-foreign.h" +#include "bpf-program.h" +#include "cgroup.h" +#include "memory-util.h" +#include "mountpoint-util.h" +#include "set.h" + +typedef struct BPFForeignKey BPFForeignKey; +struct BPFForeignKey { + uint32_t prog_id; + uint32_t attach_type; +}; + +static int bpf_foreign_key_new(uint32_t prog_id, + enum bpf_attach_type attach_type, + BPFForeignKey **ret) { + _cleanup_free_ BPFForeignKey *p = NULL; + + assert(ret); + + p = new(BPFForeignKey, 1); + if (!p) + return log_oom(); + + *p = (BPFForeignKey) { + .prog_id = prog_id, + .attach_type = attach_type, + }; + + *ret = TAKE_PTR(p); + + return 0; +} + +static int bpf_foreign_key_compare_func(const BPFForeignKey *a, const BPFForeignKey *b) { + int r = CMP(a->prog_id, b->prog_id); + if (r != 0) + return r; + + return CMP(a->attach_type, b->attach_type); +} + +static void bpf_foreign_key_hash_func(const BPFForeignKey *p, struct siphash *h) { + siphash24_compress(&p->prog_id, sizeof(p->prog_id), h); + siphash24_compress(&p->attach_type, sizeof(p->attach_type), h); +} + +DEFINE_PRIVATE_HASH_OPS_FULL(bpf_foreign_by_key_hash_ops, + BPFForeignKey, bpf_foreign_key_hash_func, bpf_foreign_key_compare_func, free, + BPFProgram, bpf_program_unref); + +static int attach_programs(Unit *u, const char *path, Hashmap* foreign_by_key, uint32_t attach_flags) { + const BPFForeignKey *key; + BPFProgram *prog; + int r; + + assert(u); + + HASHMAP_FOREACH_KEY(prog, key, foreign_by_key) { + r = bpf_program_cgroup_attach(prog, key->attach_type, path, attach_flags); + if (r < 0) + return log_unit_error_errno(u, r, "Attaching foreign BPF program to cgroup %s failed: %m", path); + } + + return 0; +} + +/* + * Prepare foreign BPF program for installation: + * - Load the program from BPF filesystem to the kernel; + * - Store program FD identified by program ID and attach type in the unit. + */ +static int bpf_foreign_prepare( + Unit *u, + enum bpf_attach_type attach_type, + const char *bpffs_path) { + _cleanup_(bpf_program_unrefp) BPFProgram *prog = NULL; + _cleanup_free_ BPFForeignKey *key = NULL; + uint32_t prog_id; + int r; + + assert(u); + assert(bpffs_path); + + r = bpf_program_new_from_bpffs_path(bpffs_path, &prog); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to create foreign BPFProgram: %m"); + + r = bpf_program_get_id_by_fd(prog->kernel_fd, &prog_id); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to get BPF program id by fd: %m"); + + r = bpf_foreign_key_new(prog_id, attach_type, &key); + if (r < 0) + return log_unit_error_errno(u, r, + "Failed to create foreign BPF program key from path '%s': %m", bpffs_path); + + r = hashmap_ensure_put(&u->bpf_foreign_by_key, &bpf_foreign_by_key_hash_ops, key, prog); + if (r == -EEXIST) { + log_unit_warning_errno(u, r, "Foreign BPF program already exists, ignoring: %m"); + return 0; + } + if (r < 0) + return log_unit_error_errno(u, r, "Failed to put foreign BPFProgram into map: %m"); + + TAKE_PTR(key); + TAKE_PTR(prog); + + return 0; +} + +int bpf_foreign_supported(void) { + int r; + + r = cg_all_unified(); + if (r <= 0) + return r; + + return path_is_mount_point("/sys/fs/bpf", NULL, 0); +} + +int bpf_foreign_install(Unit *u) { + _cleanup_free_ char *cgroup_path = NULL; + CGroupBPFForeignProgram *p; + CGroupContext *cc; + int r; + + assert(u); + + cc = unit_get_cgroup_context(u); + if (!cc) + return 0; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_path); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to get cgroup path: %m"); + + LIST_FOREACH(programs, p, cc->bpf_foreign_programs) { + r = bpf_foreign_prepare(u, p->attach_type, p->bpffs_path); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to prepare foreign BPF hashmap: %m"); + } + + r = attach_programs(u, cgroup_path, u->bpf_foreign_by_key, BPF_F_ALLOW_MULTI); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to install foreign BPF programs: %m"); + + return 0; +} diff --git a/src/core/bpf-foreign.h b/src/core/bpf-foreign.h new file mode 100644 index 0000000000..7704986e3e --- /dev/null +++ b/src/core/bpf-foreign.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#pragma once + +#include "unit.h" + +int bpf_foreign_supported(void); +/* + * Attach cgroup-bpf programs foreign to systemd, i.e. loaded to the kernel by an entity + * external to systemd. + */ +int bpf_foreign_install(Unit *u); diff --git a/src/core/meson.build b/src/core/meson.build index a389c906b3..a1294f3a72 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -11,6 +11,8 @@ libcore_sources = ''' bpf-devices.h bpf-firewall.c bpf-firewall.h + bpf-foreign.c + bpf-foreign.h cgroup.c cgroup.h core-varlink.c diff --git a/src/core/unit.c b/src/core/unit.c index 2c5dc54379..cf83272dcb 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -11,6 +11,7 @@ #include "all-units.h" #include "alloc-util.h" #include "bpf-firewall.h" +#include "bpf-foreign.h" #include "bus-common-errors.h" #include "bus-util.h" #include "cgroup-setup.h" @@ -723,6 +724,8 @@ Unit* unit_free(Unit *u) { set_free(u->ip_bpf_custom_ingress_installed); set_free(u->ip_bpf_custom_egress_installed); + hashmap_free(u->bpf_foreign_by_key); + bpf_program_unref(u->bpf_device_control_installed); condition_free_list(u->conditions); diff --git a/src/core/unit.h b/src/core/unit.h index 6d38e66680..128122b8df 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -305,6 +305,10 @@ typedef struct Unit { Set *ip_bpf_custom_egress; Set *ip_bpf_custom_egress_installed; + /* BPF programs managed (e.g. loaded to kernel) by an entity external to systemd, + * attached to unit cgroup by provided program fd and attach type. */ + Hashmap *bpf_foreign_by_key; + uint64_t ip_accounting_extra[_CGROUP_IP_ACCOUNTING_METRIC_MAX]; /* Low-priority event source which is used to remove watched PIDs that have gone away, and subscribe to any new