1
0
mirror of https://github.com/systemd/systemd synced 2026-03-24 07:44:52 +01:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Lennart Poettering
ceaa36c21e terminal-util: use the right ANSI ST sequence
There are multiple ways to encode ANSI ST, and we generally prefer ESC \
for it, for reasons explained in terminal-util.h. Hence, let's actually
follow this rule in the terminal reset logic, and use the ANSI_ST macro.

This will change the byte sequence generated (as it means we use ESC \
rather than BEL), but it doesn't change behaviour, as the two sequences
should be equivalent.
2025-12-24 09:40:43 +01:00
Mike Yuan
22eb3a6bd9 ptyfwd: turn off O_NONBLOCK on output after soft reset
Follow-up for 3d97db8f3c3e86b70d09444965ebfddd051df39c

terminal_reset_ansi_req() would try to put terminal in nonblocking
mode temporarily again, hence just avoid the back and forth
and reset nonblocking as last step.
2025-12-24 08:07:59 +01:00
Jörg Behrmann
1afe5c7b4b terminal-util: also send ANSI_NORMAL in terminal_reset_ansi_seq
Certain terminal emulators (alacritty, ghostty and kitty) require ANSI_NORMAL
to be sent to reset the colours.

Followup for 3d97db8f3c3e86b70d09444965ebfddd051df39c.

Fixes: #40163
2025-12-24 07:59:42 +01:00
Lennart Poettering
1978e42197 update TODO 2025-12-24 07:38:28 +01:00
Lennart Poettering
77639f179b update TODO 2025-12-24 07:38:15 +01:00
Luiz Amaral
c1c787651b Add support for binding a unit to a network iface 2025-12-24 07:37:58 +01:00
25 changed files with 451 additions and 32 deletions

23
TODO
View File

