diff options
-rw-r--r-- | catalog/systemd.catalog.in | 9 | ||||
-rw-r--r-- | src/network/bpf/sysctl_monitor/meson.build | 25 | ||||
-rw-r--r-- | src/network/bpf/sysctl_monitor/sysctl-monitor-skel.h | 16 | ||||
-rw-r--r-- | src/network/bpf/sysctl_monitor/sysctl-monitor.bpf.c | 134 | ||||
-rw-r--r-- | src/network/bpf/sysctl_monitor/sysctl-write-event.h | 46 | ||||
-rw-r--r-- | src/network/meson.build | 6 | ||||
-rw-r--r-- | src/network/networkd-link.c | 2 | ||||
-rw-r--r-- | src/network/networkd-manager.c | 16 | ||||
-rw-r--r-- | src/network/networkd-manager.h | 5 | ||||
-rw-r--r-- | src/network/networkd-sysctl.c | 191 | ||||
-rw-r--r-- | src/network/networkd-sysctl.h | 10 | ||||
-rw-r--r-- | src/network/networkd.c | 4 | ||||
-rw-r--r-- | src/systemd/sd-messages.h | 3 | ||||
-rw-r--r-- | units/systemd-networkd.service.in | 6 |
14 files changed, 469 insertions, 4 deletions
diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in index 200c98eabe..0a12b7c3f7 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -794,3 +794,12 @@ the TPM. Automatic SRK enrollment on TPMs in such scenarios is not supported. In order to unset the PIN/password protection on the owner hierarchy issue a command like the following: 'tpm2_changeauth -c o -p <OLDPW> ""'. + +-- 9cf56b8baf9546cf9478783a8de42113 +Subject: A foreign process changed a sysctl we manage +Defined-By: systemd +Support: %SUPPORT_URL% + +A sysctl handle under /proc/sys/net, which is managed by systemd-networkd, has been changed by another process. +The event is raised only if the written value differs from the current one. +The program name, the written value, the previous value, and the value initially set by networkd have been logged. diff --git a/src/network/bpf/sysctl_monitor/meson.build b/src/network/bpf/sysctl_monitor/meson.build new file mode 100644 index 0000000000..ac8e81e927 --- /dev/null +++ b/src/network/bpf/sysctl_monitor/meson.build @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if conf.get('HAVE_VMLINUX_H') != 1 + subdir_done() +endif + +sysctl_monitor_bpf_o_unstripped = custom_target( + 'sysctl-monitor.bpf.unstripped.o', + input : 'sysctl-monitor.bpf.c', + output : 'sysctl-monitor.bpf.unstripped.o', + command : bpf_o_unstripped_cmd, + depends : vmlinux_h_dependency) + +sysctl_monitor_bpf_o = custom_target( + 'sysctl-monitor.bpf.o', + input : sysctl_monitor_bpf_o_unstripped, + output : 'sysctl-monitor.bpf.o', + command : bpf_o_cmd) + +sysctl_monitor_skel_h = custom_target( + 'sysctl-monitor.skel.h', + input : sysctl_monitor_bpf_o, + output : 'sysctl-monitor.skel.h', + command : skel_h_cmd, + capture : true) diff --git a/src/network/bpf/sysctl_monitor/sysctl-monitor-skel.h b/src/network/bpf/sysctl_monitor/sysctl-monitor-skel.h new file mode 100644 index 0000000000..d002414521 --- /dev/null +++ b/src/network/bpf/sysctl_monitor/sysctl-monitor-skel.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* The SPDX header above is actually correct in claiming this was + * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that + * compatible with GPL we will claim this to be GPL however, which should be + * fine given that LGPL-2.1-or-later downgrades to GPL if needed. + */ + +#include "bpf-dlopen.h" + +/* libbpf is used via dlopen(), so rename symbols */ +#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton +#define bpf_object__load_skeleton sym_bpf_object__load_skeleton +#define bpf_object__open_skeleton sym_bpf_object__open_skeleton + +#include "bpf/sysctl_monitor/sysctl-monitor.skel.h" diff --git a/src/network/bpf/sysctl_monitor/sysctl-monitor.bpf.c b/src/network/bpf/sysctl_monitor/sysctl-monitor.bpf.c new file mode 100644 index 0000000000..ef154931ce --- /dev/null +++ b/src/network/bpf/sysctl_monitor/sysctl-monitor.bpf.c @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "vmlinux.h" + +#include <bpf/bpf_helpers.h> + +#include "sysctl-write-event.h" + +struct { + __uint(type, BPF_MAP_TYPE_CGROUP_ARRAY); + __type(key, u32); + __type(value, u32); + __uint(max_entries, 1); +} cgroup_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} written_sysctls SEC(".maps"); + +static bool my_streq(const char *s1, const char *s2, size_t l) { + for (size_t i = 0; i < l; i++) { + if (s1[i] != s2[i]) + return false; + if (s1[i] == 0) + return true; + } + return true; +} + +struct str { + char *s; + size_t l; +}; + +static long cut_last(u32 i, struct str *str) { + char *s; + + i = str->l - i - 1; + s = str->s + i; + + /* Sanity check for the preverifier */ + if (i >= str->l) + return 1; + + if (*s == 0) + return 0; + + if (*s == '\n' || *s == '\r' || *s == ' ' || *s == '\t') { + *s = 0; + + return 0; + } + + return 1; +} + +/* Cut off trailing whitespace and newlines */ +static void chop(char *s, size_t l) { + struct str str = { s, l }; + + bpf_loop(l, cut_last, &str, 0); +} + +SEC("cgroup/sysctl") +int sysctl_monitor(struct bpf_sysctl *ctx) { + int r; + + /* Ignore events generated by us */ + if (bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 1; + + /* Allow reads */ + if (!ctx->write) + return 1; + + /* Declare the struct without contextually initializing it. + * This avoid zero-filling the struct, which would be a waste of + * resource and code size. Since we're sending an event even on failure, + * truncate the strings to zero size, in case we don't populate them. */ + struct sysctl_write_event we; + we.version = 1; + we.errorcode = 0; + we.path[0] = 0; + we.comm[0] = 0; + we.current[0] = 0; + we.newvalue[0] = 0; + + /* Set the simple values first */ + we.pid = bpf_get_current_pid_tgid() >> 32; + we.cgroup_id = bpf_get_current_cgroup_id(); + + /* Only monitor /proc/sys/net/ */ + r = bpf_sysctl_get_name(ctx, we.path, sizeof(we.path), 0); + if (r < 0) { + we.errorcode = r; + goto send_event; + } + + if (bpf_strncmp(we.path, 4, "net/") != 0) + return 1; + + r = bpf_get_current_comm(we.comm, sizeof(we.comm)); + if (r < 0) { + we.errorcode = r; + goto send_event; + } + + r = bpf_sysctl_get_current_value(ctx, we.current, sizeof(we.current)); + if (r < 0) { + we.errorcode = r; + goto send_event; + } + + r = bpf_sysctl_get_new_value(ctx, we.newvalue, sizeof(we.newvalue)); + if (r < 0) { + we.errorcode = r; + goto send_event; + } + + /* Both the kernel and userspace applications add a newline at the end, + * remove it from both strings */ + chop(we.current, sizeof(we.current)); + chop(we.newvalue, sizeof(we.newvalue)); + +send_event: + /* If new value differs or we encountered an error, send the event */ + if (r < 0 || !my_streq(we.current, we.newvalue, sizeof(we.current))) + bpf_ringbuf_output(&written_sysctls, &we, sizeof(we), 0); + + return 1; +} + +char _license[] SEC("license") = "GPL"; diff --git a/src/network/bpf/sysctl_monitor/sysctl-write-event.h b/src/network/bpf/sysctl_monitor/sysctl-write-event.h new file mode 100644 index 0000000000..77b71fb4f9 --- /dev/null +++ b/src/network/bpf/sysctl_monitor/sysctl-write-event.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +#ifndef TASK_COMM_LEN +#define TASK_COMM_LEN 16 +#endif + +/* It would be nice to size these members to bigger values, but the stack + * in BPF programs is limited to 512 bytes, and allocating bigger structures + * leads to this compile time error: + * error: Looks like the BPF stack limit is exceeded. + * Please move large on stack variables into BPF per-cpu array map. + * For non-kernel uses, the stack can be increased using -mllvm -bpf-stack-size. */ +struct sysctl_write_event { + /* Used to track changes in the struct layout */ + int version; + + /* Error code returned to userspace to handle eventual failures. */ + int errorcode; + + /* The PID of the process which is writing the sysctl. */ + pid_t pid; + + /* The cgroup id of the process. */ + uint64_t cgroup_id; + + /* The name of the binary. */ + char comm[TASK_COMM_LEN]; + + /* The path of the sysctl, relative to /proc/sys/. + * The longest path observed is 64 bytes: + * net/ipv4/conf/123456789012345/igmpv3_unsolicited_report_interval + * so set it to 100 gives us lot of headroom */ + char path[100]; + + /* The value of the sysctl just before the write. + * The longest value observed is net.core.netdev_rss_key which + * contains 155 bytes, so set it to 160 to have some headroom + * even in this corner case. */ + char current[160]; + + /* The new value being written into the sysctl. + * same sizing as 'current' */ + char newvalue[160]; +}; diff --git a/src/network/meson.build b/src/network/meson.build index 54cf694aeb..73c48e06af 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -1,5 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +subdir('bpf/sysctl_monitor') + sources = files( 'netdev/bareudp.c', 'netdev/batadv.c', @@ -140,6 +142,10 @@ network_generator_sources = files( networkd_network_gperf_gperf = files('networkd-network-gperf.gperf') networkd_netdev_gperf_gperf = files('netdev/netdev-gperf.gperf') +if conf.get('HAVE_VMLINUX_H') == 1 + sources += sysctl_monitor_skel_h +endif + sources += custom_target( 'networkd-gperf.c', input : 'networkd-gperf.gperf', diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 0eeab6e8b0..303007a9de 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -252,6 +252,8 @@ static void link_free_engines(Link *link) { static Link *link_free(Link *link) { assert(link); + (void) sysctl_clear_link_shadows(link); + link_ntp_settings_clear(link); link_dns_settings_clear(link); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 3fdc73d914..6063834a20 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -16,6 +16,7 @@ #include "bus-log-control-api.h" #include "bus-polkit.h" #include "bus-util.h" +#include "capability-util.h" #include "common-signal.h" #include "conf-parser.h" #include "constants.h" @@ -603,6 +604,7 @@ int manager_new(Manager **ret, bool test_mode) { .duid_product_uuid.type = DUID_TYPE_UUID, .dhcp_server_persist_leases = true, .ip_forwarding = { -1, -1, }, + .cgroup_fd = -EBADF, }; *ret = TAKE_PTR(m); @@ -615,6 +617,8 @@ Manager* manager_free(Manager *m) { if (!m) return NULL; + sysctl_remove_monitor(m); + free(m->state_file); HASHMAP_FOREACH(link, m->links_by_index) @@ -694,6 +698,18 @@ int manager_start(Manager *m) { assert(m); + (void) sysctl_add_monitor(m); + + /* Loading BPF programs requires CAP_SYS_ADMIN and CAP_BPF. + * Drop the capabilities here, regardless if the load succeeds or not. */ + r = drop_capability(CAP_SYS_ADMIN); + if (r < 0) + log_warning_errno(r, "Failed to drop CAP_SYS_ADMIN: %m, ignoring."); + + r = drop_capability(CAP_BPF); + if (r < 0) + log_warning_errno(r, "Failed to drop CAP_BPF: %m, ignoring."); + manager_set_sysctl(m); r = manager_request_static_address_labels(m); diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 076cf5e3d6..5a0decced2 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -123,6 +123,11 @@ struct Manager { /* sysctl */ int ip_forwarding[2]; Hashmap *sysctl_shadow; + sd_event_source *sysctl_event_source; + struct ring_buffer *sysctl_buffer; + struct sysctl_monitor_bpf *sysctl_skel; + struct bpf_link *sysctl_link; + int cgroup_fd; }; int manager_new(Manager **ret, bool test_mode); diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 62b0b12680..b85f0ca568 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -4,7 +4,11 @@ #include <linux/if.h> #include <linux/if_arp.h> +#include "sd-messages.h" + #include "af-list.h" +#include "cgroup-util.h" +#include "fd-util.h" #include "missing_network.h" #include "networkd-link.h" #include "networkd-lldp-tx.h" @@ -12,10 +16,197 @@ #include "networkd-ndisc.h" #include "networkd-network.h" #include "networkd-sysctl.h" +#include "path-util.h" #include "socket-util.h" #include "string-table.h" #include "sysctl-util.h" +#if HAVE_VMLINUX_H + +#include "bpf-link.h" + +#include "bpf/sysctl_monitor/sysctl-monitor-skel.h" +#include "bpf/sysctl_monitor/sysctl-write-event.h" + +static struct sysctl_monitor_bpf *sysctl_monitor_bpf_free(struct sysctl_monitor_bpf *obj) { + sysctl_monitor_bpf__destroy(obj); + return NULL; +} + +static struct ring_buffer *rb_free(struct ring_buffer *rb) { + sym_ring_buffer__free(rb); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct sysctl_monitor_bpf *, sysctl_monitor_bpf_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ring_buffer *, rb_free); + +static int sysctl_event_handler(void *ctx, void *data, size_t data_sz) { + struct sysctl_write_event *we = ASSERT_PTR(data); + Hashmap **sysctl_shadow = ASSERT_PTR(ctx); + _cleanup_free_ char *path = NULL; + char *value; + + /* Returning a negative value interrupts the ring buffer polling, + * so do it only in case of a fatal error like a version mismatch. */ + if (we->version != 1) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected sysctl event, disabling sysctl monitoring: %d", we->version); + + if (we->errorcode != 0) { + log_warning_errno(we->errorcode, "Sysctl monitor BPF returned error: %m"); + return 0; + } + + path = path_join("/proc/sys", we->path); + if (!path) { + log_oom(); + return 0; + } + + /* If we never managed this handle, ignore it. */ + value = hashmap_get(*sysctl_shadow, path); + if (!value) + return 0; + + if (!strneq(value, we->newvalue, sizeof(we->newvalue))) + log_struct(LOG_WARNING, + "MESSAGE_ID=" SD_MESSAGE_SYSCTL_CHANGED_STR, + "OBJECT_PID=%d", we->pid, + "OBJECT_COMM=%s", we->comm, + "SYSCTL=/proc/sys/%s", we->path, + "OLDVALUE=%s", we->current, + "NEWVALUE=%s", we->newvalue, + "OURVALUE=%s", value, + LOG_MESSAGE("Foreign process '%s[%d]' changed sysctl '/proc/sys/%s' from '%s' to '%s', conflicting with our setting to '%s'", + we->comm, we->pid, we->path, we->current, we->newvalue, value)); + + return 0; +} + +static int on_ringbuf_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct ring_buffer *rb = ASSERT_PTR(userdata); + int r; + + r = sym_ring_buffer__poll(rb, /* timeout_msec= */ 0); + if (r < 0 && errno != EINTR) + log_error_errno(errno, "Error polling ring buffer: %m"); + + return 0; +} + +int sysctl_add_monitor(Manager *manager) { + _cleanup_(sysctl_monitor_bpf_freep) struct sysctl_monitor_bpf *obj = NULL; + _cleanup_(bpf_link_freep) struct bpf_link *sysctl_link = NULL; + _cleanup_(rb_freep) struct ring_buffer *sysctl_buffer = NULL; + _cleanup_close_ int cgroup_fd = -EBADF, rootcg = -EBADF; + _cleanup_free_ char *cgroup = NULL; + int idx = 0, r; + + assert(manager); + + r = dlopen_bpf(); + if (r < 0) { + log_info_errno(r, "sysctl monitor disabled, as BPF support is not available."); + return 0; + } + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); + if (r < 0) + return log_warning_errno(r, "Failed to get cgroup path, ignoring: %m."); + + rootcg = cg_path_open(SYSTEMD_CGROUP_CONTROLLER, "/"); + if (rootcg < 0) + return log_warning_errno(rootcg, "Failed to open cgroup, ignoring: %m."); + + obj = sysctl_monitor_bpf__open_and_load(); + if (!obj) { + log_info_errno(errno, "Unable to load sysctl monitor BPF program, ignoring: %m."); + return 0; + } + + cgroup_fd = cg_path_open(SYSTEMD_CGROUP_CONTROLLER, cgroup); + if (cgroup_fd < 0) + return log_warning_errno(cgroup_fd, "Failed to open cgroup: %m"); + + if (sym_bpf_map_update_elem(sym_bpf_map__fd(obj->maps.cgroup_map), &idx, &cgroup_fd, BPF_ANY)) + return log_warning_errno(errno, "Failed to update cgroup map: %m"); + + sysctl_link = sym_bpf_program__attach_cgroup(obj->progs.sysctl_monitor, rootcg); + r = bpf_get_error_translated(sysctl_link); + if (r < 0) { + log_info_errno(r, "Unable to attach sysctl monitor BPF program to cgroup, ignoring: %m."); + return 0; + } + + sysctl_buffer = sym_ring_buffer__new( + sym_bpf_map__fd(obj->maps.written_sysctls), + sysctl_event_handler, &manager->sysctl_shadow, NULL); + if (!sysctl_buffer) + return log_warning_errno(errno, "Failed to create ring buffer: %m"); + + r = sd_event_add_io(manager->event, &manager->sysctl_event_source, + sym_ring_buffer__epoll_fd(sysctl_buffer), EPOLLIN, on_ringbuf_io, sysctl_buffer); + if (r < 0) + return log_warning_errno(r, "Failed to watch sysctl event ringbuffer: %m"); + + manager->sysctl_link = TAKE_PTR(sysctl_link); + manager->sysctl_skel = TAKE_PTR(obj); + manager->sysctl_buffer = TAKE_PTR(sysctl_buffer); + manager->cgroup_fd = TAKE_FD(cgroup_fd); + + return 0; +} + +void sysctl_remove_monitor(Manager *manager) { + assert(manager); + + manager->sysctl_event_source = sd_event_source_disable_unref(manager->sysctl_event_source); + + if (manager->sysctl_buffer) { + sym_ring_buffer__free(manager->sysctl_buffer); + manager->sysctl_buffer = NULL; + } + + if (manager->sysctl_link) { + sym_bpf_link__destroy(manager->sysctl_link); + manager->sysctl_link = NULL; + } + + if (manager->sysctl_skel) { + sysctl_monitor_bpf__destroy(manager->sysctl_skel); + manager->sysctl_skel = NULL; + } + + manager->cgroup_fd = safe_close(manager->cgroup_fd); +} + +int sysctl_clear_link_shadows(Link *link) { + _cleanup_free_ char *ipv4 = NULL, *ipv6 = NULL; + char *key = NULL, *value = NULL; + + assert(link); + assert(link->manager); + + ipv4 = path_join("/proc/sys/net/ipv4/conf", link->ifname); + if (!ipv4) + return log_oom(); + + ipv6 = path_join("/proc/sys/net/ipv6/conf", link->ifname); + if (!ipv6) + return log_oom(); + + HASHMAP_FOREACH_KEY(value, key, link->manager->sysctl_shadow) + if (path_startswith(key, ipv4) || path_startswith(key, ipv6)) { + assert_se(hashmap_remove_value(link->manager->sysctl_shadow, key, value) == value); + free(key); + free(value); + } + + return 0; +} +#endif + static void manager_set_ip_forwarding(Manager *manager, int family) { int r, t; diff --git a/src/network/networkd-sysctl.h b/src/network/networkd-sysctl.h index d7a9b1f320..446b835555 100644 --- a/src/network/networkd-sysctl.h +++ b/src/network/networkd-sysctl.h @@ -27,6 +27,16 @@ typedef enum IPReversePathFilter { _IP_REVERSE_PATH_FILTER_INVALID = -EINVAL, } IPReversePathFilter; +#if HAVE_VMLINUX_H +int sysctl_add_monitor(Manager *manager); +void sysctl_remove_monitor(Manager *manager); +int sysctl_clear_link_shadows(Link *link); +#else +static inline int sysctl_add_monitor(Manager *manager) { return 0; } +static inline void sysctl_remove_monitor(Manager *manager) { } +static inline int sysctl_clear_link_shadows(Link *link) { return 0; } +#endif + void manager_set_sysctl(Manager *manager); int link_get_ip_forwarding(Link *link, int family); diff --git a/src/network/networkd.c b/src/network/networkd.c index 69a28647c8..2798cd8cf8 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -62,7 +62,9 @@ static int run(int argc, char *argv[]) { (1ULL << CAP_NET_ADMIN) | (1ULL << CAP_NET_BIND_SERVICE) | (1ULL << CAP_NET_BROADCAST) | - (1ULL << CAP_NET_RAW)); + (1ULL << CAP_NET_RAW) | + (1ULL << CAP_SYS_ADMIN) | + (1ULL << CAP_BPF)); if (r < 0) return log_error_errno(r, "Failed to drop privileges: %m"); } diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index f4f4e95b7f..441f4e6888 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -277,6 +277,9 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION SD_ID128_MAKE(ad,70,89,f9,28,ac,4f,7e,a0,0c,07,45,7d,47,ba,8a) #define SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION_STR SD_ID128_MAKE_STR(ad,70,89,f9,28,ac,4f,7e,a0,0c,07,45,7d,47,ba,8a) +#define SD_MESSAGE_SYSCTL_CHANGED SD_ID128_MAKE(9c,f5,6b,8b,af,95,46,cf,94,78,78,3a,8d,e4,21,13) +#define SD_MESSAGE_SYSCTL_CHANGED_STR SD_ID128_MAKE_STR(9c,f5,6b,8b,af,95,46,cf,94,78,78,3a,8d,e4,21,13) + _SD_END_DECLARATIONS; #endif diff --git a/units/systemd-networkd.service.in b/units/systemd-networkd.service.in index 6141fdbb6d..cf81c7d841 100644 --- a/units/systemd-networkd.service.in +++ b/units/systemd-networkd.service.in @@ -20,9 +20,9 @@ Conflicts=shutdown.target initrd-switch-root.target Wants=systemd-networkd.socket network.target systemd-networkd-persistent-storage.service [Service] -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN BusName=org.freedesktop.network1 -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN DeviceAllow=char-* rw ExecStart=!!{{LIBEXECDIR}}/systemd-networkd FileDescriptorStoreMax=512 @@ -48,7 +48,7 @@ RuntimeDirectory=systemd/netif RuntimeDirectoryPreserve=yes SystemCallArchitectures=native SystemCallErrorNumber=EPERM -SystemCallFilter=@system-service +SystemCallFilter=@system-service bpf Type=notify-reload User=systemd-network {{SERVICE_WATCHDOG}} |