From 5f8ba20d7f15b2d8b7d2596a1125412047b4e995 Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Wed, 16 Sep 2020 15:58:04 -0700 Subject: [PATCH] core: add bpf-foreign unit helpers - Introduce support of cgroup-bpf programs managed (i.e. compiled, loaded to and unloaded from kernel) externally. Systemd is only responsible for attaching programs to unit cgroup hence the name 'foreign'. Foreign BPF programs are identified by bpf program ID and attach type. systemd: - Gets kernel FD of BPF program; - Makes a unique identifier of BPF program from BPF attach type and program ID. Same program IDs mean the same program, i.e the same chunk of kernel memory. Even if the same program is passed multiple times, identical (program_id, attach_type) instances are collapsed into one; - Attaches programs to unit cgroup. --- src/core/bpf-foreign.c | 151 +++++++++++++++++++++++++++++++++++++++++ src/core/bpf-foreign.h | 12 ++++ src/core/meson.build | 2 + src/core/unit.c | 3 + src/core/unit.h | 4 ++ 5 files changed, 172 insertions(+) create mode 100644 src/core/bpf-foreign.c create mode 100644 src/core/bpf-foreign.h 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