@ -143,6 +143,9 @@ Features:
integrity tags are stored by the device (inline), interleaved with data (data), integrity tags are stored by the device (inline), interleaved with data (data),
and on a separate device (meta). and on a separate device (meta).
* homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can
be allowed if caller comes with the right ssh-agent keys.
* networkd/machined: implement reverse name lookups in the resolved hook * networkd/machined: implement reverse name lookups in the resolved hook
* networkd's resolved hook: optionally map all lease IP addresses handed out to * networkd's resolved hook: optionally map all lease IP addresses handed out to
@ -213,10 +216,6 @@ Features:
* similar: add a plugin for factory reset logic that erases certain parts of * similar: add a plugin for factory reset logic that erases certain parts of
the ESP, but leaves others in place. the ESP, but leaves others in place.
* systemd-repart: add --defer-partitions-factory-reset or so, as a flavour of
--defer-partitions= that picks all partitions that are marked for factory
reset. for an installer this is usually the partitions not to copy, too.
* flush_fd() should probably try to be smart and stop reading once we know that * flush_fd() should probably try to be smart and stop reading once we know that
all further queued data was enqueued after flush_fd() was originally all further queued data was enqueued after flush_fd() was originally
called. For that, try SIOCINQ if fd refers to stream socket, and look at called. For that, try SIOCINQ if fd refers to stream socket, and look at
@ -847,8 +846,6 @@ Features:
early on, but provide opt-out via kernel cmdline. early on, but provide opt-out via kernel cmdline.
* systemd-pcrextend: * systemd-pcrextend:
- support measuring to nvindex with PCR update semantics ("fake PCRs")
- add api for "allocating" such an nvindex
- once we have that start measuring every sysext we apply, every confext, - once we have that start measuring every sysext we apply, every confext,
every RootImage= we apply, every nspawn and so on. All in separate fake every RootImage= we apply, every nspawn and so on. All in separate fake
PCRs. PCRs.
@ -863,8 +860,6 @@ Features:
- translate SIGTERM to clean ACPI shutdown event - translate SIGTERM to clean ACPI shutdown event
- implement hotkeys ^]^]r and ^]^]p like nspawn - implement hotkeys ^]^]r and ^]^]p like nspawn
* systemd-pcrmachine should probably also measure the SMBIOS system UUID.
* storagetm: * storagetm:
- add USB mass storage device logic, so that all local disks are also exposed - add USB mass storage device logic, so that all local disks are also exposed
as mass storage devices on systems that have a USB controller that can as mass storage devices on systems that have a USB controller that can
@ -1269,10 +1264,6 @@ Features:
parametrization, if needed. This matches our usual rule that admin config parametrization, if needed. This matches our usual rule that admin config
should win over vendor defaults. should win over vendor defaults.
* write a "search path" spec, that documents the prefixes to search in
(i.e. the usual /etc/, /run/, /usr/lib/ dance, potentially /usr/etc/), how to
sort found entries, how masking works and overriding.
* automatic boot assessment: add one more default success check that just waits * automatic boot assessment: add one more default success check that just waits
for a bit after boot, and blesses the boot if the system stayed up that long. for a bit after boot, and blesses the boot if the system stayed up that long.
@ -1689,10 +1680,6 @@ Features:
(i.e. sysext, root verity) from those inherently local (i.e. encryption key), (i.e. sysext, root verity) from those inherently local (i.e. encryption key),
which is useful if they shall be signed separately. which is useful if they shall be signed separately.
* in uefi stub: query firmware regarding which PCR banks are being used, store
that in EFI var. then use this when enrolling TPM2 in cryptsetup to verify
that the selected PCRs actually are used by firmware.
* rework recursive read-only remount to use new mount API * rework recursive read-only remount to use new mount API
* when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release * when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release
@ -1787,9 +1774,6 @@ Features:
* Figure out naming of verbs in systemd-analyze: we have (singular) capability, * Figure out naming of verbs in systemd-analyze: we have (singular) capability,
exit-status, but (plural) filesystems, architectures. exit-status, but (plural) filesystems, architectures.
* Add service setting to run a service within the specified VRF. i.e. do the
equivalent of "ip vrf exec".
* special case some calls of chase() to use openat2() internally, so * special case some calls of chase() to use openat2() internally, so
that the kernel does what we otherwise do. that the kernel does what we otherwise do.
@ -2280,7 +2264,6 @@ Features:
- allow Type=simple with PIDFile= - allow Type=simple with PIDFile=
https://bugzilla.redhat.com/show_bug.cgi?id=723942 https://bugzilla.redhat.com/show_bug.cgi?id=723942
- allow writing multiple conditions in unit files on one line - allow writing multiple conditions in unit files on one line
- introduce Type=pid-file
- add a concept of RemainAfterExit= to scope units - add a concept of RemainAfterExit= to scope units
- Allow multiple ExecStart= for all Type= settings, so that we can cover rescue.service nicely - Allow multiple ExecStart= for all Type= settings, so that we can cover rescue.service nicely
- add verification of [Install] section to systemd-analyze verify - add verification of [Install] section to systemd-analyze verify

View File

