@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s BindNetworkInterface = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property BindNetworkInterface is not documented!-->
+
<!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="BindNetworkInterface"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s BindNetworkInterface = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property BindNetworkInterface is not documented!-->
+
<!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="BindNetworkInterface"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s BindNetworkInterface = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property BindNetworkInterface is not documented!-->
+
<!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="BindNetworkInterface"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s BindNetworkInterface = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property BindNetworkInterface is not documented!-->
+
<!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="BindNetworkInterface"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s BindNetworkInterface = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property BindNetworkInterface is not documented!-->
+
<!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="BindNetworkInterface"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s BindNetworkInterface = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...;
<!--property RestrictNetworkInterfaces is not documented!-->
+ <!--property BindNetworkInterface is not documented!-->
+
<!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="BindNetworkInterface"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
<para id="controllers-text">The following controller names may be specified: <option>cpu</option>,
<option>cpuset</option>, <option>io</option>, <option>memory</option>, <option>pids</option>,
<option>bpf-firewall</option>, <option>bpf-devices</option>, <option>bpf-foreign</option>,
- <option>bpf-socket-bind</option>, and <option>bpf-restrict-network-interfaces</option>.</para>
+ <option>bpf-socket-bind</option>, <option>bpf-restrict-network-interfaces</option>, and
+ <option>bpf-bind-network-interface</option>.</para>
</refsect1>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>BindNetworkInterface=</varname></term>
+
+ <listitem>
+ <para>Takes the name of a network interface. This option causes every socket created by processes of this
+ unit to be bound to the specified network interface.
+ </para>
+
+ <para>It is specially useful to confine a process to a VRF, when the program does not offer native support
+ for it. It is equivalent to running the program using <constant>ip vrf exec</constant>.
+ </para>
+
+ <para>In systems using <filename>nss-resolve</filename>, the interface used for DNS resolution can be chosen
+ by using the <varname>SYSTEMD_NSS_RESOLVE_IFINDEX</varname> environment variable.</para>
+
+ <para>The feature is implemented with <constant>cgroup/sock_create</constant> cgroup-bpf hooks.</para>
+
+ <para>Example:<programlisting>[Service]
+BindNetworkInterface=vrf-mgmt
+</programlisting></para>
+
+ <xi:include href="cgroup-sandboxing.xml" xpointer="singular"/>
+
+ <xi:include href="version-info.xml" xpointer="v260"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>NFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
<listitem>
[CGROUP_CONTROLLER_BPF_FOREIGN] = "bpf-foreign",
[CGROUP_CONTROLLER_BPF_SOCKET_BIND] = "bpf-socket-bind",
[CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces",
+ [CGROUP_CONTROLLER_BPF_BIND_NETWORK_INTERFACE] = "bpf-bind-network-interface",
};
DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
CGROUP_CONTROLLER_BPF_FOREIGN,
CGROUP_CONTROLLER_BPF_SOCKET_BIND,
CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES,
+ CGROUP_CONTROLLER_BPF_BIND_NETWORK_INTERFACE,
/* The BPF hook implementing RestrictFileSystems= is not defined here.
* It's applied as late as possible in exec_invoke() so we don't block
* our own unit setup code. */
CGROUP_MASK_BPF_FOREIGN = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FOREIGN),
CGROUP_MASK_BPF_SOCKET_BIND = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_SOCKET_BIND),
CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES),
+ CGROUP_MASK_BPF_BIND_NETWORK_INTERFACE = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_BIND_NETWORK_INTERFACE),
/* All real cgroup v1 controllers */
CGROUP_MASK_V1 = CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT|CGROUP_MASK_BLKIO|CGROUP_MASK_MEMORY|CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS,
CGROUP_MASK_DELEGATE = CGROUP_MASK_V2,
/* All cgroup v2 BPF pseudo-controllers */
- CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN|CGROUP_MASK_BPF_SOCKET_BIND|CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES,
+ CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN|CGROUP_MASK_BPF_SOCKET_BIND|CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES|CGROUP_MASK_BPF_BIND_NETWORK_INTERFACE,
_CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1,
} CGroupMask;
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-netlink.h"
+
+#include "alloc-util.h"
+#include "bpf-bind-iface.h"
+#include "cgroup.h"
+#include "fd-util.h"
+#include "netlink-util.h"
+#include "string-util.h"
+#include "unit.h"
+
+#if BPF_FRAMEWORK
+/* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */
+#include "bpf-dlopen.h"
+#include "bpf-link.h"
+#include "bpf/bind-iface/bind-iface-skel.h"
+
+static struct bind_iface_bpf *bind_iface_bpf_free(struct bind_iface_bpf *obj) {
+ bind_iface_bpf__destroy(obj);
+ return NULL;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct bind_iface_bpf *, bind_iface_bpf_free);
+
+int bpf_bind_network_interface_supported(void) {
+ _cleanup_(bind_iface_bpf_freep) struct bind_iface_bpf *obj = NULL;
+ static int supported = -1;
+ int r;
+
+ if (supported >= 0)
+ return supported;
+
+ if (dlopen_bpf_full(LOG_WARNING) < 0)
+ return (supported = false);
+
+ obj = bind_iface_bpf__open();
+ if (!obj) {
+ log_debug_errno(errno, "bind-interface: Failed to open BPF object: %m");
+ return (supported = false);
+ }
+
+ r = bind_iface_bpf__load(obj);
+ if (r != 0) {
+ log_debug_errno(r, "bind-interface: Failed to load BPF object: %m");
+ return (supported = false);
+ }
+
+ return (supported = bpf_can_link_program(obj->progs.sd_bind_interface));
+}
+
+int bpf_bind_network_interface_install(Unit *u) {
+ _cleanup_(bpf_link_freep) struct bpf_link *link = NULL;
+ _cleanup_(bind_iface_bpf_freep) struct bind_iface_bpf *obj = NULL;
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ _cleanup_free_ char *cgroup_path = NULL;
+ _cleanup_close_ int cgroup_fd = -EBADF;
+ CGroupContext *cc;
+ CGroupRuntime *crt;
+ int r, ifindex;
+
+ assert(u);
+
+ cc = unit_get_cgroup_context(u);
+ if (!cc)
+ return 0;
+
+ crt = unit_get_cgroup_runtime(u);
+ if (!crt)
+ return 0;
+
+ if (isempty(cc->bind_network_interface))
+ return 0;
+
+ r = cg_get_path(crt->cgroup_path, /* suffix = */ NULL, &cgroup_path);
+ if (r < 0)
+ return log_unit_error_errno(u, r, "bind-interface: Failed to get cgroup path: %m");
+
+ ifindex = rtnl_resolve_interface(&rtnl, cc->bind_network_interface);
+ if (ifindex < 0) {
+ log_unit_warning_errno(u, ifindex,
+ "bind-interface: Couldn't find index of network interface '%s', ignoring: %m",
+ cc->bind_network_interface);
+ return 0;
+ }
+ log_unit_debug(u, "bind-interface: Found index %d for network interface '%s'", ifindex, cc->bind_network_interface);
+
+ /* Open the BPF skeleton */
+ obj = bind_iface_bpf__open();
+ if (!obj)
+ return log_unit_error_errno(u, errno, "bind-interface: Failed to open BPF object: %m");
+
+ /* Set the VRF interface index in rodata before loading */
+ obj->rodata->ifindex = ifindex;
+
+ /* Load the BPF program */
+ r = bind_iface_bpf__load(obj);
+ if (r != 0)
+ return log_unit_error_errno(u, r, "bind-interface: Failed to load BPF object: %m");
+
+ /* Open the cgroup directory */
+ cgroup_fd = open(cgroup_path, O_PATH | O_CLOEXEC | O_DIRECTORY, 0);
+ if (cgroup_fd < 0)
+ return log_unit_error_errno(u, errno, "bind-interface: Failed to open cgroup directory '%s': %m", cgroup_path);
+
+ /* Attach the BPF program to the cgroup */
+ link = sym_bpf_program__attach_cgroup(obj->progs.sd_bind_interface, cgroup_fd);
+ r = bpf_get_error_translated(link);
+ if (r != 0)
+ return log_unit_error_errno(u, r, "bind-interface: Failed to create cgroup link: %m");
+
+ /* Store the link in CGroupRuntime */
+ crt->bpf_bind_network_interface_link = TAKE_PTR(link);
+
+ log_unit_debug(u, "bind-interface: Successfully installed VRF binding for interface '%s' (ifindex=%d)",
+ cc->bind_network_interface, ifindex);
+
+ return 0;
+}
+
+int bpf_bind_network_interface_serialize(Unit *u, FILE *f, FDSet *fds) {
+ CGroupRuntime *crt;
+
+ assert(u);
+
+ crt = unit_get_cgroup_runtime(u);
+ if (!crt)
+ return 0;
+
+ return bpf_serialize_link(f, fds, "bind-interface-fd", crt->bpf_bind_network_interface_link);
+}
+
+#else /* ! BPF_FRAMEWORK */
+int bpf_bind_network_interface_supported(void) {
+ return 0;
+}
+
+int bpf_bind_network_interface_install(Unit *u) {
+ return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "bind-interface: Failed to install; BPF framework is not supported");
+}
+
+int bpf_bind_network_interface_serialize(Unit *u, FILE *f, FDSet *fds) {
+ return 0;
+}
+#endif
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "core-forward.h"
+
+int bpf_bind_network_interface_supported(void);
+int bpf_bind_network_interface_install(Unit *u);
+
+int bpf_bind_network_interface_serialize(Unit *u, FILE *f, FDSet *fds);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/* 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" /* IWYU pragma: keep */
+
+/* libbpf is used via dlopen(), so rename symbols */
+#define bpf_object__open_skeleton sym_bpf_object__open_skeleton
+#define bpf_object__load_skeleton sym_bpf_object__load_skeleton
+#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton
+
+#include "bpf/bind-iface/bind-iface.skel.h" /* IWYU pragma: export */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* <linux/bpf.h> must precede <bpf/bpf_helpers.h> due to integer types
+ * in bpf helpers signatures.
+ */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* VRF interface index to bind sockets to, set from userspace */
+const volatile __u32 ifindex = 0;
+
+SEC("cgroup/sock_create")
+int sd_bind_interface(struct bpf_sock *ctx) {
+ /* Bind the socket to the VRF interface */
+ ctx->bound_dev_if = ifindex;
+ return 1;
+}
+
+static const char _license[] SEC("license") = "LGPL-2.1-or-later";
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+if conf.get('BPF_FRAMEWORK') != 1
+ subdir_done()
+endif
+
+bind_network_interface_bpf_o_unstripped = custom_target(
+ input : 'bind-iface.bpf.c',
+ output : 'bind-iface.bpf.unstripped.o',
+ command : bpf_o_unstripped_cmd)
+
+bind_network_interface_bpf_o = custom_target(
+ input : bind_network_interface_bpf_o_unstripped,
+ output : 'bind-iface.bpf.o',
+ command : bpf_o_cmd)
+
+bind_network_interface_skel_h = custom_target(
+ input : bind_network_interface_bpf_o,
+ output : 'bind-iface.skel.h',
+ command : skel_h_cmd,
+ capture : true)
+
+generated_sources += bind_network_interface_skel_h
#include "af-list.h"
#include "alloc-util.h"
#include "blockdev-util.h"
+#include "bpf-bind-iface.h"
#include "bpf-devices.h"
#include "bpf-firewall.h"
#include "bpf-foreign.h"
c->restrict_network_interfaces = set_free(c->restrict_network_interfaces);
+ c->bind_network_interface = mfree(c->bind_network_interface);
+
cpu_set_done(&c->cpuset_cpus);
cpu_set_done(&c->startup_cpuset_cpus);
cpu_set_done(&c->cpuset_mems);
fprintf(f, "%sDelegateSubgroup: %s\n",
prefix, c->delegate_subgroup);
+ if (!isempty(c->bind_network_interface))
+ fprintf(f, "%sBindNetworkInterface: %s\n",
+ prefix, c->bind_network_interface);
+
if (c->memory_pressure_threshold_usec != USEC_INFINITY)
fprintf(f, "%sMemoryPressureThresholdSec: %s\n",
prefix, FORMAT_TIMESPAN(c->memory_pressure_threshold_usec, 1));
(void) bpf_restrict_ifaces_install(u);
}
+static void cgroup_apply_bind_network_interface(Unit *u) {
+ assert(u);
+
+ (void) bpf_bind_network_interface_install(u);
+}
+
static int cgroup_apply_devices(Unit *u) {
_cleanup_(bpf_program_freep) BPFProgram *prog = NULL;
CGroupContext *c;
if (apply_mask & CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES)
cgroup_apply_restrict_network_interfaces(u);
+ if (apply_mask & CGROUP_MASK_BPF_BIND_NETWORK_INTERFACE)
+ cgroup_apply_bind_network_interface(u);
+
unit_modify_nft_set(u, /* add= */ true);
}
return !set_isempty(c->restrict_network_interfaces);
}
+static bool unit_get_needs_bind_network_interface(Unit *u) {
+ CGroupContext *c;
+ assert(u);
+
+ c = unit_get_cgroup_context(u);
+ if (!c)
+ return false;
+
+ return !isempty(c->bind_network_interface);
+}
+
static CGroupMask unit_get_cgroup_mask(Unit *u) {
CGroupMask mask = 0;
CGroupContext *c;
if (unit_get_needs_restrict_network_interfaces(u))
mask |= CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES;
+ if (unit_get_needs_bind_network_interface(u))
+ mask |= CGROUP_MASK_BPF_BIND_NETWORK_INTERFACE;
+
return mask;
}
if (r > 0)
mask |= CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES;
+ /* BPF-based cgroup/sock_create hooks */
+ r = bpf_bind_network_interface_supported();
+ if (r < 0)
+ return r;
+ if (r > 0)
+ mask |= CGROUP_MASK_BPF_BIND_NETWORK_INTERFACE;
+
*ret = mask;
return 0;
}
#if BPF_FRAMEWORK
bpf_link_free(crt->restrict_ifaces_ingress_bpf_link);
bpf_link_free(crt->restrict_ifaces_egress_bpf_link);
+
+ bpf_link_free(crt->bpf_bind_network_interface_link);
#endif
+
fdset_free(crt->initial_restrict_ifaces_link_fds);
bpf_firewall_close(crt);
(void) bpf_restrict_ifaces_serialize(u, f, fds);
+ (void) bpf_bind_network_interface_serialize(u, f, fds);
+
return 0;
}
LIST_HEAD(CGroupSocketBindItem, socket_bind_allow);
LIST_HEAD(CGroupSocketBindItem, socket_bind_deny);
+ char *bind_network_interface;
+
/* Common */
CGroupTasksMax tasks_max;
bool warned_clamping_cpu_quota_period:1;
int deserialized_cgroup_realized; /* tristate, for backwards compat */
+
+#if BPF_FRAMEWORK
+ /* BPF link to BPF programs attached to cgroup/sock_create hooks and
+ * responsible for binding created sockets to a given VRF interface. */
+ struct bpf_link *bpf_bind_network_interface_link;
+#endif
} CGroupRuntime;
uint64_t cgroup_context_cpu_weight(CGroupContext *c, ManagerState state);
SD_BUS_PROPERTY("SocketBindAllow", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_allow), 0),
SD_BUS_PROPERTY("SocketBindDeny", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_deny), 0),
SD_BUS_PROPERTY("RestrictNetworkInterfaces", "(bas)", property_get_restrict_network_interfaces, 0, 0),
+ SD_BUS_PROPERTY("BindNetworkInterface", "s", NULL, offsetof(CGroupContext, bind_network_interface), 0),
SD_BUS_PROPERTY("MemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, memory_pressure_watch), 0),
SD_BUS_PROPERTY("MemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, memory_pressure_threshold_usec), 0),
SD_BUS_PROPERTY("NFTSet", "a(iiss)", property_get_cgroup_nft_set, 0, 0),
return 1;
}
+ if (streq(name, "BindNetworkInterface")) {
+ const char *s;
+
+ r = sd_bus_message_read(message, "s", &s);
+ if (r < 0)
+ return r;
+
+ if (!ifname_valid_full(s, IFNAME_VALID_ALTERNATIVE))
+ return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface name: %s", s);
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ if (isempty(s))
+ c->bind_network_interface = mfree(c->bind_network_interface);
+ else {
+ r = free_and_strdup_warn(&c->bind_network_interface, s);
+ if (r < 0)
+ return r;
+ }
+
+ unit_write_settingf(u, flags, name, "BindNetworkInterface=%s", strempty(s));
+ }
+
+ return 1;
+ }
+
if (streq(name, "NFTSet")) {
int source, nfproto;
const char *table, *set;
if (r < 0)
return r;
+ r = serialize_item(f, "exec-cgroup-context-bind-iface", c->bind_network_interface);
+ if (r < 0)
+ return r;
+
fputc('\n', f); /* End marker */
return 0;
if (r < 0)
return r;
c->restrict_network_interfaces_is_allow_list = r;
+ } else if ((val = startswith(l, "exec-cgroup-context-bind-iface="))) {
+ r = free_and_strdup(&c->bind_network_interface, val);
+ if (r < 0)
+ return r;
} else
log_warning("Failed to parse serialized line, ignoring: %s", l);
}
{{type}}.MemoryPressureWatch, config_parse_memory_pressure_watch, 0, offsetof({{type}}, cgroup_context.memory_pressure_watch)
{{type}}.NFTSet, config_parse_cgroup_nft_set, NFT_SET_PARSE_CGROUP, offsetof({{type}}, cgroup_context)
{{type}}.CoredumpReceive, config_parse_bool, 0, offsetof({{type}}, cgroup_context.coredump_receive)
+{{type}}.BindNetworkInterface, config_parse_bind_network_interface, 0, offsetof({{type}}, cgroup_context)
{%- endmacro -%}
%{
return config_parse_unsigned(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata);
}
+int config_parse_bind_network_interface(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ CGroupContext *c = ASSERT_PTR(data);
+
+ _cleanup_free_ char *k = NULL;
+ const Unit *u = ASSERT_PTR(userdata);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ c->bind_network_interface = mfree(c->bind_network_interface);
+ return 0;
+ }
+
+ r = unit_full_printf(u, rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue);
+ return 0;
+ }
+
+ if (!ifname_valid_full(k, IFNAME_VALID_ALTERNATIVE)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid interface name, ignoring: %s", k);
+ return 0;
+ }
+
+ return free_and_strdup_warn(&c->bind_network_interface, k);
+}
+
static int merge_by_names(Unit *u, Set *names, const char *id) {
char *k;
int r;
CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max);
+CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
'bpf-restrict-fs.c',
'bpf-restrict-ifaces.c',
'bpf-socket-bind.c',
+ 'bpf-bind-iface.c',
'cgroup.c',
'dbus-automount.c',
'dbus-cgroup.c',
subdir('bpf/socket-bind')
subdir('bpf/restrict-fs')
subdir('bpf/restrict-ifaces')
+subdir('bpf/bind-iface')
if conf.get('BPF_FRAMEWORK') == 1
libcore_sources += [
socket_bind_skel_h,
restrict_fs_skel_h,
- restrict_ifaces_skel_h]
+ restrict_ifaces_skel_h,
+ bind_network_interface_skel_h]
endif
sources += libcore_sources
{ "StateDirectoryAccounting", bus_append_parse_boolean },
{ "CacheDirectoryAccounting", bus_append_parse_boolean },
{ "LogsDirectoryAccounting", bus_append_parse_boolean },
+ { "BindNetworkInterface", bus_append_string },
{ NULL, bus_try_append_resource_limit, dump_resource_limits },
{}
TEST(cg_mask_to_string) {
test_cg_mask_to_string_one(0, NULL);
- test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices bpf-foreign bpf-socket-bind bpf-restrict-network-interfaces");
+ test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices bpf-foreign bpf-socket-bind bpf-restrict-network-interfaces bpf-bind-network-interface");
test_cg_mask_to_string_one(CGROUP_MASK_CPU, "cpu");
test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT, "cpuacct");
test_cg_mask_to_string_one(CGROUP_MASK_CPUSET, "cpuset");
bash -xec 'timeout 1s ncat -6 -l ::1 1234; exit 1'
systemd-run --wait --pipe -p SuccessExitStatus=124 "${ARGUMENTS[@]}" \
bash -xec 'timeout 1s ncat -6 -l ::1 6666; exit 1'
+
+ # BindNetworkInterface*=
+ # Create a VRF interface to later bind to and check if the binding is working
+ ip link add vrf-test type vrf table 100
+ ip link set vrf-test up
+ ip address add 127.0.0.1/8 dev vrf-test
+
+ # Verify that a socket with BindNetworkInterface set is correctly bound to the interface
+ systemd-run --wait --pipe -p BindNetworkInterface=vrf-test \
+ bash -xec 'ncat -l 127.0.0.1 9999 & sleep 0.5; ss -tlnp | grep "127.0.0.1%vrf-test:9999" > /dev/null'
+
+ # Verify that a socket without BindNetworkInterface is not bound to any interface
+ systemd-run --wait --pipe \
+ bash -xec 'ncat -l 127.0.0.1 9998 & sleep 0.5; ss -tlnp | grep "127.0.0.1:9998" > /dev/null'
+
+ ip link del vrf-test
+
fi
losetup -d "$LODEV"
org.freedesktop.systemd1.Mount.AppArmorProfile
org.freedesktop.systemd1.Mount.AttachProcesses()
org.freedesktop.systemd1.Mount.BPFProgram
+org.freedesktop.systemd1.Mount.BindNetworkInterface
org.freedesktop.systemd1.Mount.BindPaths
org.freedesktop.systemd1.Mount.BindReadOnlyPaths
org.freedesktop.systemd1.Mount.BlockIOAccounting
org.freedesktop.systemd1.Scope.AllowedMemoryNodes
org.freedesktop.systemd1.Scope.AttachProcesses()
org.freedesktop.systemd1.Scope.BPFProgram
+org.freedesktop.systemd1.Scope.BindNetworkInterface
org.freedesktop.systemd1.Scope.BlockIOAccounting
org.freedesktop.systemd1.Scope.BlockIODeviceWeight
org.freedesktop.systemd1.Scope.BlockIOReadBandwidth
org.freedesktop.systemd1.Service.AttachProcesses()
org.freedesktop.systemd1.Service.BPFProgram
org.freedesktop.systemd1.Service.BindMount()
+org.freedesktop.systemd1.Service.BindNetworkInterface
org.freedesktop.systemd1.Service.BindPaths
org.freedesktop.systemd1.Service.BindReadOnlyPaths
org.freedesktop.systemd1.Service.BlockIOAccounting
org.freedesktop.systemd1.Slice.AllowedMemoryNodes
org.freedesktop.systemd1.Slice.AttachProcesses()
org.freedesktop.systemd1.Slice.BPFProgram
+org.freedesktop.systemd1.Slice.BindNetworkInterface
org.freedesktop.systemd1.Slice.BlockIOAccounting
org.freedesktop.systemd1.Slice.BlockIODeviceWeight
org.freedesktop.systemd1.Slice.BlockIOReadBandwidth
org.freedesktop.systemd1.Socket.BPFProgram
org.freedesktop.systemd1.Socket.Backlog
org.freedesktop.systemd1.Socket.BindIPv6Only
+org.freedesktop.systemd1.Socket.BindNetworkInterface
org.freedesktop.systemd1.Socket.BindPaths
org.freedesktop.systemd1.Socket.BindReadOnlyPaths
org.freedesktop.systemd1.Socket.BindToDevice
org.freedesktop.systemd1.Swap.AppArmorProfile
org.freedesktop.systemd1.Swap.AttachProcesses()
org.freedesktop.systemd1.Swap.BPFProgram
+org.freedesktop.systemd1.Swap.BindNetworkInterface
org.freedesktop.systemd1.Swap.BindPaths
org.freedesktop.systemd1.Swap.BindReadOnlyPaths
org.freedesktop.systemd1.Swap.BlockIOAccounting