@ -3037,6 +3037,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...; readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s BindNetworkInterface = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...'; readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...; readonly t MemoryPressureThresholdUSec = ...;
@ -3704,6 +3706,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property RestrictNetworkInterfaces is not documented!--> <!--property RestrictNetworkInterfaces is not documented!-->
<!--property BindNetworkInterface is not documented!-->
<!--property MemoryPressureWatch is not documented!--> <!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!--> <!--property MemoryPressureThresholdUSec is not documented!-->
@ -4396,6 +4400,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/> <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="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@ -5294,6 +5300,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...; readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s BindNetworkInterface = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...'; readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...; readonly t MemoryPressureThresholdUSec = ...;
@ -5979,6 +5987,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property RestrictNetworkInterfaces is not documented!--> <!--property RestrictNetworkInterfaces is not documented!-->
<!--property BindNetworkInterface is not documented!-->
<!--property MemoryPressureWatch is not documented!--> <!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!--> <!--property MemoryPressureThresholdUSec is not documented!-->
@ -6647,6 +6657,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/> <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="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@ -7369,6 +7381,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...; readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s BindNetworkInterface = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...'; readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...; readonly t MemoryPressureThresholdUSec = ...;
@ -7978,6 +7992,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property RestrictNetworkInterfaces is not documented!--> <!--property RestrictNetworkInterfaces is not documented!-->
<!--property BindNetworkInterface is not documented!-->
<!--property MemoryPressureWatch is not documented!--> <!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!--> <!--property MemoryPressureThresholdUSec is not documented!-->
@ -8554,6 +8570,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/> <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="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@ -9409,6 +9427,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...; readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s BindNetworkInterface = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...'; readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...; readonly t MemoryPressureThresholdUSec = ...;
@ -10000,6 +10020,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property RestrictNetworkInterfaces is not documented!--> <!--property RestrictNetworkInterfaces is not documented!-->
<!--property BindNetworkInterface is not documented!-->
<!--property MemoryPressureWatch is not documented!--> <!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!--> <!--property MemoryPressureThresholdUSec is not documented!-->
@ -10558,6 +10580,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/> <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="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@ -11266,6 +11290,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...; readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s BindNetworkInterface = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...'; readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...; readonly t MemoryPressureThresholdUSec = ...;
@ -11443,6 +11469,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
<!--property RestrictNetworkInterfaces is not documented!--> <!--property RestrictNetworkInterfaces is not documented!-->
<!--property BindNetworkInterface is not documented!-->
<!--property MemoryPressureWatch is not documented!--> <!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!--> <!--property MemoryPressureThresholdUSec is not documented!-->
@ -11635,6 +11663,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/> <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="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>
@ -11850,6 +11880,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly (bas) RestrictNetworkInterfaces = ...; readonly (bas) RestrictNetworkInterfaces = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s BindNetworkInterface = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s MemoryPressureWatch = '...'; readonly s MemoryPressureWatch = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryPressureThresholdUSec = ...; readonly t MemoryPressureThresholdUSec = ...;
@ -12041,6 +12073,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
<!--property RestrictNetworkInterfaces is not documented!--> <!--property RestrictNetworkInterfaces is not documented!-->
<!--property BindNetworkInterface is not documented!-->
<!--property MemoryPressureWatch is not documented!--> <!--property MemoryPressureWatch is not documented!-->
<!--property MemoryPressureThresholdUSec is not documented!--> <!--property MemoryPressureThresholdUSec is not documented!-->
@ -12257,6 +12291,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictNetworkInterfaces"/> <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="MemoryPressureWatch"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryPressureThresholdUSec"/>

View File

@ -11,6 +11,7 @@
<para id="controllers-text">The following controller names may be specified: <option>cpu</option>, <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>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-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> </refsect1>

View File

@ -1023,6 +1023,33 @@ RestrictNetworkInterfaces=~eth1</programlisting>
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><varname>NFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term> <term><varname>NFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
<listitem> <listitem>

View File

@ -1771,6 +1771,7 @@ static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
[CGROUP_CONTROLLER_BPF_FOREIGN] = "bpf-foreign", [CGROUP_CONTROLLER_BPF_FOREIGN] = "bpf-foreign",
[CGROUP_CONTROLLER_BPF_SOCKET_BIND] = "bpf-socket-bind", [CGROUP_CONTROLLER_BPF_SOCKET_BIND] = "bpf-socket-bind",
[CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES] = "bpf-restrict-network-interfaces", [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); DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);

View File

@ -23,6 +23,7 @@ typedef enum CGroupController {
CGROUP_CONTROLLER_BPF_FOREIGN, CGROUP_CONTROLLER_BPF_FOREIGN,
CGROUP_CONTROLLER_BPF_SOCKET_BIND, CGROUP_CONTROLLER_BPF_SOCKET_BIND,
CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES, CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES,
CGROUP_CONTROLLER_BPF_BIND_NETWORK_INTERFACE,
/* The BPF hook implementing RestrictFileSystems= is not defined here. /* The BPF hook implementing RestrictFileSystems= is not defined here.
* It's applied as late as possible in exec_invoke() so we don't block * It's applied as late as possible in exec_invoke() so we don't block
* our own unit setup code. */ * our own unit setup code. */
@ -48,6 +49,7 @@ typedef enum CGroupMask {
CGROUP_MASK_BPF_FOREIGN = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FOREIGN), 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_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_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 */ /* 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_V1 = CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT|CGROUP_MASK_BLKIO|CGROUP_MASK_MEMORY|CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS,
@ -59,7 +61,7 @@ typedef enum CGroupMask {
CGROUP_MASK_DELEGATE = CGROUP_MASK_V2, CGROUP_MASK_DELEGATE = CGROUP_MASK_V2,
/* All cgroup v2 BPF pseudo-controllers */ /* 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, _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1,
} CGroupMask; } CGroupMask;

View File

@ -969,7 +969,8 @@ int terminal_reset_ansi_seq(int fd) {
k = loop_write_full(fd, k = loop_write_full(fd,
"\033[!p" /* soft terminal reset */ "\033[!p" /* soft terminal reset */
"\033]104\007" /* reset colors */ ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */
ANSI_NORMAL /* reset colors */
"\033[?7h" /* enable line-wrapping */ "\033[?7h" /* enable line-wrapping */
"\033[1G" /* place cursor at beginning of current line */ "\033[1G" /* place cursor at beginning of current line */
"\033[0J", /* erase till end of screen */ "\033[0J", /* erase till end of screen */

146
src/core/bpf-bind-iface.c Normal file
View File

@ -0,0 +1,146 @@
/* 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

View File

@ -0,0 +1,9 @@
/* 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);

View File

@ -0,0 +1,17 @@
/* 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 */

View File

@ -0,0 +1,19 @@
/* 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";

View File

@ -0,0 +1,23 @@
# 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

View File

@ -10,6 +10,7 @@
#include "af-list.h" #include "af-list.h"
#include "alloc-util.h" #include "alloc-util.h"
#include "blockdev-util.h" #include "blockdev-util.h"
#include "bpf-bind-iface.h"
#include "bpf-devices.h" #include "bpf-devices.h"
#include "bpf-firewall.h" #include "bpf-firewall.h"
#include "bpf-foreign.h" #include "bpf-foreign.h"
@ -268,6 +269,8 @@ void cgroup_context_done(CGroupContext *c) {
c->restrict_network_interfaces = set_free(c->restrict_network_interfaces); 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->cpuset_cpus);
cpu_set_done(&c->startup_cpuset_cpus); cpu_set_done(&c->startup_cpuset_cpus);
cpu_set_done(&c->cpuset_mems); cpu_set_done(&c->cpuset_mems);
@ -568,6 +571,10 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
fprintf(f, "%sDelegateSubgroup: %s\n", fprintf(f, "%sDelegateSubgroup: %s\n",
prefix, c->delegate_subgroup); 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) if (c->memory_pressure_threshold_usec != USEC_INFINITY)
fprintf(f, "%sMemoryPressureThresholdSec: %s\n", fprintf(f, "%sMemoryPressureThresholdSec: %s\n",
prefix, FORMAT_TIMESPAN(c->memory_pressure_threshold_usec, 1)); prefix, FORMAT_TIMESPAN(c->memory_pressure_threshold_usec, 1));
@ -1369,6 +1376,12 @@ static void cgroup_apply_restrict_network_interfaces(Unit *u) {
(void) bpf_restrict_ifaces_install(u); (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) { static int cgroup_apply_devices(Unit *u) {
_cleanup_(bpf_program_freep) BPFProgram *prog = NULL; _cleanup_(bpf_program_freep) BPFProgram *prog = NULL;
CGroupContext *c; CGroupContext *c;
@ -1609,6 +1622,9 @@ static void cgroup_context_apply(
if (apply_mask & CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES) if (apply_mask & CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES)
cgroup_apply_restrict_network_interfaces(u); 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); unit_modify_nft_set(u, /* add= */ true);
} }
@ -1674,6 +1690,17 @@ static bool unit_get_needs_restrict_network_interfaces(Unit *u) {
return !set_isempty(c->restrict_network_interfaces); 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) { static CGroupMask unit_get_cgroup_mask(Unit *u) {
CGroupMask mask = 0; CGroupMask mask = 0;
CGroupContext *c; CGroupContext *c;
@ -1726,6 +1753,9 @@ static CGroupMask unit_get_bpf_mask(Unit *u) {
if (unit_get_needs_restrict_network_interfaces(u)) if (unit_get_needs_restrict_network_interfaces(u))
mask |= CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES; mask |= CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES;
if (unit_get_needs_bind_network_interface(u))
mask |= CGROUP_MASK_BPF_BIND_NETWORK_INTERFACE;
return mask; return mask;
} }
@ -3244,6 +3274,13 @@ static int cg_bpf_mask_supported(CGroupMask *ret) {
if (r > 0) if (r > 0)
mask |= CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES; 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; *ret = mask;
return 0; return 0;
} }
@ -4193,7 +4230,10 @@ CGroupRuntime* cgroup_runtime_free(CGroupRuntime *crt) {
#if BPF_FRAMEWORK #if BPF_FRAMEWORK
bpf_link_free(crt->restrict_ifaces_ingress_bpf_link); bpf_link_free(crt->restrict_ifaces_ingress_bpf_link);
bpf_link_free(crt->restrict_ifaces_egress_bpf_link); bpf_link_free(crt->restrict_ifaces_egress_bpf_link);
bpf_link_free(crt->bpf_bind_network_interface_link);
#endif #endif
fdset_free(crt->initial_restrict_ifaces_link_fds); fdset_free(crt->initial_restrict_ifaces_link_fds);
bpf_firewall_close(crt); bpf_firewall_close(crt);
@ -4317,6 +4357,8 @@ int cgroup_runtime_serialize(Unit *u, FILE *f, FDSet *fds) {
(void) bpf_restrict_ifaces_serialize(u, f, fds); (void) bpf_restrict_ifaces_serialize(u, f, fds);
(void) bpf_bind_network_interface_serialize(u, f, fds);
return 0; return 0;
} }

View File

@ -185,6 +185,8 @@ typedef struct CGroupContext {
LIST_HEAD(CGroupSocketBindItem, socket_bind_allow); LIST_HEAD(CGroupSocketBindItem, socket_bind_allow);
LIST_HEAD(CGroupSocketBindItem, socket_bind_deny); LIST_HEAD(CGroupSocketBindItem, socket_bind_deny);
char *bind_network_interface;
/* Common */ /* Common */
CGroupTasksMax tasks_max; CGroupTasksMax tasks_max;
@ -332,6 +334,12 @@ typedef struct CGroupRuntime {
bool warned_clamping_cpu_quota_period:1; bool warned_clamping_cpu_quota_period:1;
int deserialized_cgroup_realized; /* tristate, for backwards compat */ 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; } CGroupRuntime;
uint64_t cgroup_context_cpu_weight(CGroupContext *c, ManagerState state); uint64_t cgroup_context_cpu_weight(CGroupContext *c, ManagerState state);

View File

@ -429,6 +429,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_PROPERTY("SocketBindAllow", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_allow), 0), 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("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("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("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("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), SD_BUS_PROPERTY("NFTSet", "a(iiss)", property_get_cgroup_nft_set, 0, 0),
@ -1950,6 +1951,31 @@ int bus_cgroup_set_property(
return 1; 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")) { if (streq(name, "NFTSet")) {
int source, nfproto; int source, nfproto;
const char *table, *set; const char *table, *set;

View File

@ -428,6 +428,10 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) {
if (r < 0) if (r < 0)
return r; 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 */ fputc('\n', f); /* End marker */
return 0; return 0;
@ -907,6 +911,10 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) {
if (r < 0) if (r < 0)
return r; return r;
c->restrict_network_interfaces_is_allow_list = 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 } else
log_warning("Failed to parse serialized line, ignoring: %s", l); log_warning("Failed to parse serialized line, ignoring: %s", l);
} }

View File

@ -278,6 +278,7 @@
{{type}}.MemoryPressureWatch, config_parse_memory_pressure_watch, 0, offsetof({{type}}, cgroup_context.memory_pressure_watch) {{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}}.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}}.CoredumpReceive, config_parse_bool, 0, offsetof({{type}}, cgroup_context.coredump_receive)
{{type}}.BindNetworkInterface, config_parse_bind_network_interface, 0, offsetof({{type}}, cgroup_context)
{%- endmacro -%} {%- endmacro -%}
%{ %{

View File

@ -5999,6 +5999,47 @@ int config_parse_concurrency_max(
return config_parse_unsigned(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata); 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) { static int merge_by_names(Unit *u, Set *names, const char *id) {
char *k; char *k;
int r; int r;

View File

@ -167,6 +167,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch);
CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set); CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
CONFIG_PARSER_PROTOTYPE(config_parse_mount_node); CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max); CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max);
CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface);
/* gperf prototypes */ /* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);

View File

@ -9,6 +9,7 @@ libcore_sources = files(
'bpf-restrict-fs.c', 'bpf-restrict-fs.c',
'bpf-restrict-ifaces.c', 'bpf-restrict-ifaces.c',
'bpf-socket-bind.c', 'bpf-socket-bind.c',
'bpf-bind-iface.c',
'cgroup.c', 'cgroup.c',
'dbus-automount.c', 'dbus-automount.c',
'dbus-cgroup.c', 'dbus-cgroup.c',
@ -74,12 +75,14 @@ libcore_sources = files(
subdir('bpf/socket-bind') subdir('bpf/socket-bind')
subdir('bpf/restrict-fs') subdir('bpf/restrict-fs')
subdir('bpf/restrict-ifaces') subdir('bpf/restrict-ifaces')
subdir('bpf/bind-iface')
if conf.get('BPF_FRAMEWORK') == 1 if conf.get('BPF_FRAMEWORK') == 1
libcore_sources += [ libcore_sources += [
socket_bind_skel_h, socket_bind_skel_h,
restrict_fs_skel_h, restrict_fs_skel_h,
restrict_ifaces_skel_h] restrict_ifaces_skel_h,
bind_network_interface_skel_h]
endif endif
sources += libcore_sources sources += libcore_sources

View File

@ -2551,6 +2551,7 @@ static const BusProperty execute_properties[] = {
{ "StateDirectoryAccounting", bus_append_parse_boolean }, { "StateDirectoryAccounting", bus_append_parse_boolean },
{ "CacheDirectoryAccounting", bus_append_parse_boolean }, { "CacheDirectoryAccounting", bus_append_parse_boolean },
{ "LogsDirectoryAccounting", bus_append_parse_boolean }, { "LogsDirectoryAccounting", bus_append_parse_boolean },
{ "BindNetworkInterface", bus_append_string },
{ NULL, bus_try_append_resource_limit, dump_resource_limits }, { NULL, bus_try_append_resource_limit, dump_resource_limits },
{} {}

View File

@ -131,9 +131,6 @@ static void pty_forward_disconnect(PTYForward *f) {
if (f->saved_stdout) if (f->saved_stdout)
(void) tcsetattr(f->output_fd, TCSANOW, &f->saved_stdout_attr); (void) tcsetattr(f->output_fd, TCSANOW, &f->saved_stdout_attr);
/* STDIN/STDOUT should not be non-blocking normally, so let's reset it */
(void) fd_nonblock(f->output_fd, false);
if (f->last_char_set && f->last_char != '\n') { if (f->last_char_set && f->last_char != '\n') {
const char *s; const char *s;
@ -154,6 +151,9 @@ static void pty_forward_disconnect(PTYForward *f) {
terminal_reset_ansi_seq(f->output_fd); terminal_reset_ansi_seq(f->output_fd);
} }
/* STDIN/STDOUT should not be non-blocking normally, so let's reset it */
(void) fd_nonblock(f->output_fd, false);
if (f->close_output_fd) if (f->close_output_fd)
f->output_fd = safe_close(f->output_fd); f->output_fd = safe_close(f->output_fd);
} }

View File

@ -130,7 +130,7 @@ static void test_cg_mask_to_string_one(CGroupMask mask, const char *t) {
TEST(cg_mask_to_string) { TEST(cg_mask_to_string) {
test_cg_mask_to_string_one(0, NULL); 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_CPU, "cpu");
test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT, "cpuacct"); test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT, "cpuacct");
test_cg_mask_to_string_one(CGROUP_MASK_CPUSET, "cpuset"); test_cg_mask_to_string_one(CGROUP_MASK_CPUSET, "cpuset");

View File

@ -216,6 +216,23 @@ if ! systemd-detect-virt -cq; then
bash -xec 'timeout 1s ncat -6 -l ::1 1234; exit 1' bash -xec 'timeout 1s ncat -6 -l ::1 1234; exit 1'
systemd-run --wait --pipe -p SuccessExitStatus=124 "${ARGUMENTS[@]}" \ systemd-run --wait --pipe -p SuccessExitStatus=124 "${ARGUMENTS[@]}" \
bash -xec 'timeout 1s ncat -6 -l ::1 6666; exit 1' 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 fi
losetup -d "$LODEV" losetup -d "$LODEV"

View File

@ -751,6 +751,7 @@ org.freedesktop.systemd1.Mount.AmbientCapabilities
org.freedesktop.systemd1.Mount.AppArmorProfile org.freedesktop.systemd1.Mount.AppArmorProfile
org.freedesktop.systemd1.Mount.AttachProcesses() org.freedesktop.systemd1.Mount.AttachProcesses()
org.freedesktop.systemd1.Mount.BPFProgram org.freedesktop.systemd1.Mount.BPFProgram
org.freedesktop.systemd1.Mount.BindNetworkInterface
org.freedesktop.systemd1.Mount.BindPaths org.freedesktop.systemd1.Mount.BindPaths
org.freedesktop.systemd1.Mount.BindReadOnlyPaths org.freedesktop.systemd1.Mount.BindReadOnlyPaths
org.freedesktop.systemd1.Mount.BlockIOAccounting org.freedesktop.systemd1.Mount.BlockIOAccounting
@ -1018,6 +1019,7 @@ org.freedesktop.systemd1.Scope.AllowedCPUs
org.freedesktop.systemd1.Scope.AllowedMemoryNodes org.freedesktop.systemd1.Scope.AllowedMemoryNodes
org.freedesktop.systemd1.Scope.AttachProcesses() org.freedesktop.systemd1.Scope.AttachProcesses()
org.freedesktop.systemd1.Scope.BPFProgram org.freedesktop.systemd1.Scope.BPFProgram
org.freedesktop.systemd1.Scope.BindNetworkInterface
org.freedesktop.systemd1.Scope.BlockIOAccounting org.freedesktop.systemd1.Scope.BlockIOAccounting
org.freedesktop.systemd1.Scope.BlockIODeviceWeight org.freedesktop.systemd1.Scope.BlockIODeviceWeight
org.freedesktop.systemd1.Scope.BlockIOReadBandwidth org.freedesktop.systemd1.Scope.BlockIOReadBandwidth
@ -1107,6 +1109,7 @@ org.freedesktop.systemd1.Service.AppArmorProfile
org.freedesktop.systemd1.Service.AttachProcesses() org.freedesktop.systemd1.Service.AttachProcesses()
org.freedesktop.systemd1.Service.BPFProgram org.freedesktop.systemd1.Service.BPFProgram
org.freedesktop.systemd1.Service.BindMount() org.freedesktop.systemd1.Service.BindMount()
org.freedesktop.systemd1.Service.BindNetworkInterface
org.freedesktop.systemd1.Service.BindPaths org.freedesktop.systemd1.Service.BindPaths
org.freedesktop.systemd1.Service.BindReadOnlyPaths org.freedesktop.systemd1.Service.BindReadOnlyPaths
org.freedesktop.systemd1.Service.BlockIOAccounting org.freedesktop.systemd1.Service.BlockIOAccounting
@ -1409,6 +1412,7 @@ org.freedesktop.systemd1.Slice.AllowedCPUs
org.freedesktop.systemd1.Slice.AllowedMemoryNodes org.freedesktop.systemd1.Slice.AllowedMemoryNodes
org.freedesktop.systemd1.Slice.AttachProcesses() org.freedesktop.systemd1.Slice.AttachProcesses()
org.freedesktop.systemd1.Slice.BPFProgram org.freedesktop.systemd1.Slice.BPFProgram
org.freedesktop.systemd1.Slice.BindNetworkInterface
org.freedesktop.systemd1.Slice.BlockIOAccounting org.freedesktop.systemd1.Slice.BlockIOAccounting
org.freedesktop.systemd1.Slice.BlockIODeviceWeight org.freedesktop.systemd1.Slice.BlockIODeviceWeight
org.freedesktop.systemd1.Slice.BlockIOReadBandwidth org.freedesktop.systemd1.Slice.BlockIOReadBandwidth
@ -1487,6 +1491,7 @@ org.freedesktop.systemd1.Socket.AttachProcesses()
org.freedesktop.systemd1.Socket.BPFProgram org.freedesktop.systemd1.Socket.BPFProgram
org.freedesktop.systemd1.Socket.Backlog org.freedesktop.systemd1.Socket.Backlog
org.freedesktop.systemd1.Socket.BindIPv6Only org.freedesktop.systemd1.Socket.BindIPv6Only
org.freedesktop.systemd1.Socket.BindNetworkInterface
org.freedesktop.systemd1.Socket.BindPaths org.freedesktop.systemd1.Socket.BindPaths
org.freedesktop.systemd1.Socket.BindReadOnlyPaths org.freedesktop.systemd1.Socket.BindReadOnlyPaths
org.freedesktop.systemd1.Socket.BindToDevice org.freedesktop.systemd1.Socket.BindToDevice
@ -1786,6 +1791,7 @@ org.freedesktop.systemd1.Swap.AmbientCapabilities
org.freedesktop.systemd1.Swap.AppArmorProfile org.freedesktop.systemd1.Swap.AppArmorProfile
org.freedesktop.systemd1.Swap.AttachProcesses() org.freedesktop.systemd1.Swap.AttachProcesses()
org.freedesktop.systemd1.Swap.BPFProgram org.freedesktop.systemd1.Swap.BPFProgram
org.freedesktop.systemd1.Swap.BindNetworkInterface
org.freedesktop.systemd1.Swap.BindPaths org.freedesktop.systemd1.Swap.BindPaths
org.freedesktop.systemd1.Swap.BindReadOnlyPaths org.freedesktop.systemd1.Swap.BindReadOnlyPaths
org.freedesktop.systemd1.Swap.BlockIOAccounting org.freedesktop.systemd1.Swap.BlockIOAccounting