binfmt:
- changed-files:
- any-glob-to-any-file: '**/*binfmt*'
+bsod:
+ - changed-files:
+ - any-glob-to-any-file: '**/*bsod*'
btrfs:
- changed-files:
- any-glob-to-any-file: '**/*btrfs*'
growfs:
- changed-files:
- any-glob-to-any-file: '**/*growfs*'
+hibernate-resume:
+ - changed-files:
+ - any-glob-to-any-file: '**/*hibernate-resume*'
hwdb:
- changed-files:
- any-glob-to-any-file: 'hwdb.d/**/*'
network:
- changed-files:
- any-glob-to-any-file: ['src/libsystemd-network/**/*', 'src/network/**/*']
+nspawn:
+ - changed-files:
+ - any-glob-to-any-file: '**/*nspawn*'
portable:
- changed-files:
- any-glob-to-any-file: 'src/portable/**/*'
vconsole:
- changed-files:
- any-glob-to-any-file: '**/*vconsole*'
+vmspawn:
+ - changed-files:
+ - any-glob-to-any-file: '**/*vmspawn*'
xdg-autostart:
- changed-files:
- any-glob-to-any-file: '**/**xdg-autostart-generator*'
ARGS=(
"--optimization=0 -Dopenssl=disabled -Dcryptolib=gcrypt -Ddns-over-tls=gnutls -Dtpm=true -Dtpm2=enabled"
"--optimization=s -Dutmp=false"
+ "--optimization=2 -Dc_args=-Wmaybe-uninitialized -Ddns-over-tls=openssl"
"--optimization=3 -Db_lto=true -Ddns-over-tls=false"
"--optimization=3 -Db_lto=false -Dtpm2=disabled -Dlibfido2=disabled -Dp11kit=disabled"
- "--optimization=3 -Ddns-over-tls=openssl"
"--optimization=3 -Dfexecve=true -Dstandalone-binaries=true -Dstatic-libsystemd=true -Dstatic-libudev=true"
"-Db_ndebug=true"
)
for args in "${ARGS[@]}"; do
SECONDS=0
+ if [[ "$COMPILER" == clang && "$args" =~ Wmaybe-uninitialized ]]; then
+ # -Wmaybe-uninitialized is not implemented in clang
+ continue
+ fi
+
info "Checking build with $args"
# shellcheck disable=SC2086
if ! AR="$AR" \
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- - uses: systemd/mkosi@bbe715f42911f9660712377a5b39335b9391ae22
+ - uses: systemd/mkosi@dbce89aabda438ba58080366631b2c242e365f21
- name: Configure
run: |
- tee mkosi.local.conf <<- EOF
+ tee mkosi.local.conf <<EOF
[Distribution]
Distribution=${{ matrix.distro }}
Release=${{ matrix.release }}
- EOF
- tee mkosi.conf.d/99-ci.conf <<- EOF
+ [Output]
+ # Build a disk image in CI as this logic is much more prone to breakage.
+ Format=disk
+
[Content]
Environment=CI_BUILD=1
SLOW_TESTS=true
+ [Host]
+ ToolsTree=default
+ ToolsTreeDistribution=fedora
+ QemuVsock=yes
+ # Sometimes we run on a host with /dev/kvm, but it is broken, so explicitly disable it
+ QemuKvm=no
+ Ephemeral=yes
+ EOF
+
+ # These should override the options from mkosi.conf so we put them in a dropin that's ordered later
+ # instead.
+ tee mkosi.conf.d/99-ci.conf <<EOF
[Host]
KernelCommandLineExtra=systemd.unit=mkosi-check-and-shutdown.service
systemd.journald.max_level_console=debug
udev.log_level=info
# Root device can take a long time to appear, so let's bump the timeout.
systemd.default_device_timeout_sec=180
- QemuVsock=yes
- # Sometimes we run on a host with /dev/kvm, but it is broken, so explicitly disable it
- QemuKvm=no
- Ephemeral=yes
EOF
# For erofs, we have to install linux-modules-extra-azure, but that doesn't match the running kernel
# version, so we can't load the erofs module. squashfs is a builtin module so we use that instead.
mkdir -p mkosi.images/system/mkosi.repart/10-usr.conf.d
- tee mkosi.images/system/mkosi.repart/10-usr.conf.d/squashfs.conf <<- EOF
+ tee mkosi.images/system/mkosi.repart/10-usr.conf.d/squashfs.conf <<EOF
[Partition]
Format=squashfs
EOF
# eventually times out. Override it to just shutdown immediately.
mkdir -p mkosi.images/initrd/mkosi.extra/usr/lib/systemd/system/emergency.service.d/
mkdir -p mkosi.images/system/mkosi.extra/usr/lib/systemd/system/emergency.service.d/
- tee mkosi.images/initrd/mkosi.extra/usr/lib/systemd/system/emergency.service.d/poweroff.conf <<- EOF
+ tee mkosi.images/initrd/mkosi.extra/usr/lib/systemd/system/emergency.service.d/poweroff.conf <<EOF
[Unit]
FailureAction=exit
[Service]
python3-pefile
python3-pyelftools
python3-pyparsing
+ python3-pytest
rpm
zstd
)
Features:
+* extend the smbios11 logic for passing credentials so that instead of passing
+ the credential data literally it can also just reference an AF_VSOCK CID/port
+ to read them from. This way the data doesn't remain in the SMBIOS blob during
+ runtime, but only in the credentials fs.
+
+* machined: make machine registration available via varlink to simplify
+ nspawn/vmspawn, and to have an extensible way to register VM/machine metadata
+
+* ssh-proxy: add support for "ssh machine/foobar" to automatically connect to
+ machined registered machine "foobar". Requires updating machined to track CID
+ and unix-export dir of containers.
+
* add a new ExecStart= flag that inserts the configured user's shell as first
word in the command line. (maybe use character '.'). Usecase: tool such as
uid0 can use that to spawn the target user's default shell.
the realized cgroup, to pin it (and later execute all cgroup operations over,
once we drop cgroupv1 compat).
-* add new "systemd-ssh-generator", which allows basic ssh config via
- credentials (host key). It generates sshd.socket for IP, but also
- sshd-vsock.socket for listening on AF_VSOCK when running in a VM, and
- sshd-unix.socket on AF_UNIX when running in a container. It also generates a
- matching sshd.service file with a host key passed in on the cmdline via
- credentials. Then, add a ssh_config drop-in that matches some suitable
- hostname pattern and has a ProxyCommand set that allows connecting to any
- local VM/container that way without any networking configured.
-
* Varlinkification of the following command line tools, to open them up to
other programs via IPC:
- bootctl
* teach systemd-nspawn the boot assessment logic: hook up vpick's try counters
with success notifications from nspawn payloads. When this is enabled,
- automatically support reverting back to older OS versin images if newer ones
+ automatically support reverting back to older OS version images if newer ones
fail to boot.
* implement new "systemd-fsrebind" tool that works like gpt-auto-generator but
would just use the same public key specified with --public-key= (or the one
automatically derived from --private-key=).
-* push people to use ".sysext.raw" as suffix for sysext DDIs (DDI =
- discoverable disk images, i.e. the new name for gpt disk images following the
- discoverable disk spec). [Also: just ".sysext/" for directory-based sysext]
-
* Add "purpose" flag to partition flags in discoverable partition spec that
indicate if partition is intended for sysext, for portable service, for
booting and so on. Then, when dissecting DDI allow specifying a purpose to
should probably also one you can use to get a remote attestation quote.
* Process credentials in:
- • networkd/udevd: add a way to define additional .link, .network, .netdev files
- via the credentials logic.
• crypttab-generator: allow defining additional crypttab-like volumes via
credentials (similar: verity-generator, integrity-generator). Use
fstab-generator logic as inspiration.
.p7s is available in the image, use it to protect the system.attached copy
with fs-verity, so that it cannot be tampered with
-* logind introduce two types of sessions: "heavy" and "light". The former would
- be our current sessions. But the latter would be a new type of session that
- is mostly the same but does not pull in user@.service or wait for it. Then,
- allow configuration which type of session is desired via pam_systemd
- parameters, and then make user@.service's session one of these "light" ones.
- People could then choose to make FTP sessions and suchlike "light" if they
- don't want the service manager to be started for that.
-
* /etc/veritytab: allow that the roothash column can be specified as fs path
including a path to an AF_UNIX path, similar to how we do things with the
keys of /etc/crypttab. That way people can store/provide the roothash
- acquire + decrypt creds from pkcs11?
- make systemd-cryptsetup acquire pw via creds logic
- make PAMName= acquire pw via creds logic
- - make macsec/wireguard code in networkd read key via creds logic
- - make gatwayd/remote read key via creds logic
+ - make macsec code in networkd read key via creds logic (copy logic from
+ wireguard)
+ - make gatewayd/remote read key via creds logic
- add sd_notify() command for flushing out creds not needed anymore
- make user manager instances create and use a user-specific key (the one in
/var/lib is root-only) and add --user switch to systemd-creds to use it
* man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted.
-* load .d/*.conf dropins for device units
-
* There's currently no way to cancel fsck (used to be possible via C-c or c on the console)
* add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/
* systemctl:
- add systemctl switch to dump transaction without executing it
- Add a verbose mode to "systemctl start" and friends that explains what is being done or not done
- - "systemctl disable" on a static unit prints no message and does
- nothing. "systemctl enable" does nothing, and gives a bad message
- about it. Should fix both to print nice actionable messages.
- print nice message from systemctl --failed if there are no entries shown, and hook that into ExecStartPre of rescue.service/emergency.service
- add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible
- systemctl enable: fail if target to alias into does not exist? maybe show how many units are enabled afterwards?
- systemctl: "Journal has been rotated since unit was started." message is misleading
- - systemctl status output should include list of triggering units and their status
* introduce an option (or replacement) for "systemctl show" that outputs all
properties as JSON, similar to busctl's new JSON output. In contrast to that
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+@@
+/* Avoid running this transformation on the mfree function itself */
+position p : script:python() { p[0].current_element != "mfree" };
+expression e;
+@@
+- free@p(e);
+- return NULL;
++ return mfree(e);
+
@@
expression p;
@@
- free(p);
- p = NULL;
+ p = mfree(p);
+
+@@
+expression p;
+@@
+- if (p)
+- free(p);
++ free(p);
+
+@@
+expression p;
+@@
+- if (p)
+- mfree(p);
++ free(p);
+
+@@
+expression p;
+@@
+- mfree(p);
++ free(p);
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-@@
-/* Avoid running this transformation on the mfree function itself */
-position p : script:python() { p[0].current_element != "mfree" };
-expression e;
-@@
-- free@p(e);
-- return NULL;
-+ return mfree(e);
7. The `/run/host/credentials/` directory is a good place to pass credentials
into the container, using the `$CREDENTIALS_DIRECTORY` protocol, see above.
+8. The `/run/host/unix-export/` directory shall be writable from the container
+ payload, and is where container payload can bind `AF_UNIX` sockets in that
+ shall be *exported* to the host, so that the host can connect to them. The
+ container manager should bind mount this directory on the host side
+ (read-only ideally), so that the host can connect to contained sockets. This
+ is most prominently used by `systemd-ssh-generator` when run in such a
+ container to automatically bind an SSH socket into that directory, which
+ then can be used to connect to the container.
+
+9. The `/run/host/unix-export/ssh` `AF_UNIX` socket will be automatically bound
+ by `systemd-ssh-generator` in the container if possible, and can be used to
+ connect to the container.
+
+10. The `/run/host/userdb/` directory may be used to drop-in additional JSON
+ user records that `nss-systemd` inside the container shall include in the
+ system's user database. This is useful to make host users and their home
+ directories automatically accessible to containers in transitive
+ fashion. See `nss-systemd(8)` for details.
+
+11. The `/run/host/home/` directory may be used to bind mount host home
+ directories of users that shall be made available in the container to. This
+ may be used in combination with `/run/host/userdb/` above: one defines the
+ user record, the other contains the user's home directory.
+
## What You Shouldn't Do
1. Do not drop `CAP_MKNOD` from the container. `PrivateDevices=` is a commonly
udev manager process waits for a worker process kills slow programs specified
by IMPORT{program}=, PROGRAM=, or RUN=, and finalizes the processing event.
If the worker process cannot finalize the event within the specified timespan,
- the worker process is killed by the manager process. Defaults to 10 seconds.
+ the worker process is killed by the manager process. Defaults to 10 seconds,
+ maximum allowed is 5 hours.
`udevadm` and `systemd-hwdb`:
latter two via the environment variable unless `systemd-storagetm` is invoked
to expose a single device only, since those identifiers better should be kept
unique.
+
+Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as
+`busctl`):
+
+* `$SYSTEMD_SSH` – the ssh binary to invoke when the `ssh:` transport is
+ used. May be a filename (which is searched for in `$PATH`) or absolute path.
+
+* `$SYSTEMD_VARLINK_LISTEN` – interpreted by some tools that provide a Varlink
+ service. Takes a file system path: if specified the tool will listen on an
+ `AF_UNIX` stream socket on the specified path in addition to whatever else it
+ would listen on.
Every time you rerun the `mkosi` command a fresh image is built, incorporating
all current changes you made to the project tree.
+By default a directory image is built. This requires `virtiofsd` to be installed
+on the host. To build a disk image instead which does not require `virtiofsd`,
+add the following to `mkosi.local.conf`:
+
+```conf
+[Output]
+Format=disk
+```
+
+To boot in UEFI mode instead of using QEMU's direct kernel boot, add the following
+to `mkosi.local.conf`:
+
+```conf
+[Host]
+QemuFirmware=uefi
+```
+
Putting this all together, here's a series of commands for preparing a patch
for systemd:
sensor:modalias:acpi:BOSC0200*:dmi:*:svnHampoo*:pnC3W6_AP108_4GB:*
ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1
+# Chuwi Ubook X (CWI535)
+sensor:modalias:acpi:MXC6655*:dmi*:svnCHUWIInnovationAndTechnology*:pnUBookX:*
+ ACCEL_MOUNT_MATRIX=0, 0, -1; 1, 0, 0; 0, 1, 0
+
#########################################
# Connect
#########################################
sensor:modalias:acpi:KIOX000A*:dmi:*:svnTECLAST:pnX98PlusII:*
ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
+# Teclast X98 Pro
+sensor:modalias:acpi:BMA250E*:dmi:*:svnTECLAST:pnX98Pro:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+
#########################################
# Thundersoft
#########################################
<para>When invoked with the <command>firstboot</command> command, <command>homectl</command> supports the
service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
- (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist class='system-credentials'>
<xi:include href="version-info.xml" xpointer="v217"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-T</option></term>
+ <term><option>--exclude-identifier=<replaceable>SYSLOG_IDENTIFIER</replaceable></option></term>
+
+ <listitem><para>Exclude messages for the specified syslog identifier
+ <replaceable>SYSLOG_IDENTIFIER</replaceable>.</para>
+
+ <para>This parameter can be specified multiple times.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-p</option></term>
<term><option>--priority=</option></term>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>systemd.ssh_auto=</varname></term>
+ <term><varname>systemd.ssh_listen=</varname></term>
+ <listitem>
+ <para>These parameters are interpreted by
+ <citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ and may be used to control SSH sockets the system shall be reachable on.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>systemd.volatile=</varname></term>
<listitem>
<varlistentry>
<term><command>list-sessions</command></term>
- <listitem><para>List current sessions.</para></listitem>
+ <listitem><para>List current sessions. The JSON format output can be toggled using <option>--json=</option>
+ or <option>-j</option> option.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><command>list-users</command></term>
- <listitem><para>List currently logged in users.
- </para></listitem>
+ <listitem><para>List currently logged in users. The JSON format output can be toggled using
+ <option>--json=</option> or <option>-j</option> option.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><command>list-seats</command></term>
- <listitem><para>List currently available seats on the local
- system.</para></listitem>
+ <listitem><para>List currently available seats on the local system. The JSON format output can be
+ toggled using <option>--json=</option> or <option>-j</option> option.</para></listitem>
</varlistentry>
<varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />
+ <xi:include href="standard-options.xml" xpointer="json" />
+ <xi:include href="standard-options.xml" xpointer="j" />
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
readonly ay MachineID = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly ay BootID = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly u VSockCID = ...;
};
interface org.freedesktop.DBus.Peer { ... };
interface org.freedesktop.DBus.Introspectable { ... };
<!--property FirmwareDate is not documented!-->
- <!--property MachineID is not documented!-->
-
- <!--property BootID is not documented!-->
-
<!--Autogenerated cross-references for systemd.directives, do not edit-->
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.hostname1"/>
<variablelist class="dbus-property" generated="True" extra-ref="BootID"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="VSockCID"/>
+
<!--End of Autogenerated section-->
<para>Whenever the hostname or other metadata is changed via the daemon,
purpose of those properties is to allow remote clients to access this information over D-Bus. Local
clients can access the information directly.</para>
+ <para><varname>MachineID</varname> expose the 128bit machine ID, see
+ <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details.</para>
+
+ <para><varname>BootID</varname> expose the 128bit boot ID, as per
+ <filename>/proc/sys/kernel/random/boot_id</filename>.</para>
+
+ <para><varname>VSockCID</varname> exposes the system's local <constant>AF_VSOCK</constant> CID (Context
+ Identifier, i.e. address) for the system, if one is available in the virtual machine environment. Set to
+ <constant>UINT32_MAX</constant> otherwise. See <citerefentry project="man-pages"><refentrytitle>vsock</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
+ details.</para>
+
<refsect2>
<title>Methods</title>
<para><varname>OperatingSystemSupportEnd</varname>,
<varname>FirmwareVendor</varname>, and
<varname>FirmwareDate</varname> were added in version 253.</para>
- <para><varname>MachineID</varname>, and
- <varname>BootID</varname> were added in version 256.</para>
+ <para><varname>MachineID</varname>, <varname>BootID</varname> and
+ <varname>VSockCID</varname> were added in version 256.</para>
</refsect2>
</refsect1>
</refentry>
GetSeat(in s seat_id,
out o object_path);
ListSessions(out a(susso) sessions);
+ ListSessionsEx(out a(sussussbto) sessions);
ListUsers(out a(uso) users);
ListSeats(out a(so) seats);
ListInhibitors(out a(ssssuu) inhibitors);
readonly (st) ScheduledShutdown = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b Docked = ...;
- @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b LidClosed = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b OnExternalPower = ...;
<variablelist class="dbus-method" generated="True" extra-ref="ListSessions()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="ListSessionsEx()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="ListUsers()"/>
<variablelist class="dbus-method" generated="True" extra-ref="ListSeats()"/>
is any.</para>
<para><function>ListSessions()</function> returns an array of all current sessions. The structures in
- the array consist of the following fields: session id, user id, user name, seat id, session object
- path. If a session does not have a seat attached, the seat id field will be an empty string.</para>
+ the array consist of the following fields: <varname>session id</varname>, <varname>user id</varname>,
+ <varname>user name</varname>, <varname>seat id</varname>, and <varname>session object path</varname>.
+ If a session does not have a seat attached, the seat id field will be an empty string.</para>
+
+ <para><function>ListSessionsEx()</function> returns an array of all current sessions with more metadata
+ than <function>ListSessions()</function>. The structures in the array consist of the following fields:
+ <varname>session id</varname>, <varname>user id</varname>, <varname>user name</varname>,
+ <varname>seat id</varname>, <varname>leader pid</varname>, <varname>session class</varname>,
+ <varname>tty name</varname>, <varname>idle hint</varname>, <varname>idle hint monotonic timestamp</varname>,
+ and <varname>session object path</varname>. <varname>tty</varname> and <varname>seat id</varname> fields
+ could be empty, if the session has no associated tty or session has no seat attached, respectively.</para>
<para><function>ListUsers()</function> returns an array of all currently logged in users. The
structures in the array consist of the following fields: user id, user name, user object path.</para>
<para><function>PrepareForShutdownWithMetadata</function> and
<function>CreateSessionWithPIDFD()</function> were added in version 255.</para>
<para><function>Sleep()</function>,
- <function>CanSleep()</function>, and
- <varname>SleepOperation</varname> were added in version 256.</para>
+ <function>CanSleep()</function>,
+ <varname>SleepOperation</varname>, and
+ <function>ListSessionsEx()</function> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Session Objects</title>
readonly s OnlineState = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t NamespaceId = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly u NamespaceNSID = ...;
};
interface org.freedesktop.DBus.Peer { ... };
interface org.freedesktop.DBus.Introspectable { ... };
<!--property OnlineState is not documented!-->
- <!--property NamespaceId is not documented!-->
-
<!--Autogenerated cross-references for systemd.directives, do not edit-->
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.network1.Manager"/>
<variablelist class="dbus-property" generated="True" extra-ref="NamespaceId"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="NamespaceNSID"/>
+
<!--End of Autogenerated section-->
<para>
Provides information about the manager.
</para>
+
+ <refsect2>
+ <title>Properties</title>
+
+ <para><varname>NamespaceId</varname> contains the inode number of the network namespace that the
+ network service runs in. A client may compare this with the inode number of its own network namespace
+ to verify whether the service manages the same network namespace.</para>
+
+ <para><varname>NamespaceNSID</varname> contains the "nsid" identifier the kernel maintains for the
+ network namespace, if there's one assigned.</para>
+ </refsect2>
</refsect1>
<refsect1>
<title>DHCPv6 Client Object</title>
<para><varname>State</varname> was added in version 255.</para>
</refsect2>
+ <refsect2>
+ <title>Manager Object</title>
+ <para><varname>NamespaceNSID</varname> was added in version 256.</para>
+ </refsect2>
</refsect1>
</refentry>
enqueued and complete successfully. The key value pairs correspond (in lowercase) to the environment
variables described in the <literal>Environment Variables Set or Propagated by the Service
Manager</literal> section in
- <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry>. Note
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Note
that new key value pair may be added at any time in future versions. Existing entries will not be
removed.</para>
</refsect2>
<varlistentry>
<term><varname>class=</varname></term>
- <listitem><para>Takes a string argument which sets the session class. The <varname>XDG_SESSION_CLASS</varname>
- environment variable (see below) takes precedence. One of <literal>user</literal>, <literal>greeter</literal>,
- <literal>lock-screen</literal> or <literal>background</literal>. See
- <citerefentry><refentrytitle>sd_session_get_class</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
- details about the session class.</para>
+ <listitem><para>Takes a string argument which sets the session class. The
+ <varname>XDG_SESSION_CLASS</varname> environment variable (see below) takes precedence. See
+ <citerefentry><refentrytitle>sd_session_get_class</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+ for a way to query the class of a session. The following session classes are defined:</para>
+
+ <table>
+ <title>Session Classes</title>
+ <tgroup cols='2' align='left' colsep='1' rowsep='1'>
+ <colspec colname="name" />
+ <colspec colname="explanation" />
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Explanation</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><constant>user</constant></entry>
+ <entry>A regular interactive user session. This is the default class for sessions for which a TTY or X display is known at session registration time.</entry>
+ </row>
+ <row>
+ <entry><constant>user-early</constant></entry>
+ <entry>Similar to <literal>user</literal> but sessions of this class are not ordered after <filename>systemd-user-sessions.service</filename>, i.e. may be started before regular sessions are allowed to be established. This session class is the default for sessions of the root user that would otherwise qualify for the <constant>user</constant> class, see above. (Added in v256.)</entry>
+ </row>
+ <row>
+ <entry><constant>greeter</constant></entry>
+ <entry>Similar to <literal>user</literal> but for sessions that are spawned by a display manager ephemerally and which prompt the user for login credentials.</entry>
+ </row>
+ <row>
+ <entry><constant>lock-screen</constant></entry>
+ <entry>Similar to <literal>user</literal> but for sessions that are spawned by a display manager ephemerally and which show a lock screen that can be used to unlock locked user accounts or sessions.</entry>
+ </row>
+ <row>
+ <entry><constant>background</constant></entry>
+ <entry>Used for background sessions, such as those invoked by <command>cron</command> and similar tools. This is the default class for sessions for which no TTY or X display is known at session registration time.</entry>
+ </row>
+ <row>
+ <entry><constant>background-light</constant></entry>
+ <entry>Similar to <constant>background</constant>, but sessions of this class will not pull in the <filename>user@.service</filename> of the user, and thus possibly have no services of the user running. (Added in v256.)</entry>
+ </row>
+ <row>
+ <entry><constant>manager</constant></entry>
+ <entry>The <filename>user@.service</filename> service of the user is registered under this session class. (Added in v256.)</entry>
+ </row>
+ <row>
+ <entry><constant>manager-early</constant></entry>
+ <entry>Similar to <constant>manager</constant>, but for the root user. Compare with the <constant>user</constant> vs. <constant>user-early</constant> situation. (Added in v256.)</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
<xi:include href="version-info.xml" xpointer="v197"/></listitem>
</varlistentry>
['systemd-socket-activate', '1', [], ''],
['systemd-socket-proxyd', '8', [], ''],
['systemd-soft-reboot.service', '8', [], ''],
+ ['systemd-ssh-generator', '8', [], ''],
+ ['systemd-ssh-proxy', '1', [], ''],
['systemd-stdio-bridge', '1', [], ''],
['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'],
['systemd-stub',
['timesyncd.conf', '5', ['timesyncd.conf.d'], 'ENABLE_TIMESYNCD'],
['tmpfiles.d', '5', [], ''],
['udev', '7', [], ''],
- ['udev.conf', '5', [], ''],
+ ['udev.conf', '5', ['udev.conf.d'], ''],
['udev_device_get_syspath',
'3',
['udev_device_get_action',
<citerefentry project='man-pages'><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
call after use.</para>
- <para><function>sd_session_get_class()</function> may be used to
- determine the class of the session identified by the specified
- session identifier. The returned string is one of
- <literal>user</literal>, <literal>greeter</literal>,
- <literal>lock-screen</literal>, or <literal>background</literal>
- and needs to be freed with the libc
- <citerefentry project='man-pages'><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
- call after use.</para>
+ <para><function>sd_session_get_class()</function> may be used to determine the class of the session
+ identified by the specified session identifier. The returned string is one of <literal>user</literal>,
+ <literal>user-early</literal>, <literal>greeter</literal>, <literal>lock-screen</literal>,
+ <literal>background</literal>, <literal>background-light</literal>, <literal>manager</literal> or
+ <literal>manager-early</literal> and needs to be freed with the libc <citerefentry
+ project='man-pages'><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry> call after
+ use.</para>
<para><function>sd_session_get_desktop()</function> may be used to
determine the brand of the desktop running on the session
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
-<variablelist
- xmlns:xi="http://www.w3.org/2001/XInclude">
-
+<variablelist>
<varlistentry id='help'>
<term><option>-h</option></term>
<term><option>--help</option></term>
</listitem>
</varlistentry>
- <varlistentry id='no-pager-255'>
- <term><option>--no-pager</option></term>
-
- <listitem>
- <para>Do not pipe output into a pager.</para>
-
- <xi:include href="version-info.xml" xpointer="v255"/>
- </listitem>
- </varlistentry>
-
<varlistentry id='no-ask-password'>
<term><option>--no-ask-password</option></term>
off JSON output, the default).</para></listitem>
</varlistentry>
+ <varlistentry id='j'>
+ <term><option>-j</option></term>
+
+ <listitem><para>Equivalent to <option>--json=pretty</option> if running on a terminal, and
+ <option>--json=short</option> otherwise.</para></listitem>
+ </varlistentry>
+
<varlistentry id='signal'>
<term><option>-s</option></term>
<term><option>--signal=</option></term>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">verify</arg>
- <arg choice="opt" rep="repeat"><replaceable>FILE</replaceable></arg>
+ <arg choice="plain" rep="repeat"><replaceable>FILE</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">security</arg>
- <arg choice="plain" rep="repeat"><replaceable>UNIT</replaceable></arg>
+ <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">fdstore</arg>
- <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
+ <arg choice="plain" rep="repeat"><replaceable>UNIT</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
</refsect2>
<refsect2>
- <title><command>systemd-analyze fdstore <optional><replaceable>UNIT</replaceable>...</optional></command></title>
+ <title><command>systemd-analyze fdstore <replaceable>UNIT</replaceable>...</command></title>
<para>Lists the current contents of the specified service unit's file descriptor store. This shows
names, inode types, device numbers, inode numbers, paths and open modes of the open file
</refsect2>
<refsect2>
- <title><command>systemd-analyze image-policy <optional><replaceable>POLICY</replaceable>…</optional></command></title>
+ <title><command>systemd-analyze image-policy <replaceable>POLICY</replaceable>…</command></title>
<para>This command analyzes the specified image policy string, as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
<varlistentry>
<term><option>--root=<replaceable>PATH</replaceable></option></term>
- <listitem><para>With <command>cat-files</command> and <command>verify</command>,
- operate on files underneath the specified root path <replaceable>PATH</replaceable>.</para>
+ <listitem><para>With <command>cat-config</command>, <command>verify</command>,
+ <command>condition</command> and <command>security</command> when used with
+ <option>--offline=</option>, operate on files underneath the specified root path
+ <replaceable>PATH</replaceable>.</para>
<xi:include href="version-info.xml" xpointer="v239"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--image=<replaceable>PATH</replaceable></option></term>
- <listitem><para>With <command>cat-files</command> and <command>verify</command>,
- operate on files inside the specified image path <replaceable>PATH</replaceable>.</para>
+ <listitem><para>With <command>cat-config</command>, <command>verify</command>,
+ <command>condition</command> and <command>security</command> when used with
+ <option>--offline=</option>, operate on files inside the specified image path
+ <replaceable>PATH</replaceable>.</para>
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
</varlistentry>
<xi:include href="user-system-options.xml" xpointer="machine" />
<varlistentry>
+ <term><option>-q</option></term>
<term><option>--quiet</option></term>
<listitem><para>Suppress hints and other non-essential output.</para>
<listitem><para>When specified with the <command>encrypt</command> command controls the
encryption/signature key to use. Takes one of <literal>host</literal>, <literal>tpm2</literal>,
- <literal>host+tpm2</literal>, <literal>tpm2-absent</literal>, <literal>auto</literal>,
+ <literal>host+tpm2</literal>, <literal>null</literal>, <literal>auto</literal>,
<literal>auto-initrd</literal>. See above for details on the three key types. If set to
<literal>auto</literal> (which is the default) the TPM2 key is used if a TPM2 device is found and not
running in a container. The host key is used if <filename>/var/lib/systemd/</filename> is on
chip and the OS installation, and both need to be available to decrypt the credential again. If
<literal>auto</literal> is selected but neither TPM2 is available (or running in container) nor
<filename>/var/lib/systemd/</filename> is on persistent media, encryption will fail. If set to
- <literal>tpm2-absent</literal> a fixed zero length key is used (thus, in this mode no confidentiality
+ <literal>null</literal> a fixed zero length key is used (thus, in this mode no confidentiality
nor authenticity are provided!). This logic is useful to cover for systems that lack a TPM2 chip but
where credentials shall be generated. Note that decryption of such credentials is refused on systems
that have a TPM2 chip and where UEFI SecureBoot is enabled (this is done so that such a locked down
system cannot be tricked into loading a credential generated this way that lacks authentication
information). If set to <literal>auto-initrd</literal> a TPM2 key is used if a TPM2 is found. If not
- a fixed zero length key is used, equivalent to <literal>tpm2-absent</literal> mode. This option is
+ a fixed zero length key is used, equivalent to <literal>null</literal> mode. This option is
particularly useful to generate credentials files that are encrypted/authenticated against TPM2 where
available but still work on systems lacking support for this.</para>
<varlistentry>
<term><option>--pkcs11-token-uri=</option><replaceable>URI</replaceable></term>
- <listitem><para>Enroll a PKCS#11 security token or smartcard (e.g. a YubiKey). Expects a PKCS#11
- smartcard URI referring to the token. Alternatively the special value <literal>auto</literal> may
- be specified, in order to automatically determine the URI of a currently plugged in security token
- (of which there must be exactly one). The special value <literal>list</literal> may be used to
- enumerate all suitable PKCS#11 tokens currently plugged in.</para>
+ <listitem><para>Enroll a PKCS#11 security token or smartcard (e.g. a YubiKey). Expects a PKCS#11 URI
+ that allows to find an X.509 certificate on the token. The URI must also be suitable to find
+ a related private key after changing the type of object in it. Alternatively the special value
+ <literal>auto</literal> may be specified, in order to automatically determine the suitable URI if
+ a single security token containing a single key pair is plugged in. The special value
+ <literal>list</literal> may be used to enumerate all suitable PKCS#11 tokens currently plugged in.
+ </para>
<para>The PKCS#11 token must contain an RSA or EC key pair which will be used to unlock a LUKS2 volume.
For RSA, a randomly generated volume key is encrypted with a public key in the token, and stored in
<para><command>systemd-cryptsetup</command> supports the service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
- (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used by <literal>systemd-crypsetup@root.service</literal>
(generated by <command>systemd-gpt-auto-generator</command>) when passed in:</para>
<row>
<entry><varname>apple</varname></entry>
- <entry><ulink url="https://developer.apple.com/documentation/virtualization">Apple Virtualization.framework</ulink></entry>
+ <entry><ulink url="https://developer.apple.com/documentation/virtualization">Apple virtualization framework</ulink></entry>
</row>
<row>
<entry><ulink url="https://www.lockheedmartin.com/en-us/products/Hardened-Security-for-Intel-Processors.html">LMHS SRE hypervisor</ulink></entry>
</row>
+ <row>
+ <entry><varname>google</varname></entry>
+ <entry><ulink url="https://cloud.google.com/compute">Google Compute Engine</ulink></entry>
+ </row>
+
<row>
<entry valign="top" morerows="9">Container</entry>
<entry><varname>openvz</varname></entry>
<para><command>systemd-firstboot</command> supports the service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
- (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist class='system-credentials'>
<xi:include href="version-info.xml" xpointer="v244"/></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="no-pager" />
+ <xi:include href="standard-options.xml" xpointer="no-legend" />
+ <xi:include href="standard-options.xml" xpointer="json" />
+ <xi:include href="standard-options.xml" xpointer="j" />
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
for option syntax and details.</para>
</refsect1>
+ <refsect1>
+ <title>Credentials</title>
+
+ <para><command>systemd-network-generator</command> supports the service credentials logic as implemented
+ by
+ <varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details). The following credentials are used when passed in:</para>
+
+ <variablelist class='system-credentials'>
+ <varlistentry>
+ <term><varname>network.netdev.*</varname></term>
+ <term><varname>network.link.*</varname></term>
+ <term><varname>network.network.*</varname></term>
+
+ <listitem><para>These credentials should contain valid
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ configuration data. From each matching credential a separate file is created. Example: a passed
+ credential <filename>network.link.50-foobar</filename> will be copied into a configuration file
+ <filename>50-foobar.link</filename>.</para>
+
+ <para>Note that the resulting files are created world-readable, it's hence recommended to not include
+ secrets in these credentials, but supply them via separate credentials directly to
+ <filename>systemd-networkd.service</filename>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>Note that by default the <filename>systemd-network-generator.service</filename> unit file is set up
+ to inherit the these credentials from the service manager.</para>
+ </refsect1>
+
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<xi:include href="version-info.xml" xpointer="v215"/></listitem>
</varlistentry>
- <xi:include href="standard-options.xml" xpointer="no-pager-255"/>
+ <xi:include href="standard-options.xml" xpointer="no-pager"/>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
<para><command>systemd-resolved</command> supports the service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
- (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist class='system-credentials'>
<term>suspend-then-hibernate</term>
<listitem>
- <para>A low power state where the system is initially suspended (the state is stored in
- RAM). If the system supports low-battery alarms (ACPI _BTP), then the system will be woken up by
- the ACPI low-battery signal and hibernated (the state is then stored on disk). Also, if not
- interrupted within the timespan specified by <varname>HibernateDelaySec=</varname> or the estimated
- timespan until the system battery charge level goes down to 5%, then the system will be woken up by the
- RTC alarm and hibernated. The estimated timespan is calculated from the change of the battery
- capacity level after the time specified by <varname>SuspendEstimationSec=</varname> or when
- the system is woken up from the suspend.</para>
+ <para>A low power state where the system is initially suspended (the state is stored in RAM).
+ When the battery level is too low (less than 5%) or a certain timespan has passed, whichever
+ happens first, the system is automatically woken up and then hibernated. This establishes a balance
+ between speed and safety.</para>
+
+ <para>If the system has no battery, it would be hibernated after <varname>HibernateDelaySec=</varname>
+ has passed. If not set, then defaults to <literal>2h</literal>.</para>
+
+ <para>If the system has battery and <varname>HibernateDelaySec=</varname> is not set, low-battery
+ alarms (ACPI _BTP) are tried first for detecting battery percentage and wake up the system for hibernation.
+ If not available, or <varname>HibernateDelaySec=</varname> is set, the system would regularly wake
+ up to check the time and detect the battery percentage/discharging rate. The rate is used to
+ schedule the next detection. If that is also not available, <varname>SuspendEstimationSec=</varname>
+ is used as last resort.</para>
<xi:include href="version-info.xml" xpointer="v239"/>
</listitem>
<para>The amount of time the system spends in suspend mode before the system is
automatically put into hibernate mode. Only used by
<citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
- If the system has a battery, then defaults to the estimated timespan until the system battery charge level goes down to 5%.
- If the system has no battery, then defaults to 2h.</para>
+ Refer to <command>suspend-then-hibernate</command> for details on how this option interacts with
+ other options/system battery state.</para>
<xi:include href="version-info.xml" xpointer="v239"/>
</listitem>
<listitem>
<para>The RTC alarm will wake the system after the specified timespan to measure the system battery
- capacity level and estimate battery discharging rate, which is used for estimating timespan until the system battery charge
- level goes down to 5%. Only used by
+ capacity level and estimate battery discharging rate. Only used by
<citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
- Defaults to 1h.</para>
+ Refer to <command>suspend-then-hibernate</command> for details on how this option interacts with
+ other options/system battery state.</para>
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
--- /dev/null
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
+<!ENTITY % entities SYSTEM "custom-entities.ent" >
+%entities;
+]>
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+<refentry id="systemd-ssh-generator"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-ssh-generator</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-ssh-generator</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-ssh-generator</refname>
+ <refpurpose>Generator for binding a socket-activated SSH server to local <constant>AF_VSOCK</constant>
+ and <constant>AF_UNIX</constant> sockets</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/usr/lib/systemd/system-generators/systemd-ssh-generator</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-ssh-generator</command> binds a socket-activated SSH server to local
+ <constant>AF_VSOCK</constant> and <constant>AF_UNIX</constant> sockets under certain conditions. It only
+ has an effect if the <citerefentry
+ project="man-pages"><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry> binary is
+ installed. Specifically, it does the following:</para>
+
+ <itemizedlist>
+ <listitem><para>If invoked in a VM with <constant>AF_VSOCK</constant> support, a socket-activated SSH
+ per-connection service is bound to <constant>AF_VSOCK</constant> port 22.</para></listitem>
+
+ <listitem><para>If invoked in a container environment with a writable directory
+ <filename>/run/host/unix-export/</filename> pre-mounted it binds SSH to an <constant>AF_UNIX</constant>
+ socket <filename>/run/host/unix-export/ssh</filename>. The assumption is that this directory is bind
+ mounted to the host side as well, and can be used to connect to the container from there. See <ulink
+ url="https://systemd.io/CONTAINER_INTERFACE">Container Interface</ulink> for more information about
+ this interface.</para></listitem>
+
+ <listitem><para>A local <constant>AF_UNIX</constant> socket
+ <filename>/run/ssh-unix-local/socket</filename> is also bound, unconditionally. This may be used for
+ SSH communication from the host to itself, without involving networking, for example to traverse
+ security boundaries safely and with secure authentication.</para></listitem>
+
+ <listitem><para>Additional <constant>AF_UNIX</constant> and <constant>AF_VSOCK</constant> sockets are
+ optionally bound, based on the <varname>systemd.ssh_listen=</varname> kernel command line option or the
+ <filename>ssh.listen</filename> system credential (see below).</para></listitem>
+ </itemizedlist>
+
+ <para>See
+ <citerefentry><refentrytitle>systemd-ssh-proxy</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ details on how to connect to these sockets via the <command>ssh</command> client.</para>
+
+ <para>The generator will use a packaged <filename>sshd@.service</filename> service template file if one
+ exists, and otherwise generate a suitable service template file.</para>
+
+ <para><filename>systemd-ssh-generator</filename> implements
+ <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Kernel Command Line</title>
+
+ <para><filename>systemd-ssh-generator</filename> understands the following
+ <citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ parameters:</para>
+
+ <variablelist class='kernel-commandline-options'>
+ <varlistentry>
+ <term><varname>systemd.ssh_auto=</varname></term>
+
+ <listitem><para>This option takes an optional boolean argument, and defaults to yes. If enabled, the
+ automatic binding to the <constant>AF_VSOCK</constant> and <constant>AF_UNIX</constant> sockets
+ listed above is done. If disable, this is not done, except for those explicitly requested via
+ <varname>systemd.ssh_listen=</varname> on the kernel command line or via the
+ <varname>ssh.listen</varname> system credential.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>systemd.ssh_listen=</varname></term>
+
+ <listitem><para>This option configures an additional socket to bind SSH to. It may be used multiple
+ times to bind multiple sockets. The syntax should follow the one of <varname>ListenStream=</varname>,
+ see
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details. This functionality supports all socket families systemd supports, including
+ <constant>AF_INET</constant> and <constant>AF_INET6</constant>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Credentials</title>
+
+ <para><command>systemd-ssh-generator</command> supports the system credentials logic. The following
+ credentials are used when passed in:</para>
+
+ <variablelist class='system-credentials'>
+ <varlistentry>
+ <term><varname>ssh.listen</varname></term>
+
+ <listitem><para>This credential should be a text file, with each line referencing one additional
+ socket to bind SSH to. The syntax should follow the one of <varname>ListenStream=</varname>, see
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details. This functionality supports all socket families systemd supports, including
+ <constant>AF_INET</constant> and <constant>AF_INET6</constant>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><simplelist type="inline">
+ <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+ <member><citerefentry project="man-pages"><refentrytitle>vsock</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+ <member><citerefentry project="man-pages"><refentrytitle>unix</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+ <member><citerefentry project="man-pages"><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry project="man-pages"><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+ </simplelist></para>
+ </refsect1>
+</refentry>
--- /dev/null
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd-ssh-proxy"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-ssh-proxy</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-ssh-proxy</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-ssh-proxy</refname>
+ <refpurpose>SSH client plugin for connecting to <constant>AF_VSOCK</constant> and
+ <constant>AF_UNIX</constant> sockets</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <programlisting>
+Host unix/* vsock/*
+ ProxyCommand /usr/lib/systemd/systemd-ssh-proxy %h %p
+ ProxyUseFdpass yes
+</programlisting>
+ <cmdsynopsis>
+ <command>/usr/lib/systemd/systemd-ssh-proxy</command> <arg>ADDRESS</arg> <arg>PORT</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-ssh-proxy</command> is a small "proxy" plugin for the <citerefentry
+ project="man-pages"><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ tool that allows connecting to <constant>AF_UNIX</constant> and <constant>AF_VSOCK</constant> sockets. It
+ implements the interface defined by <filename>ssh</filename>'s <varname>ProxyCommand</varname>
+ configuration option. It's supposed to be used with an <citerefentry
+ project="man-pages"><refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ configuration fragment like the following:</para>
+
+ <programlisting>
+Host unix/* vsock/*
+ ProxyCommand /usr/lib/systemd/systemd-ssh-proxy %h %p
+ ProxyUseFdpass yes
+ CheckHostIP no
+
+Host .host
+ ProxyCommand /usr/lib/systemd/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p
+ ProxyUseFdpass yes
+ CheckHostIP no
+</programlisting>
+
+ <para>A configuration fragment along these lines is by default installed into
+ <filename>/etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf.in</filename>.</para>
+
+ <para>With this in place, SSH connections to host string <literal>unix/</literal> followed by an absolute
+ <constant>AF_UNIX</constant> file system path to a socket will be directed to the specified socket, which
+ must be of type <constant>SOCK_STREAM</constant>. Similar, SSH connections to <literal>vsock/</literal>
+ followed by an <constant>AF_VSOCK</constant> CID will result in an SSH connection made to that
+ CID. Moreover connecting to <literal>.host</literal> will connect to the local host via SSH, without
+ involving networking.</para>
+
+ <para>This tool is supposed to be used together with
+ <citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ which when run inside a VM or container will bind SSH to suitable
+ addresses. <command>systemd-ssh-generator</command> is supposed to run in the container of VM guest, and
+ <command>systemd-ssh-proxy</command> is run on the host, in order to connect to the container or VM
+ guest.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned, a non-zero failure code
+ otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <example>
+ <title>Talk to a local VM with CID 4711</title>
+
+ <programlisting>ssh vsock/4711</programlisting>
+ </example>
+
+ <example>
+ <title>Talk to the local host via ssh</title>
+
+ <programlisting>ssh .host</programlisting>
+
+ <para>or equivalent:</para>
+
+ <programlisting>ssh unix/run/ssh-unix-local/socket</programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><simplelist type="inline">
+ <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+ <member><citerefentry project="man-pages"><refentrytitle>vsock</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+ <member><citerefentry project="man-pages"><refentrytitle>unix</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+ <member><citerefentry project="man-pages"><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry project="man-pages"><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+ </simplelist></para>
+ </refsect1>
+</refentry>
<para><command>systemd-sysctl</command> supports the service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
- (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist class='system-credentials'>
<para><command>systemd-sysusers</command> supports the service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
- (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist class='system-credentials'>
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><ulink url="https://systemd.io/UIDS-GIDS">Users, Groups, UIDs and GIDs on systemd systems</ulink></member>
- <member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry project='man-pages'><refentrytitle>mkpasswd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
<para><command>systemd-tmpfiles</command> supports the service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
- (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist class='system-credentials'>
<para><command>systemd-vconsole-setup</command> supports the service credentials logic as implemented by
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
- (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist class='system-credentials'>
<listitem>
<para>A description of the device.</para>
- <xi:include href="version-info.xml" xpointer="v211"/>
+ <xi:include href="version-info.xml" xpointer="v211"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>Property=</varname></term>
+ <listitem>
+ <para>Set specified udev properties. This takes space separated list of key-value pairs
+ concatenated with equal sign (<literal>=</literal>). Example:
+ <programlisting>Property=HOGE=foo BAR=baz</programlisting>
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.</para>
+
+ <para>This setting is useful to configure the <literal>ID_NET_MANAGED_BY=</literal> property which
+ declares which network management service shall manage the interface, which is respected by
+ systemd-networkd and others. Use
+ <programlisting>Property=ID_NET_MANAGED_BY=io.systemd.Network</programlisting>
+ to declare explicitly that <command>systemd-networkd</command> shall manage the interface, or set
+ the property to something else to declare explicitly it shall not do so. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details how this property is used to match interface names.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>ImportProperty=</varname></term>
+ <listitem>
+ <para>Import specified udev properties from the saved database. This takes space separated list of
+ property names. Example: <programlisting>ImportProperty=HOGE BAR</programlisting>
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.</para>
+ <para>If the same property is also set in <varname>Property=</varname> in the above, then the
+ imported property value will be overridden by the value specified in <varname>Property=</varname>.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>UnsetProperty=</varname></term>
+ <listitem>
+ <para>Unset specified udev properties. This takes space separated list of
+ property names. Example: <programlisting>ImportProperty=HOGE BAR</programlisting>
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.</para>
+ <para>This setting is applied after <varname>ImportProperty=</varname> and
+ <varname>Property=</varname> are applied. Hence, if the same property is specified in
+ <varname>ImportProperty=</varname> or <varname>Property=</varname>, then the imported or specified
+ property value will be ignored, and the property will be unset.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<listitem>
<para>The <varname>ifalias</varname> interface property is set to this value.</para>
- <xi:include href="version-info.xml" xpointer="v211"/>
+ <xi:include href="version-info.xml" xpointer="v211"/>
</listitem>
</varlistentry>
<varlistentry>
<literal>60-foo.link.wol.password</literal>), and if the credential not found, then
read from <literal>wol.password</literal>. See
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> in
- <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. The password in the credential, must be 6 bytes in hex format with each
byte separated by a colon (<literal>:</literal>) like an Ethernet MAC address, e.g.,
<literal>aa:bb:cc:dd:ee:ff</literal>.</para>
</variablelist>
</refsect1>
+ <refsect1>
+ <title>Specifiers</title>
+
+ <para>Some settings resolve specifiers which may be used to write generic unit files referring to runtime
+ or unit parameters that are replaced when the unit files are loaded. Specifiers must be known and
+ resolvable for the setting to be valid. The following specifiers are understood:</para>
+
+ <table class='specifiers'>
+ <title>Specifiers available in unit files</title>
+ <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <colspec colname="spec" />
+ <colspec colname="mean" />
+ <colspec colname="detail" />
+ <thead>
+ <row>
+ <entry>Specifier</entry>
+ <entry>Meaning</entry>
+ <entry>Details</entry>
+ </row>
+ </thead>
+ <tbody>
+ <xi:include href="standard-specifiers.xml" xpointer="a"/>
+ <xi:include href="standard-specifiers.xml" xpointer="A"/>
+ <xi:include href="standard-specifiers.xml" xpointer="b"/>
+ <xi:include href="standard-specifiers.xml" xpointer="B"/>
+ <xi:include href="standard-specifiers.xml" xpointer="H"/>
+ <xi:include href="standard-specifiers.xml" xpointer="l"/>
+ <xi:include href="standard-specifiers.xml" xpointer="m"/>
+ <xi:include href="standard-specifiers.xml" xpointer="M"/>
+ <xi:include href="standard-specifiers.xml" xpointer="o"/>
+ <xi:include href="standard-specifiers.xml" xpointer="q"/>
+ <xi:include href="standard-specifiers.xml" xpointer="T"/>
+ <xi:include href="standard-specifiers.xml" xpointer="v"/>
+ <xi:include href="standard-specifiers.xml" xpointer="V"/>
+ <xi:include href="standard-specifiers.xml" xpointer="w"/>
+ <xi:include href="standard-specifiers.xml" xpointer="W"/>
+ </tbody>
+ </tgroup>
+ </table>
+ </refsect1>
+
<refsect1>
<title>Examples</title>
<varlistentry>
<term><varname>What=</varname></term>
- <listitem><para>Takes an absolute path of a device node, file or other resource to mount. See
- <citerefentry
+ <listitem><para>Takes an absolute path or a fstab-style identifier of a device node, file or
+ other resource to mount. See <citerefentry
project='man-pages'><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
details. If this refers to a device node, a dependency on the respective device unit is automatically
created. (See
<varlistentry>
<term><varname>PrivateKey=</varname></term>
<listitem>
- <para>The Base64 encoded private key for the interface. It can be
- generated using the <command>wg genkey</command> command
+ <para>The Base64 encoded private key for the interface. It can be generated using
+ the <command>wg genkey</command> command
(see <citerefentry project="wireguard"><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>).
- This option or <varname>PrivateKeyFile=</varname> is mandatory to use WireGuard.
- Note that because this information is secret, you may want to set
- the permissions of the .netdev file to be owned by <literal>root:systemd-network</literal>
- with a <literal>0640</literal> file mode.</para>
+ Specially, if the specified key is prefixed with <literal>@</literal>, it is interpreted as
+ the name of the credential from which the actual key shall be read. <command>systemd-networkd.service</command>
+ automatically imports credentials matching <literal>network.wireguard.*</literal>. For more details
+ on credentials, refer to
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ A private key is mandatory to use WireGuard. If not set, the credential
+ <literal>network.wireguard.private.<replaceable>netdev</replaceable></literal> is used if exists.
+ I.e. for <filename>50-foobar.netdev</filename>, <literal>network.wireguard.private.50-foobar</literal>
+ is tried.</para>
+
+ <para>Note that because this information is secret, it's strongly recommended to use an (encrypted)
+ credential. Alternatively, you may want to set the permissions of the .netdev file to be owned
+ by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
<listitem>
<para>Sets a Base64 encoded public key calculated by <command>wg pubkey</command>
(see <citerefentry project="wireguard"><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
- from a private key, and usually transmitted out of band to the
- author of the configuration file. This option is mandatory for this
- section.</para>
+ from a private key, and usually transmitted out of band to the author of the configuration file.
+ This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
+ setting of the <option>[WireGuard]</option> section. This option is mandatory for this section.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
<varlistentry>
<term><varname>PresharedKey=</varname></term>
<listitem>
- <para>Optional preshared key for the interface. It can be generated
- by the <command>wg genpsk</command> command. This option adds an
- additional layer of symmetric-key cryptography to be mixed into the
- already existing public-key cryptography, for post-quantum
- resistance.
- Note that because this information is secret, you may want to set
- the permissions of the .netdev file to be owned by <literal>root:systemd-network</literal>
- with a <literal>0640</literal> file mode.</para>
+ <para>Optional preshared key for the interface. It can be generated by the <command>wg genpsk</command>
+ command. This option adds an additional layer of symmetric-key cryptography to be mixed into the
+ already existing public-key cryptography, for post-quantum resistance.
+ This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
+ setting of the <option>[WireGuard]</option> section.</para>
+
+ <para>Note that because this information is secret, it's strongly recommended to use an (encrypted)
+ credential. Alternatively, you may want to set the permissions of the .netdev file to be owned
+ by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
<varlistentry>
<term><varname>Endpoint=</varname></term>
<listitem>
- <para>Sets an endpoint IP address or hostname, followed by a colon, and then
- a port number. IPv6 address must be in the square brackets. For example,
- <literal>111.222.333.444:51820</literal> for IPv4 and <literal>[1111:2222::3333]:51820</literal>
- for IPv6 address. This endpoint will be updated automatically once to
- the most recent source IP address and port of correctly
+ <para>Sets an endpoint IP address or hostname, followed by a colon, and then a port number.
+ IPv6 address must be in the square brackets. For example, <literal>111.222.333.444:51820</literal>
+ for IPv4 and <literal>[1111:2222::3333]:51820</literal> for IPv6 address. This endpoint will be
+ updated automatically once to the most recent source IP address and port of correctly
authenticated packets from the peer at configuration time.</para>
+ <para>This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
+ setting of the <option>[WireGuard]</option> section.</para>
+
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RequiredForOnline=</varname></term>
<listitem>
- <para>Takes a boolean or a minimum operational state and an optional maximum operational
- state. Please see
+ <para>Takes a boolean, a minimum operational state (e.g., <literal>carrier</literal>), or a range
+ of operational state separated with a colon (e.g., <literal>degraded:routable</literal>).
+ Please see
<citerefentry><refentrytitle>networkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for possible operational states. When <literal>yes</literal>, the network is deemed required
when determining whether the system is online (including when running
minimum and maximum operational state required for the network interface to be considered
online.</para>
+ <para>When <literal>yes</literal> is specified for a CAN device,
+ <command>systemd-networkd-wait-online</command> deems that the interface is online when its
+ operational state becomes <literal>carrier</literal>. For an interface with other type, e.g.
+ <literal>ether</literal>, the interface is deened online when its online state is
+ <literal>degraded</literal> or <literal>routable</literal>.</para>
+
<para>Defaults to <literal>yes</literal> when <varname>ActivationPolicy=</varname> is not
set, or set to <literal>up</literal>, <literal>always-up</literal>, or
<literal>bound</literal>. Defaults to <literal>no</literal> when
<varlistentry>
<term><varname>What=</varname></term>
- <listitem><para>Takes an absolute path of a device node or file to use for paging. See <citerefentry
+ <listitem><para>Takes an absolute path or a fstab-style identifier of a device node or file to use
+ for paging. See <citerefentry
project='man-pages'><refentrytitle>swapon</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
details. If this refers to a device node, a dependency on the respective device unit is automatically
created. (See
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>network.netdev.*</varname></term>
+ <term><varname>network.link.*</varname></term>
+ <term><varname>network.network.*</varname></term>
+ <listitem>
+ <para>Configures network devices. Read by
+ <citerefentry><refentrytitle>systemd-network-generator.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. These
+ credentials directly translate to a matching <filename>*.netdev</filename>,
+ <filename>*.link</filename> or <filename>*.network</filename> file. Example: the contents of a
+ credential <filename>network.link.50-foobar</filename> will be copied into a file
+ <filename>50-foobar.link</filename>. See
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details.</para>
+
+ <para>Note that the resulting files are created world-readable, it's hence recommended to not include
+ secrets in these credentials, but supply them via separate credentials directly to
+ <filename>systemd-networkd.service</filename>, e.g. <varname>network.wireguard.*</varname>
+ as described below.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>network.wireguard.*</varname></term>
+ <listitem>
+ <para>Configures secrets for WireGuard netdevs. Read by
+ <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ For more information, refer to the <option>[WireGuard]</option> section of
+ <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>passwd.hashed-password.root</varname></term>
<term><varname>passwd.plaintext-password.root</varname></term>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>ssh.listen</varname></term>
+ <listitem>
+ <para>May be used to configure SSH sockets the system shall be reachable on. See
+ <citerefentry><refentrytitle>systemd-ssh-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ for details.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>sysusers.extra</varname></term>
<listitem>
<entry>Credentials directory</entry>
<entry>This is the value of the <literal>$CREDENTIALS_DIRECTORY</literal> environment variable if available. See section "Credentials" in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more information.</entry>
</row>
+ <row>
+ <entry><literal>%D</literal></entry>
+ <entry>Shared data directory</entry>
+ <entry>This is either <filename>/usr/share/</filename> (for the system manager) or the path <literal>$XDG_DATA_HOME</literal> resolves to (for user managers).</entry>
+ </row>
<row>
<entry><literal>%E</literal></entry>
<entry>Configuration directory root</entry>
with no boot counters or with a non-zero "tries left" counter are sorted before filenames with a zero
"tries left" counter.</para></listitem>
- <listitem><para>Preceeding the use counters (if they are specified), an optional CPU architecture
+ <listitem><para>Preceding the use counters (if they are specified), an optional CPU architecture
identifier may be specified in the filename (separated from the version with an underscore), as defined
in the architecture vocabulary of the <varname>ConditionArchitecture=</varname> unit file setting, as
documented in
<refnamediv>
<refname>udev.conf</refname>
+ <refname>udev.conf.d</refname>
<refpurpose>Configuration for device event managing daemon</refpurpose>
</refnamediv>
<refsynopsisdiv>
- <para><filename>/etc/udev/udev.conf</filename></para>
+ <para>
+ <simplelist>
+ <member><filename>/etc/udev/udev.conf</filename></member>
+ <member><filename>/run/udev/udev.conf</filename></member>
+ <member><filename>/usr/lib/udev/udev.conf</filename></member>
+ <member><filename>/etc/udev/udev.conf.d/*.conf</filename></member>
+ <member><filename>/run/udev/udev.conf.d/*.conf</filename></member>
+ <member><filename>/usr/lib/udev/udev.conf.d/*.conf</filename></member>
+ </simplelist>
+ </para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
+ <para>These files contain configuration options for
+ <citerefentry><refentrytitle>systemd-udevd</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ The syntax of these files is very simple: a list
+ of assignments, one per line.
+ All empty lines or lines beginning with <literal>#</literal> are
+ ignored.
+ </para>
<para>
- <citerefentry><refentrytitle>systemd-udevd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- expects its main configuration file at
- <filename>/etc/udev/udev.conf</filename>. It consists of a set
- of variables allowing the user to override default udev
- values. All empty lines or lines beginning with '#' are
- ignored. The following variables can be set:
+ The following options can be set:
</para>
<variablelist class='config-directives'>
<option>err</option>, <option>info</option> and
<option>debug</option>.</para>
+ <note>
+ <para>This option is also honored by
+ <citerefentry><refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ </para>
+ </note>
+
<xi:include href="version-info.xml" xpointer="v216"/>
</listitem>
</varlistentry>
<itemizedlist>
<listitem><para>A Varlink service reference starting with the <literal>unix:</literal> string, followed
- by an absolute <constant>AF_UNIX</constant> path, or by <literal>@</literal> and an arbitrary string
+ by an absolute <constant>AF_UNIX</constant> socket path, or by <literal>@</literal> and an arbitrary string
(the latter for referencing sockets in the abstract namespace).</para></listitem>
<listitem><para>A Varlink service reference starting with the <literal>exec:</literal> string, followed
by an absolute path of a binary to execute.</para></listitem>
+
+ <listitem><para>A Varlink service reference starting with the <literal>ssh:</literal> string, followed
+ by an SSH host specification, followed by <literal>:</literal>, followed by an absolute
+ <constant>AF_UNIX</constant> socket path. (This requires OpenSSH 9.4 or newer on the server side,
+ abstract namespace sockets are not supported.)</para></listitem>
</itemizedlist>
<para>For convenience these two simpler (redundant) service address syntaxes are also supported:</para>
pamconfdir = prefixdir / 'lib/pam.d'
endif
+sshconfdir = get_option('sshconfdir')
+if sshconfdir == ''
+ sshconfdir = sysconfdir / 'ssh/ssh_config.d'
+endif
+
sshdconfdir = get_option('sshdconfdir')
if sshdconfdir == ''
sshdconfdir = sysconfdir / 'ssh/sshd_config.d'
conf.set_quoted('RANDOM_SEED', randomseeddir / 'random-seed')
conf.set_quoted('RANDOM_SEED_DIR', randomseeddir)
conf.set_quoted('RC_LOCAL_PATH', get_option('rc-local'))
+conf.set_quoted('SSHCONFDIR', sshconfdir)
conf.set_quoted('SSHDCONFDIR', sshdconfdir)
conf.set_quoted('SYSCONF_DIR', sysconfdir)
conf.set_quoted('SYSCTL_DIR', sysctldir)
subdir('src/sleep')
subdir('src/socket-activate')
subdir('src/socket-proxy')
+subdir('src/ssh-generator')
subdir('src/stdio-bridge')
subdir('src/sulogin-shell')
subdir('src/sysctl')
'SysV rc?.d directories' : sysvrcnd_path,
'PAM modules directory' : pamlibdir,
'PAM configuration directory' : pamconfdir,
- 'ssh configuration directory' : sshdconfdir,
+ 'ssh server configuration directory' : sshdconfdir,
+ 'ssh client configuration directory' : sshconfdir,
'libcryptsetup plugins directory' : libcryptsetup_plugins_dir,
'RPM macros directory' : rpmmacrosdir,
'modprobe.d directory' : modprobedir,
description : 'directory for PAM modules')
option('pamconfdir', type : 'string',
description : 'directory for PAM configuration ["no" disables]')
+option('sshconfdir', type : 'string',
+ description : 'directory for SSH client configuration ["no" disables]')
option('sshdconfdir', type : 'string',
description : 'directory for SSH server configuration ["no" disables]')
option('libcryptsetup-plugins-dir', type : 'string',
@Incremental=yes
@QemuMem=2G
@RuntimeSize=8G
+ToolsTreePackages=virtiofsd
KernelCommandLineExtra=systemd.crash_shell
systemd.log_level=debug
systemd.log_ratelimit_kmsg=0
[Config]
Dependencies=base
+[Output]
+@Format=directory
+
[Content]
Autologin=yes
BaseTrees=../../mkosi.output/base
libcap-ng-utils
netcat
openssh-server
+ openssh-clients
p11-kit
pam
passwd
libcap-ng-utils
netcat-openbsd
openssh-server
+ openssh-client
passwd
policykit-1
procps
kernel-kvmsmall
libcap-ng-utils
openssh-server
+ openssh-clients
python3
python3-pefile
python3-psutil
linux-image-generic
linux-tools-common
linux-tools-generic
+# "orphan_file" is enabled by default in recent versions of mkfs.ext4 but not supported by the Jammy kernel
+# so we explicitly disable it.
+Environment=SYSTEMD_REPART_MKFS_OPTIONS_EXT4="-O ^orphan_file"
# Do the same for dummy0.
options dummy numdummies=0
+
+# Do the same for ifb0.
+
+options ifb numifbs=0
--- /dev/null
+# SPDX-License-Identifier: MIT-0
+#
+# This config file is installed as part of systemd.
+# It may be freely copied and edited (following the MIT No Attribution license).
+#
+# To make local modifications, one of the following methods may be used:
+# 1. add a drop-in file that extends this file by creating the
+# /etc/systemd/network/80-6rd-tunnel.link.d/ directory and creating a
+# new .conf file there.
+# 2. copy this file into /etc/systemd/network/ or one of the other paths checked
+# by systemd-udevd and edit it there.
+# This file should not be edited in place, because it'll be overwritten on upgrades.
+
+# This .link file matches 6rd-* SIT devices and marks them as managed by
+# systemd-networkd.
+
+[Match]
+Kind=sit
+OriginalName=6rd-*
+
+[Link]
+NamePolicy=keep
+MACAddressPolicy=persistent
+Property=ID_NET_MANAGED_BY=io.systemd.Network
# 1. add a drop-in file that extends this file by creating the
# /etc/systemd/network/80-6rd-tunnel.network.d/ directory and creating a
# new .conf file there.
-# 2. copy this file into /etc/systemd/network or one of the other paths checked
+# 2. copy this file into /etc/systemd/network/ or one of the other paths checked
# by systemd-networkd and edit it there.
# This file should not be edited in place, because it'll be overwritten on upgrades.
-# This network file matches 6rd-* SIT devices which is automatically created by
+# This .network file matches 6rd-* SIT devices which is automatically created by
# systemd-networkd when DHCPv4 6RD option is received.
[Match]
--- /dev/null
+# SPDX-License-Identifier: MIT-0
+#
+# This config file is installed as part of systemd.
+# It may be freely copied and edited (following the MIT No Attribution license).
+#
+# To make local modifications, one of the following methods may be used:
+# 1. add a drop-in file that extends this file by creating the
+# /etc/systemd/network/80-container-vb.link.d/ directory and creating a
+# new .conf file there.
+# 2. copy this file into /etc/systemd/network/ or one of the other paths checked
+# by systemd-udevd and edit it there.
+# This file should not be edited in place, because it'll be overwritten on upgrades.
+
+# This .link file matches the host-side of the virtual Ethernet link created
+# by systemd-nspawn's --network-veth switch with --network-bridge= or
+# --network-zone= switch. See systemd-nspawn(1) for details.
+
+[Match]
+Kind=veth
+OriginalName=vb-*
+
+[Link]
+NamePolicy=keep
+Property=ID_NET_MANAGED_BY=io.systemd.Network
# 1. add a drop-in file that extends this file by creating the
# /etc/systemd/network/80-container-vb.network.d/ directory and creating a
# new .conf file there.
-# 2. copy this file into /etc/systemd/network or one of the other paths checked
+# 2. copy this file into /etc/systemd/network/ or one of the other paths checked
# by systemd-networkd and edit it there.
# This file should not be edited in place, because it'll be overwritten on upgrades.
-# This network file matches the host-side of the virtual Ethernet link
-# created by systemd-nspawn's --network-veth switch with --network-bridge or
-# --network-zone switch. See systemd-nspawn(1) for details.
+# This .network file matches the host-side of the virtual Ethernet link created
+# by systemd-nspawn's --network-veth switch with --network-bridge= or
+# --network-zone= switch. See systemd-nspawn(1) for details.
[Match]
Kind=veth
--- /dev/null
+# SPDX-License-Identifier: MIT-0
+#
+# This config file is installed as part of systemd.
+# It may be freely copied and edited (following the MIT No Attribution license).
+#
+# To make local modifications, one of the following methods may be used:
+# 1. add a drop-in file that extends this file by creating the
+# /etc/systemd/network/80-container-ve.link.d/ directory and creating a
+# new .conf file there.
+# 2. copy this file into /etc/systemd/network/ or one of the other paths checked
+# by systemd-udevd and edit it there.
+# This file should not be edited in place, because it'll be overwritten on upgrades.
+
+# This .link file matches the host-side of the virtual Ethernet link
+# created by systemd-nspawn's --network-veth switch. See systemd-nspawn(1) for
+# details.
+
+[Match]
+Kind=veth
+OriginalName=ve-*
+
+[Link]
+NamePolicy=keep
+Property=ID_NET_MANAGED_BY=io.systemd.Network
# 1. add a drop-in file that extends this file by creating the
# /etc/systemd/network/80-container-ve.network.d/ directory and creating a
# new .conf file there.
-# 2. copy this file into /etc/systemd/network or one of the other paths checked
+# 2. copy this file into /etc/systemd/network/ or one of the other paths checked
# by systemd-networkd and edit it there.
# This file should not be edited in place, because it'll be overwritten on upgrades.
-# This network file matches the host-side of the virtual Ethernet link
+# This .network file matches the host-side of the virtual Ethernet link
# created by systemd-nspawn's --network-veth switch. See systemd-nspawn(1) for
# details.
--- /dev/null
+# SPDX-License-Identifier: MIT-0
+#
+# This config file is installed as part of systemd.
+# It may be freely copied and edited (following the MIT No Attribution license).
+#
+# To make local modifications, one of the following methods may be used:
+# 1. add a drop-in file that extends this file by creating the
+# /etc/systemd/network/80-container-vz.link.d/ directory and creating a
+# new .conf file there.
+# 2. copy this file into /etc/systemd/network/ or one of the other paths checked
+# by systemd-udevd and edit it there.
+# This file should not be edited in place, because it'll be overwritten on upgrades.
+
+# This .link file matches the bridge interface created by systemd-nspawn's
+# --network-zone= switch. See systemd-nspawn(1) for details.
+
+[Match]
+Kind=bridge
+OriginalName=vz-*
+
+[Link]
+NamePolicy=keep
+Property=ID_NET_MANAGED_BY=io.systemd.Network
--- /dev/null
+# SPDX-License-Identifier: MIT-0
+#
+# This config file is installed as part of systemd.
+# It may be freely copied and edited (following the MIT No Attribution license).
+#
+# To make local modifications, one of the following methods may be used:
+# 1. add a drop-in file that extends this file by creating the
+# /etc/systemd/network/80-vm-vt.link.d/ directory and creating a
+# new .conf file there.
+# 2. copy this file into /etc/systemd/network/ or one of the other paths checked
+# by systemd-udevd and edit it there.
+# This file should not be edited in place, because it'll be overwritten on upgrades.
+
+# This .link file matches vt-* TUN/TAP devices on the host and marks them as
+# managed by systemd-networkd.
+
+[Match]
+Kind=tun
+OriginalName=vt-*
+
+[Link]
+NamePolicy=keep
+Property=ID_NET_MANAGED_BY=io.systemd.Network
if conf.get('ENABLE_NETWORKD') == 1
install_data(
'80-6rd-tunnel.network',
+ '80-6rd-tunnel.link',
'80-container-host0.network',
'80-container-vb.network',
+ '80-container-vb.link',
'80-container-ve.network',
+ '80-container-ve.link',
'80-container-vz.network',
+ '80-container-vz.link',
'80-vm-vt.network',
+ '80-vm-vt.link',
'80-wifi-adhoc.network',
install_dir : networkdir)
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# A S Alam <amanpreet.alam@gmail.com>, 2020, 2021, 2023.
-# A S Alam <aalam@users.noreply.translate.fedoraproject.org>, 2023.
+# A S Alam <aalam@users.noreply.translate.fedoraproject.org>, 2023, 2024.
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-14 21:25+0000\n"
-"PO-Revision-Date: 2023-12-28 15:36+0000\n"
+"PO-Revision-Date: 2024-01-16 14:35+0000\n"
"Last-Translator: A S Alam <aalam@users.noreply.translate.fedoraproject.org>\n"
"Language-Team: Punjabi <https://translate.fedoraproject.org/projects/systemd/"
"master/pa/>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
-"X-Generator: Weblate 5.3\n"
+"X-Generator: Weblate 5.3.1\n"
#: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system"
#: src/home/pam_systemd_home.c:352
#, c-format
msgid "Security token of user %s not inserted."
-msgstr ""
+msgstr "%s ਵਰਤੋਂਕਾਰ ਲਈ ਸੁਰੱਖਿਆ ਟੋਕਨ ਨਹੀਂ ਦਿੱਤਾ ਗਿਆ।"
#: src/home/pam_systemd_home.c:353 src/home/pam_systemd_home.c:356
msgid "Try again with password: "
#: src/network/org.freedesktop.network1.policy:165
msgid "Reload network settings"
-msgstr ""
+msgstr "ਨੈੱਟਵਰਕ ਸੈਟਿੰਗਾਂ ਨੂੰ ਮੁੜ-ਲੋਡ ਕਰੋ"
#: src/network/org.freedesktop.network1.policy:166
msgid "Authentication is required to reload network settings."
-msgstr ""
+msgstr "ਨੈੱਟਵਰਕ ਸੈਟਿੰਗਾਂ ਨੂੰ ਮੁੜ-ਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/network/org.freedesktop.network1.policy:176
msgid "Reconfigure network interface"
-msgstr ""
+msgstr "ਨੈੱਟਵਰਕ ਇੰਟਰਫੇਸ ਦੀ ਮੁੜ-ਸੰਰਚਨਾ ਕਰੋ"
#: src/network/org.freedesktop.network1.policy:177
msgid "Authentication is required to reconfigure network interface."
-msgstr ""
+msgstr "ਨੈੱਟਵਰਕ ਇੰਟਰਫੇਸ ਦੀ ਮੁੜ-ਸੰਰਚਨਾ ਕਰਨ ਥਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/portable/org.freedesktop.portable1.policy:13
msgid "Inspect a portable service image"
#: src/resolve/org.freedesktop.resolve1.policy:133
msgid "Authentication is required to reset name resolution settings."
-msgstr ""
+msgstr "ਨਾਂ ਹੱਲ ਸੈਟਿੰਗਾਂ ਮੁੜ-ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/timedate/org.freedesktop.timedate1.policy:22
msgid "Set system time"
#: src/timedate/org.freedesktop.timedate1.policy:23
msgid "Authentication is required to set the system time."
-msgstr ""
+msgstr "ਸਿਸਟਮ ਟਾਈਮ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/timedate/org.freedesktop.timedate1.policy:33
msgid "Set system timezone"
-msgstr ""
+msgstr "ਸਿਸਟਮ ਸਮਾਂ-ਖੇਤਰ ਸੈੱਟ ਕਰੋ"
#: src/timedate/org.freedesktop.timedate1.policy:34
msgid "Authentication is required to set the system timezone."
-msgstr ""
+msgstr "ਸਿਸਟਮ ਸਮਾਂ-ਖੇਤਰ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/timedate/org.freedesktop.timedate1.policy:43
msgid "Set RTC to local timezone or UTC"
#: src/core/dbus-unit.c:352
msgid "Authentication is required to start '$(unit)'."
-msgstr ""
+msgstr "'$(unit)' ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/core/dbus-unit.c:353
msgid "Authentication is required to stop '$(unit)'."
-msgstr ""
+msgstr "'$(unit)' ਨੂੰ ਰੋਕਣ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/core/dbus-unit.c:354
msgid "Authentication is required to reload '$(unit)'."
-msgstr ""
+msgstr "'$(unit)' ਨੂੰ ਮੁੜ-ਲੋਡ (reload) ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/core/dbus-unit.c:355 src/core/dbus-unit.c:356
msgid "Authentication is required to restart '$(unit)'."
-msgstr ""
+msgstr "'$(unit)' ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ (restart) ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।"
#: src/core/dbus-unit.c:553
msgid "Authentication is required to send a UNIX signal to the processes of '$(unit)'."
# ATA
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_BUS}=="ata", ENV{ID_ATA_PERIPHERAL_DEVICE_TYPE}=="20", PROGRAM="scsi_id -u -g $devnode", \
+ SYMLINK+="disk/by-id/scsi-$result$env{.PART_SUFFIX}"
# ATAPI devices (SPC-3 or later)
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"
__get_machines() {
local a b
{ machinectl list --full --max-addresses=0 --no-legend --no-pager 2>/dev/null; echo ".host"; } | \
- { while read a b; do echo " $a"; done; } | \
+ { while read a b; do echo " $a"; done; } | \
sort -u
}
--version --list-catalog --update-catalog --list-boots
--show-cursor --dmesg -k --pager-end -e -r --reverse
--utc -x --catalog --no-full --force --dump-catalog
- --flush --rotate --sync --no-hostname -N --fields'
- [ARG]='-b --boot -D --directory --file -F --field -t --identifier --facility
- -M --machine -o --output -u --unit --user-unit -p --priority
- --root --case-sensitive'
+ --flush --rotate --sync --no-hostname -N --fields
+ --list-namespaces'
+ [ARG]='-b --boot -D --directory --file -F --field -t --identifier
+ -T --exclude-identifier --facility -M --machine -o --output
+ -u --unit --user-unit -p --priority --root --case-sensitive
+ --namespace'
[ARGUNKNOWN]='-c --cursor --interval -n --lines -S --since -U --until
--after-cursor --cursor-file --verify-key -g --grep
--vacuum-size --vacuum-time --vacuum-files --output-fields'
--user-unit)
comps=$(journalctl -F '_SYSTEMD_USER_UNIT' 2>/dev/null)
;;
- --identifier|-t)
+ --identifier|-t|--exclude-identifier|-T)
comps=$(journalctl -F 'SYSLOG_IDENTIFIER' 2>/dev/null)
;;
--case-sensitive)
comps='yes no'
;;
+ --namespace)
+ comps=$(journalctl --list-namespaces --output=cat 2>/dev/null)
+ ;;
*)
return 0
;;
local -A OPTS=(
[STANDALONE]='--all -a --help -h --no-pager --version
- --no-legend --no-ask-password -l --full --value'
+ --no-legend --no-ask-password -l --full --value -j'
[ARG]='--host -H --kill-whom --property -p --signal -s -M --machine
- -n --lines -o --output -P'
+ -n --lines -o --output -P --json'
)
if __contains_word "$prev" ${OPTS[ARG]}; then
--output|-o)
comps=$( loginctl --output=help 2>/dev/null )
;;
+ --json)
+ comps=$( loginctl --json=help 2>/dev/null )
+ ;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
local -A OPTS=(
[STANDALONE]='-h --help --version --system --user --global --order --require --no-pager
- --man=no --generators=yes --quiet'
+ --man=no --generators=yes -q --quiet'
[ARG]='-H --host -M --machine --fuzz --from-pattern --to-pattern --root'
)
local i verb comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
local -A OPTS=(
- [STANDALONE]='-h --help --version -p --pretty'
- [ARG]='-a --app-specific'
+ [STANDALONE]='-h --help --version -p --pretty --value -u --uuid --no-legend --no-pager -j'
+ [ARG]='-a --app-specific --json'
)
local -A VERBS=(
[STANDALONE]='new machine-id boot-id invocation-id help'
+ [ARG]='show'
)
_init_completion || return
--app-specific|-a)
comps=""
;;
+ --json)
+ comps="short pretty off"
+ ;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
comps=${VERBS[*]}
elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
comps=''
+ elif __contains_word "$verb" ${VERBS[ARG]}; then
+ case $verb in
+ show)
+ comps="$(IFS='\n ' systemd-id128 show --no-legend)"
+ ;;
+ esac
fi
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
_describe 'possible values' _journalctl_facilities
}
+(( $+functions[_journalctl_namespaces] )) ||
+_journalctl_namespaces() {
+ local -a _journalctl_namespaces
+ _journalctl_namespaces=( ${(f)"$(_call_program namespaces "$service --list-namespaces --output=cat" 2>/dev/null)"} )
+ _describe 'possible values' _journalctl_namespaces
+}
+
# Build arguments for "journalctl" to be used in completion.
# Use both --user and --system modes, they are not exclusive.
local -a _modes; _modes=(--user --system)
'--header[Show journal header information]' \
'--interval=[Time interval for changing the FSS sealing key]:time interval' \
'--list-catalog[List messages in catalog]' \
+ '--list-namespaces[List available journal namespaces]' \
+ '--namespace[Show journal data from specified namespace]:namespace:_journalctl_namespaces' \
'--new-id128[Generate a new 128 Bit ID]' \
'--rotate[Request immediate rotation of the journal files]' \
'--setup-keys[Generate a new FSS key pair]' \
if (r < 0)
return log_error_errno(r, "Failed to read '%s': %m", p);
- r = unhexmem(s, ss, &buf, &bufsize);
+ r = unhexmem_full(s, ss, /* secure = */ false, &buf, &bufsize);
if (r < 0)
return log_error_errno(r, "Failed to decode hex PCR data '%s': %m", s);
" dot [UNIT...] Output dependency graph in %s format\n"
" dump [PATTERN...] Output state serialization of service\n"
" manager\n"
- " cat-config Show configuration file and drop-ins\n"
+ " cat-config NAME|PATH... Show configuration file and drop-ins\n"
" unit-files List files and symlinks for units\n"
" unit-paths List load directories for units\n"
" exit-status [STATUS...] List exit status definitions\n"
" inspect-elf FILE... Parse and print ELF package metadata\n"
" malloc [D-BUS SERVICE...] Dump malloc stats of a D-Bus service\n"
" fdstore SERVICE... Show file descriptor store contents of service\n"
+ " image-policy POLICY... Analyze image policy string\n"
" pcrs [PCR...] Show TPM2 PCRs and their names\n"
" srk > FILE Write TPM2 SRK to stdout\n"
"\nOptions:\n"
" specified time\n"
" --profile=name|PATH Include the specified profile in the\n"
" security review of the unit(s)\n"
+ " --unit=UNIT Evaluate conditions and asserts of unit\n"
" --table Output plot's raw time data as a table\n"
" -h --help Show this help\n"
" --version Show package version\n"
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hH:M:U:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hH:M:U:q", options, NULL)) >= 0)
switch (c) {
case 'h':
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --offline= is only supported for security right now.");
+ if (arg_offline && optind >= argc - 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Option --offline= requires one or more units to perform a security review.");
+
if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot", "fdstore", "pcrs", "architectures", "capability", "exit-status"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --json= is only supported for security, inspect-elf, plot, fdstore, pcrs, architectures, capability, exit-status right now.");
* proceeding and smashing the stack limits. Note that by default RLIMIT_STACK is 8M on Linux. */
#define ALLOCA_MAX (4U*1024U*1024U)
-#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n)))
+#define new(t, n) ((t*) malloc_multiply((n), sizeof(t)))
#define new0(t, n) ((t*) calloc((n) ?: 1, sizeof(t)))
(t*) alloca0((sizeof(t)*_n_)); \
})
-#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n)))
+#define newdup(t, p, n) ((t*) memdup_multiply(p, (n), sizeof(t)))
#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n)))
return _unlikely_(need != 0 && size > (SIZE_MAX / need));
}
-_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t need) {
+_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t need, size_t size) {
if (size_multiply_overflow(size, need))
return NULL;
}
#endif
-_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, size_t need) {
+_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t need, size_t size) {
if (size_multiply_overflow(size, need))
return NULL;
return true;
if (!strchr(pattern, '=')) {
- size_t l = strlen(pattern);
+ t = startswith(t, pattern);
- return strneq(t, pattern, l) && t[l] == '=';
+ return t && *t == '=';
}
return false;
}
int setenv_systemd_exec_pid(bool update_only) {
- char str[DECIMAL_STR_MAX(pid_t)];
const char *e;
+ int r;
/* Update $SYSTEMD_EXEC_PID=pid except when '*' is set for the variable. */
if (streq_ptr(e, "*"))
return 0;
- xsprintf(str, PID_FMT, getpid_cached());
-
- if (setenv("SYSTEMD_EXEC_PID", str, 1) < 0)
- return -errno;
+ r = setenvf("SYSTEMD_EXEC_PID", /* overwrite= */ 1, PID_FMT, getpid_cached());
+ if (r < 0)
+ return r;
return 1;
}
return 0;
}
+
+int setenvf(const char *name, bool overwrite, const char *valuef, ...) {
+ _cleanup_free_ char *value = NULL;
+ va_list ap;
+ int r;
+
+ assert(name);
+
+ if (!valuef)
+ return RET_NERRNO(unsetenv(name));
+
+ va_start(ap, valuef);
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ r = vasprintf(&value, valuef, ap);
+ REENABLE_WARNING;
+ va_end(ap);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ return RET_NERRNO(setenv(name, value, overwrite));
+}
int getenv_steal_erase(const char *name, char **ret);
int set_full_environment(char **env);
+
+int setenvf(const char *name, bool overwrite, const char *valuef, ...) _printf_(3,4);
return RET_NERRNO(fcntl(fd, F_SETFL, nflags));
}
+int stdio_disable_nonblock(void) {
+ int ret = 0;
+
+ /* stdin/stdout/stderr really should have O_NONBLOCK, which would confuse apps if left on, as
+ * write()s might unexpectedly fail with EAGAIN. */
+
+ RET_GATHER(ret, fd_nonblock(STDIN_FILENO, false));
+ RET_GATHER(ret, fd_nonblock(STDOUT_FILENO, false));
+ RET_GATHER(ret, fd_nonblock(STDERR_FILENO, false));
+
+ return ret;
+}
+
int fd_cloexec(int fd, bool cloexec) {
int flags, nflags;
#define _cleanup_close_pair_ _cleanup_(close_pairp)
int fd_nonblock(int fd, bool nonblock);
+int stdio_disable_nonblock(void);
+
int fd_cloexec(int fd, bool cloexec);
int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
return 0;
}
+static mode_t write_string_file_flags_to_mode(WriteStringFileFlags flags) {
+
+ /* We support three different modes, that are the ones that really make sense for text files like this:
+ *
+ * → 0600 (i.e. root-only)
+ * → 0444 (i.e. read-only)
+ * → 0644 (i.e. writable for root, readable for everyone else)
+ */
+
+ return FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 :
+ FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0444) ? 0444 : 0644;
+}
+
static int write_string_file_atomic_at(
int dir_fd,
const char *fn,
if (r < 0)
goto fail;
- r = fchmod_umask(fileno(f), FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0644);
+ r = fchmod_umask(fileno(f), write_string_file_flags_to_mode(flags));
if (r < 0)
goto fail;
(FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) |
(FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) |
(FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY),
- (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666));
+ write_string_file_flags_to_mode(flags));
if (fd < 0) {
r = -errno;
goto fail;
WRITE_STRING_FILE_NOFOLLOW = 1 << 8,
WRITE_STRING_FILE_MKDIR_0755 = 1 << 9,
WRITE_STRING_FILE_MODE_0600 = 1 << 10,
- WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 11,
+ WRITE_STRING_FILE_MODE_0444 = 1 << 11,
+ WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 12,
/* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()
const char *p,
size_t l,
bool secure,
- void **ret,
+ void **ret_data,
size_t *ret_len) {
_cleanup_free_ uint8_t *buf = NULL;
if (ret_len)
*ret_len = (size_t) (z - buf);
- if (ret)
- *ret = TAKE_PTR(buf);
+ if (ret_data)
+ *ret_data = TAKE_PTR(buf);
return 0;
}
const char *p,
size_t l,
bool secure,
- void **ret,
+ void **ret_data,
size_t *ret_size) {
_cleanup_free_ uint8_t *buf = NULL;
if (ret_size)
*ret_size = (size_t) (z - buf);
- if (ret)
- *ret = TAKE_PTR(buf);
+ if (ret_data)
+ *ret_data = TAKE_PTR(buf);
return 0;
}
int unhexchar(char c) _const_;
char *hexmem(const void *p, size_t l);
-int unhexmem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
-static inline int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
- return unhexmem_full(p, l, false, mem, len);
+int unhexmem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size);
+static inline int unhexmem(const char *p, void **ret_data, size_t *ret_size) {
+ return unhexmem_full(p, SIZE_MAX, false, ret_data, ret_size);
}
char base32hexchar(int x) _const_;
size_t l,
size_t margin,
size_t width);
-int unbase64mem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
-static inline int unbase64mem(const char *p, size_t l, void **mem, size_t *len) {
- return unbase64mem_full(p, l, false, mem, len);
+int unbase64mem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size);
+static inline int unbase64mem(const char *p, void **ret_data, size_t *ret_size) {
+ return unbase64mem_full(p, SIZE_MAX, false, ret_data, ret_size);
}
void hexdump(FILE *f, const void *p, size_t s);
#include "alloc-util.h"
#include "macro.h"
+/* An iovec pointing to a single NUL byte */
+#define IOVEC_NUL_BYTE (const struct iovec) { \
+ .iov_base = (void*) (const uint8_t[1]) { 0 }, \
+ .iov_len = 1, \
+ }
+
size_t iovec_total_size(const struct iovec *iovec, size_t n);
bool iovec_increment(struct iovec *iovec, size_t n, size_t k);
-#define IOVEC_MAKE(base, len) (struct iovec) { .iov_base = (base), .iov_len = (len) }
-#define IOVEC_MAKE_STRING(string) \
- ({ \
- const char *_s = (string); \
- IOVEC_MAKE((char*) _s, strlen(_s)); \
- })
+/* This accepts both const and non-const pointers */
+#define IOVEC_MAKE(base, len) \
+ (struct iovec) { \
+ .iov_base = (void*) (base), \
+ .iov_len = (len), \
+ }
+
+static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) {
+ assert(iovec);
+ /* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */
+ *iovec = IOVEC_MAKE(s, s ? strlen(s) : 0);
+ return iovec;
+}
+
+#define IOVEC_MAKE_STRING(s) \
+ *iovec_make_string(&(struct iovec) {}, s)
+
+#define CONST_IOVEC_MAKE_STRING(s) \
+ (const struct iovec) { \
+ .iov_base = (char*) s, \
+ .iov_len = STRLEN(s), \
+ }
static inline void iovec_done(struct iovec *iovec) {
/* A _cleanup_() helper that frees the iov_base in the iovec */
}
static inline bool iovec_is_set(const struct iovec *iovec) {
+ /* Checks if the iovec points to a non-empty chunk of memory */
return iovec && iovec->iov_len > 0 && iovec->iov_base;
}
+static inline bool iovec_is_valid(const struct iovec *iovec) {
+ /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */
+ return !iovec || (iovec->iov_base || iovec->iov_len == 0);
+}
+
char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value);
void iovec_array_free(struct iovec *iovec, size_t n_iovec);
+
+static inline int iovec_memcmp(const struct iovec *a, const struct iovec *b) {
+
+ if (a == b)
+ return 0;
+
+ return memcmp_nn(a ? a->iov_base : NULL,
+ a ? a->iov_len : 0,
+ b ? b->iov_base : NULL,
+ b ? b->iov_len : 0);
+}
+
+static inline struct iovec *iovec_memdup(const struct iovec *source, struct iovec *ret) {
+ assert(ret);
+
+ if (!iovec_is_set(source))
+ *ret = (struct iovec) {};
+ else {
+ void *p = memdup(source->iov_base, source->iov_len);
+ if (!p)
+ return NULL;
+
+ *ret = IOVEC_MAKE(p, source->iov_len);
+ }
+
+ return ret;
+}
'terminal-util.c',
'time-util.c',
'tmpfile-util.c',
- 'uid-alloc-range.c',
+ 'uid-classification.c',
'uid-range.c',
'unit-def.c',
'unit-file.c',
#if HAVE_LINUX_VM_SOCKETS_H
#include <linux/vm_sockets.h>
#else
-#define VMADDR_CID_ANY -1U
struct sockaddr_vm {
unsigned short svm_family;
unsigned short svm_reserved1;
};
#endif /* !HAVE_LINUX_VM_SOCKETS_H */
+#ifndef VMADDR_CID_ANY
+#define VMADDR_CID_ANY -1U
+#endif
+
+#ifndef VMADDR_CID_HYPERVISOR
+#define VMADDR_CID_HYPERVISOR 0U
+#endif
+
+#ifndef VMADDR_CID_LOCAL
+#define VMADDR_CID_LOCAL 1U
+#endif
+
+#ifndef VMADDR_CID_HOST
+#define VMADDR_CID_HOST 2U
+#endif
+
+#ifndef VMADDR_PORT_ANY
+#define VMADDR_PORT_ANY -1U
+#endif
+
#ifndef AF_VSOCK
#define AF_VSOCK 40
#endif
return 0;
}
-int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) {
+int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero) {
unsigned l, h;
int r;
if (r < 0)
return r;
- if (l <= 0 || l > 65535 || h <= 0 || h > 65535)
+ if (l > 65535 || h > 65535)
+ return -EINVAL;
+
+ if (!allow_zero && (l == 0 || h == 0))
return -EINVAL;
if (h < l)
int parse_nice(const char *p, int *ret);
int parse_ip_port(const char *s, uint16_t *ret);
-int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high);
+int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero);
int parse_ip_prefix_length(const char *s, int *ret);
return NULL;
/* Now merge everything we found. */
- if (strv_extend(&res, persistent_control) < 0)
- return NULL;
-
- if (strv_extend(&res, runtime_control) < 0)
- return NULL;
-
- if (strv_extend(&res, transient) < 0)
- return NULL;
-
- if (strv_extend(&res, generator_early) < 0)
- return NULL;
-
- if (strv_extend(&res, persistent_config) < 0)
+ if (strv_extend_many(
+ &res,
+ persistent_control,
+ runtime_control,
+ transient,
+ generator_early,
+ persistent_config) < 0)
return NULL;
if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0)
if (strv_extend_strv(&res, (char**) user_config_unit_paths, false) < 0)
return NULL;
- if (strv_extend(&res, runtime_config) < 0)
- return NULL;
-
- if (strv_extend(&res, global_runtime_config) < 0)
- return NULL;
-
- if (strv_extend(&res, generator) < 0)
- return NULL;
-
- if (strv_extend(&res, data_home) < 0)
+ if (strv_extend_many(
+ &res,
+ runtime_config,
+ global_runtime_config,
+ generator,
+ data_home) < 0)
return NULL;
if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0)
_cleanup_fclose_ FILE *f = NULL;
char *value = NULL;
const char *path;
- size_t l, sum = 0;
+ size_t sum = 0;
int r;
assert(pid >= 0);
if (r < 0)
return r;
- l = strlen(field);
for (;;) {
_cleanup_free_ char *line = NULL;
+ const char *match;
if (sum > ENVIRONMENT_BLOCK_MAX) /* Give up searching eventually */
return -ENOBUFS;
sum += r;
- if (strneq(line, field, l) && line[l] == '=') {
- value = strdup(line + l + 1);
+ match = startswith(line, field);
+ if (match && *match == '=') {
+ value = strdup(match + 1);
if (!value)
return -ENOMEM;
log_full_errno(prio, r, "Failed to rearrange stdio fds: %m");
_exit(EXIT_FAILURE);
}
+
+ /* Turn off O_NONBLOCK on the fdio fds, in case it was left on */
+ stdio_disable_nonblock();
} else {
r = make_null_stdio();
if (r < 0) {
return sockaddr_pretty(&sa.sa, salen, false, true, ret);
}
-int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) {
+int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **ret) {
+ char host[NI_MAXHOST];
int r;
- char host[NI_MAXHOST], *ret;
- assert(_ret);
+ assert(sa);
+ assert(salen > sizeof(sa_family_t));
- r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS);
+ r = getnameinfo(sa, salen, host, sizeof(host), /* service= */ NULL, /* service_len= */ 0, IDN_FLAGS);
if (r != 0) {
- int saved_errno = errno;
+ if (r == EAI_MEMORY)
+ return log_oom_debug();
+ if (r == EAI_SYSTEM)
+ log_debug_errno(errno, "getnameinfo() failed, ignoring: %m");
+ else
+ log_debug("getnameinfo() failed, ignoring: %s", gai_strerror(r));
- r = sockaddr_pretty(&sa->sa, salen, true, true, &ret);
- if (r < 0)
- return r;
+ return sockaddr_pretty(sa, salen, /* translate_ipv6= */ true, /* include_port= */ true, ret);
+ }
- log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret);
- } else {
- ret = strdup(host);
- if (!ret)
+ if (ret) {
+ char *copy = strdup(host);
+ if (!copy)
return -ENOMEM;
+
+ *ret = copy;
}
- *_ret = ret;
return 0;
}
return 0;
}
+int vsock_parse_port(const char *s, unsigned *ret) {
+ int r;
+
+ assert(ret);
+
+ if (!s)
+ return -EINVAL;
+
+ unsigned u;
+ r = safe_atou(s, &u);
+ if (r < 0)
+ return r;
+
+ /* Port 0 is apparently valid and not special in AF_VSOCK (unlike on IP). But VMADDR_PORT_ANY
+ * (UINT32_MAX) is. Hence refuse that. */
+
+ if (u == VMADDR_PORT_ANY)
+ return -EINVAL;
+
+ *ret = u;
+ return 0;
+}
+
+int vsock_parse_cid(const char *s, unsigned *ret) {
+ assert(ret);
+
+ if (!s)
+ return -EINVAL;
+
+ /* Parsed an AF_VSOCK "CID". This is a 32bit entity, and the usual type is "unsigned". We recognize
+ * the three special CIDs as strings, and otherwise parse the numeric CIDs. */
+
+ if (streq(s, "hypervisor"))
+ *ret = VMADDR_CID_HYPERVISOR;
+ else if (streq(s, "local"))
+ *ret = VMADDR_CID_LOCAL;
+ else if (streq(s, "host"))
+ *ret = VMADDR_CID_HOST;
+ else
+ return safe_atou(s, ret);
+
+ return 0;
+}
+
int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) {
/* AF_VSOCK socket in vsock:cid:port notation */
_cleanup_free_ char *n = NULL;
if (!e)
return -EINVAL;
- r = safe_atou(e+1, &port);
+ r = vsock_parse_port(e+1, &port);
if (r < 0)
return r;
if (isempty(n))
cid = VMADDR_CID_ANY;
else {
- r = safe_atou(n, &cid);
+ r = vsock_parse_cid(n, &cid);
if (r < 0)
return r;
}
*ret_address = (SocketAddress) {
.sockaddr.vm = {
- .svm_cid = cid,
.svm_family = AF_VSOCK,
+ .svm_cid = cid,
.svm_port = port,
},
.type = type,
return 0;
}
+
+int vsock_get_local_cid(unsigned *ret) {
+ _cleanup_close_ int vsock_fd = -EBADF;
+
+ assert(ret);
+
+ vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC);
+ if (vsock_fd < 0)
+ return log_debug_errno(errno, "Failed to open /dev/vsock: %m");
+
+ if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, ret) < 0)
+ return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m");
+
+ return 0;
+}
int getpeername_pretty(int fd, bool include_port, char **ret);
int getsockname_pretty(int fd, char **ret);
-int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret);
+int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **_ret);
const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_;
SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_;
int connect_unix_path(int fd, int dir_fd, const char *path);
+static inline bool VSOCK_CID_IS_REGULAR(unsigned cid) {
+ /* 0, 1, 2, UINT32_MAX are special, refuse those */
+ return cid > 2 && cid < UINT32_MAX;
+}
+
+int vsock_parse_port(const char *s, unsigned *ret);
+int vsock_parse_cid(const char *s, unsigned *ret);
+
/* Parses AF_UNIX and AF_VSOCK addresses. AF_INET[6] require some netlink calls, so it cannot be in
* src/basic/ and is done from 'socket_local_address from src/shared/. Return -EPROTO in case of
* protocol mismatch. */
* /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file
* authoritative. */
#define SOMAXCONN_DELUXE INT_MAX
+
+int vsock_get_local_cid(unsigned *ret);
#include "macro.h"
#include "missing_stat.h"
#include "siphash24.h"
+#include "time-util.h"
int is_symlink(const char *path);
int is_dir_full(int atfd, const char *fname, bool follow);
} var
#endif
+static inline usec_t statx_timestamp_load(const struct statx_timestamp *ts) {
+ return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
+}
+static inline nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) {
+ return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
+}
+
void inode_hash_func(const struct stat *q, struct siphash *state);
int inode_compare_func(const struct stat *a, const struct stat *b);
extern const struct hash_ops inode_hash_ops;
}
char *strrstr(const char *haystack, const char *needle) {
- const char *f = NULL;
- size_t l;
-
- /* Like strstr() but returns the last rather than the first occurence of "needle" in "haystack". */
+ /* Like strstr() but returns the last rather than the first occurrence of "needle" in "haystack". */
if (!haystack || !needle)
return NULL;
- l = strlen(needle);
-
- /* Special case: for the empty string we return the very last possible occurence, i.e. *after* the
+ /* Special case: for the empty string we return the very last possible occurrence, i.e. *after* the
* last char, not before. */
- if (l == 0)
+ if (*needle == 0)
return strchr(haystack, 0);
- for (const char *p = haystack; *p; p++)
- if (strneq(p, needle, l))
- f = p;
-
- return (char*) f;
+ for (const char *p = strstr(haystack, needle), *q; p; p = q) {
+ q = strstr(p + 1, needle);
+ if (!q)
+ return (char *) p;
+ }
+ return NULL;
}
return TAKE_PTR(result);
}
+int strv_copy_unless_empty(char * const *l, char ***ret) {
+ assert(ret);
+
+ if (strv_isempty(l)) {
+ *ret = NULL;
+ return 0;
+ }
+
+ char **copy = strv_copy(l);
+ if (!copy)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(copy);
+ return 1;
+}
+
size_t strv_length(char * const *l) {
size_t n = 0;
char **c;
size_t n, m;
+ assert(l);
+
if (!value)
return 0;
n = strv_length(*l);
position = MIN(position, n);
- /* increase and check for overflow */
- m = n + 2;
- if (m < n)
+ /* check for overflow and increase*/
+ if (n > SIZE_MAX - 2)
return -ENOMEM;
+ m = n + 2;
- c = new(char*, m);
+ c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m), sizeof(char*));
if (!c)
return -ENOMEM;
- for (size_t i = 0; i < position; i++)
- c[i] = (*l)[i];
+ if (n > position)
+ memmove(c + position + 1, c + position, (n - position) * sizeof(char*));
+
c[position] = value;
- for (size_t i = position; i < n; i++)
- c[i+1] = (*l)[i];
- c[n+1] = NULL;
+ c[n + 1] = NULL;
- return free_and_replace(*l, c);
+ *l = c;
+ return 0;
}
int strv_consume_with_size(char ***l, size_t *n, char *value) {
return strv_consume_with_size(l, n, v);
}
-int strv_extend_front(char ***l, const char *value) {
+int strv_extend_many_internal(char ***l, const char *value, ...) {
+ va_list ap;
size_t n, m;
- char *v, **c;
+ int r;
assert(l);
- /* Like strv_extend(), but prepends rather than appends the new entry */
+ m = n = strv_length(*l);
- if (!value)
- return 0;
+ r = 0;
+ va_start(ap, value);
+ for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) {
+ if (!s)
+ continue;
- n = strv_length(*l);
+ if (m > SIZE_MAX-1) { /* overflow */
+ r = -ENOMEM;
+ break;
+ }
+ m++;
+ }
+ va_end(ap);
- /* Increase and overflow check. */
- m = n + 2;
- if (m < n)
+ if (r < 0)
+ return r;
+ if (m > SIZE_MAX-1)
return -ENOMEM;
- v = strdup(value);
- if (!v)
+ char **c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m+1), sizeof(char*));
+ if (!c)
return -ENOMEM;
+ *l = c;
- c = reallocarray(*l, m, sizeof(char*));
- if (!c) {
- free(v);
- return -ENOMEM;
+ r = 0;
+ size_t i = n;
+ va_start(ap, value);
+ for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) {
+ if (!s)
+ continue;
+
+ c[i] = strdup(s);
+ if (!c[i]) {
+ r = -ENOMEM;
+ break;
+ }
+ i++;
}
+ va_end(ap);
- memmove(c+1, c, n * sizeof(char*));
- c[0] = v;
- c[n+1] = NULL;
+ if (r < 0) {
+ /* rollback on error */
+ for (size_t j = n; j < i; j++)
+ c[j] = mfree(c[j]);
+ return r;
+ }
- *l = c;
+ c[i] = NULL;
return 0;
}
static inline char** strv_copy(char * const *l) {
return strv_copy_n(l, SIZE_MAX);
}
+int strv_copy_unless_empty(char * const *l, char ***ret);
+
size_t strv_length(char * const *l) _pure_;
int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates);
return strv_extend_with_size(l, NULL, value);
}
+int strv_extend_many_internal(char ***l, const char *value, ...);
+#define strv_extend_many(l, ...) strv_extend_many_internal(l, __VA_ARGS__, POINTER_MAX)
+
int strv_extendf(char ***l, const char *format, ...) _printf_(2,3);
-int strv_extend_front(char ***l, const char *value);
int strv_push_with_size(char ***l, size_t *n, char *value);
static inline int strv_push(char ***l, char *value) {
/* Always include UTC */
r = strv_extend(&zones, "UTC");
if (r < 0)
- return -ENOMEM;
+ return r;
strv_sort(zones);
strv_uniq(zones);
#include "fileio.h"
#include "missing_threads.h"
#include "string-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
static const UGIDAllocationRange default_ugid_allocation_range = {
#include "uid-range.h"
#include "user-util.h"
-UidRange *uid_range_free(UidRange *range) {
+UIDRange *uid_range_free(UIDRange *range) {
if (!range)
return NULL;
return mfree(range);
}
-static bool uid_range_entry_intersect(const UidRangeEntry *a, const UidRangeEntry *b) {
+static bool uid_range_entry_intersect(const UIDRangeEntry *a, const UIDRangeEntry *b) {
assert(a);
assert(b);
return a->start <= b->start + b->nr && a->start + a->nr >= b->start;
}
-static int uid_range_entry_compare(const UidRangeEntry *a, const UidRangeEntry *b) {
+static int uid_range_entry_compare(const UIDRangeEntry *a, const UIDRangeEntry *b) {
int r;
assert(a);
return CMP(a->nr, b->nr);
}
-static void uid_range_coalesce(UidRange *range) {
+static void uid_range_coalesce(UIDRange *range) {
assert(range);
if (range->n_entries <= 0)
typesafe_qsort(range->entries, range->n_entries, uid_range_entry_compare);
for (size_t i = 0; i < range->n_entries; i++) {
- UidRangeEntry *x = range->entries + i;
+ UIDRangeEntry *x = range->entries + i;
for (size_t j = i + 1; j < range->n_entries; j++) {
- UidRangeEntry *y = range->entries + j;
+ UIDRangeEntry *y = range->entries + j;
uid_t begin, end;
if (!uid_range_entry_intersect(x, y))
x->nr = end - begin;
if (range->n_entries > j + 1)
- memmove(y, y + 1, sizeof(UidRangeEntry) * (range->n_entries - j - 1));
+ memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1));
range->n_entries--;
j--;
}
}
-int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesce) {
- _cleanup_(uid_range_freep) UidRange *range_new = NULL;
- UidRange *p;
+int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce) {
+ _cleanup_(uid_range_freep) UIDRange *range_new = NULL;
+ UIDRange *p;
assert(range);
if (*range)
p = *range;
else {
- range_new = new0(UidRange, 1);
+ range_new = new0(UIDRange, 1);
if (!range_new)
return -ENOMEM;
if (!GREEDY_REALLOC(p->entries, p->n_entries + 1))
return -ENOMEM;
- p->entries[p->n_entries++] = (UidRangeEntry) {
+ p->entries[p->n_entries++] = (UIDRangeEntry) {
.start = start,
.nr = nr,
};
return 0;
}
-int uid_range_add_str(UidRange **range, const char *s) {
+int uid_range_add_str(UIDRange **range, const char *s) {
uid_t start, end;
int r;
return uid_range_add_internal(range, start, end - start + 1, /* coalesce = */ true);
}
-int uid_range_next_lower(const UidRange *range, uid_t *uid) {
+int uid_range_next_lower(const UIDRange *range, uid_t *uid) {
uid_t closest = UID_INVALID, candidate;
assert(range);
return 1;
}
-bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr) {
+bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr) {
if (nr == 0) /* empty range? always covered... */
return true;
return 0;
}
-int uid_range_load_userns(UidRange **ret, const char *path) {
- _cleanup_(uid_range_freep) UidRange *range = NULL;
+int uid_range_load_userns(UIDRange **ret, const char *path) {
+ _cleanup_(uid_range_freep) UIDRange *range = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
return r;
}
- range = new0(UidRange, 1);
+ range = new0(UIDRange, 1);
if (!range)
return -ENOMEM;
#include "macro.h"
-typedef struct UidRangeEntry {
+typedef struct UIDRangeEntry {
uid_t start, nr;
-} UidRangeEntry;
+} UIDRangeEntry;
-typedef struct UidRange {
- UidRangeEntry *entries;
+typedef struct UIDRange {
+ UIDRangeEntry *entries;
size_t n_entries;
-} UidRange;
+} UIDRange;
-UidRange *uid_range_free(UidRange *range);
-DEFINE_TRIVIAL_CLEANUP_FUNC(UidRange*, uid_range_free);
+UIDRange *uid_range_free(UIDRange *range);
+DEFINE_TRIVIAL_CLEANUP_FUNC(UIDRange*, uid_range_free);
-int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesce);
-static inline int uid_range_add(UidRange **range, uid_t start, uid_t nr) {
+int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce);
+static inline int uid_range_add(UIDRange **range, uid_t start, uid_t nr) {
return uid_range_add_internal(range, start, nr, true);
}
-int uid_range_add_str(UidRange **range, const char *s);
+int uid_range_add_str(UIDRange **range, const char *s);
-int uid_range_next_lower(const UidRange *range, uid_t *uid);
+int uid_range_next_lower(const UIDRange *range, uid_t *uid);
-bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr);
-static inline bool uid_range_contains(const UidRange *range, uid_t uid) {
+bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr);
+static inline bool uid_range_contains(const UIDRange *range, uid_t uid) {
return uid_range_covers(range, uid, 1);
}
int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_range);
-int uid_range_load_userns(UidRange **ret, const char *path);
+int uid_range_load_userns(UIDRange **ret, const char *path);
static int synthesize_user_creds(
const char **username,
- uid_t *uid, gid_t *gid,
- const char **home,
- const char **shell,
+ uid_t *ret_uid, gid_t *ret_gid,
+ const char **ret_home,
+ const char **ret_shell,
UserCredsFlags flags) {
+ assert(username);
+ assert(*username);
+
/* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode
* their user record data. */
if (STR_IN_SET(*username, "root", "0")) {
*username = "root";
- if (uid)
- *uid = 0;
- if (gid)
- *gid = 0;
-
- if (home)
- *home = "/root";
-
- if (shell)
- *shell = default_root_shell(NULL);
+ if (ret_uid)
+ *ret_uid = 0;
+ if (ret_gid)
+ *ret_gid = 0;
+ if (ret_home)
+ *ret_home = "/root";
+ if (ret_shell)
+ *ret_shell = default_root_shell(NULL);
return 0;
}
synthesize_nobody()) {
*username = NOBODY_USER_NAME;
- if (uid)
- *uid = UID_NOBODY;
- if (gid)
- *gid = GID_NOBODY;
-
- if (home)
- *home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/";
-
- if (shell)
- *shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN;
+ if (ret_uid)
+ *ret_uid = UID_NOBODY;
+ if (ret_gid)
+ *ret_gid = GID_NOBODY;
+ if (ret_home)
+ *ret_home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/";
+ if (ret_shell)
+ *ret_shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN;
return 0;
}
int get_user_creds(
const char **username,
- uid_t *uid, gid_t *gid,
- const char **home,
- const char **shell,
+ uid_t *ret_uid, gid_t *ret_gid,
+ const char **ret_home,
+ const char **ret_shell,
UserCredsFlags flags) {
+ bool patch_username = false;
uid_t u = UID_INVALID;
struct passwd *p;
int r;
assert(*username);
if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
- (!home && !shell)) {
+ (!ret_home && !ret_shell)) {
/* So here's the deal: normally, we'll try to synthesize all records we can synthesize, and override
* the user database with that. However, if the user specifies USER_CREDS_PREFER_NSS then the
* of the relevant users, but changing the UID/GID mappings for them is something we explicitly don't
* support. */
- r = synthesize_user_creds(username, uid, gid, home, shell, flags);
+ r = synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags);
if (r >= 0)
return 0;
if (r != -ENOMEDIUM) /* not a username we can synthesize */
* instead of the first occurrence in the database. However if the uid was configured by a numeric uid,
* then let's pick the real username from /etc/passwd. */
if (p)
- *username = p->pw_name;
- else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !gid && !home && !shell) {
+ patch_username = true;
+ else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) {
/* If the specified user is a numeric UID and it isn't in the user database, and the caller
* passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that
* and don't complain. */
- if (uid)
- *uid = u;
+ if (ret_uid)
+ *ret_uid = u;
return 0;
}
r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
/* If the user requested that we only synthesize as fallback, do so now */
- if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
- if (synthesize_user_creds(username, uid, gid, home, shell, flags) >= 0)
+ if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS))
+ if (synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags) >= 0)
return 0;
- }
return r;
}
- if (uid) {
- if (!uid_is_valid(p->pw_uid))
- return -EBADMSG;
+ if (ret_uid && !uid_is_valid(p->pw_uid))
+ return -EBADMSG;
- *uid = p->pw_uid;
- }
+ if (ret_gid && !gid_is_valid(p->pw_gid))
+ return -EBADMSG;
- if (gid) {
- if (!gid_is_valid(p->pw_gid))
- return -EBADMSG;
+ if (ret_uid)
+ *ret_uid = p->pw_uid;
- *gid = p->pw_gid;
- }
+ if (ret_gid)
+ *ret_gid = p->pw_gid;
- if (home) {
- if (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
- (empty_or_root(p->pw_dir) ||
- !path_is_valid(p->pw_dir) ||
- !path_is_absolute(p->pw_dir)))
- *home = NULL; /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
- else
- *home = p->pw_dir;
- }
+ if (ret_home)
+ /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
+ *ret_home = (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
+ (empty_or_root(p->pw_dir) ||
+ !path_is_valid(p->pw_dir) ||
+ !path_is_absolute(p->pw_dir))) ? NULL : p->pw_dir;
- if (shell) {
- if (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
- (isempty(p->pw_shell) ||
- !path_is_valid(p->pw_dir) ||
- !path_is_absolute(p->pw_shell) ||
- is_nologin_shell(p->pw_shell)))
- *shell = NULL;
- else
- *shell = p->pw_shell;
- }
+ if (ret_shell)
+ *ret_shell = (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
+ (isempty(p->pw_shell) ||
+ !path_is_valid(p->pw_dir) ||
+ !path_is_absolute(p->pw_shell) ||
+ is_nologin_shell(p->pw_shell))) ? NULL : p->pw_shell;
+
+ if (patch_username)
+ *username = p->pw_name;
return 0;
}
-int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
- struct group *g;
- gid_t id;
+static int synthesize_group_creds(
+ const char **groupname,
+ gid_t *ret_gid) {
assert(groupname);
-
- /* We enforce some special rules for gid=0: in order to avoid NSS lookups for root we hardcode its data. */
+ assert(*groupname);
if (STR_IN_SET(*groupname, "root", "0")) {
*groupname = "root";
- if (gid)
- *gid = 0;
+ if (ret_gid)
+ *ret_gid = 0;
return 0;
}
synthesize_nobody()) {
*groupname = NOBODY_GROUP_NAME;
- if (gid)
- *gid = GID_NOBODY;
+ if (ret_gid)
+ *ret_gid = GID_NOBODY;
return 0;
}
+ return -ENOMEDIUM;
+}
+
+int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags) {
+ bool patch_groupname = false;
+ struct group *g;
+ gid_t id;
+ int r;
+
+ assert(groupname);
+ assert(*groupname);
+
+ if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
+ r = synthesize_group_creds(groupname, ret_gid);
+ if (r >= 0)
+ return 0;
+ if (r != -ENOMEDIUM) /* not a groupname we can synthesize */
+ return r;
+ }
+
if (parse_gid(*groupname, &id) >= 0) {
errno = 0;
g = getgrgid(id);
if (g)
- *groupname = g->gr_name;
+ patch_groupname = true;
else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) {
- if (gid)
- *gid = id;
+ if (ret_gid)
+ *ret_gid = id;
return 0;
}
g = getgrnam(*groupname);
}
- if (!g)
+ if (!g) {
/* getgrnam() may fail with ENOENT if /etc/group is missing.
* For us that is equivalent to the name not being defined. */
- return IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
+ r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
+
+ if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS))
+ if (synthesize_group_creds(groupname, ret_gid) >= 0)
+ return 0;
- if (gid) {
+ return r;
+ }
+
+ if (ret_gid) {
if (!gid_is_valid(g->gr_gid))
return -EBADMSG;
- *gid = g->gr_gid;
+ *ret_gid = g->gr_gid;
}
+ if (patch_groupname)
+ *groupname = g->gr_name;
+
return 0;
}
USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */
} UserCredsFlags;
-int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell, UserCredsFlags flags);
-int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags);
+int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags);
+int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags);
char* uid_to_name(uid_t uid);
char* gid_to_name(gid_t gid);
const char *vendor;
Virtualization id;
} dmi_vendor_table[] = {
- { "KVM", VIRTUALIZATION_KVM },
- { "OpenStack", VIRTUALIZATION_KVM }, /* Detect OpenStack instance as KVM in non x86 architecture */
- { "KubeVirt", VIRTUALIZATION_KVM }, /* Detect KubeVirt instance as KVM in non x86 architecture */
- { "Amazon EC2", VIRTUALIZATION_AMAZON },
- { "QEMU", VIRTUALIZATION_QEMU },
- { "VMware", VIRTUALIZATION_VMWARE }, /* https://kb.vmware.com/s/article/1009458 */
- { "VMW", VIRTUALIZATION_VMWARE },
- { "innotek GmbH", VIRTUALIZATION_ORACLE },
- { "VirtualBox", VIRTUALIZATION_ORACLE },
- { "Xen", VIRTUALIZATION_XEN },
- { "Bochs", VIRTUALIZATION_BOCHS },
- { "Parallels", VIRTUALIZATION_PARALLELS },
+ { "KVM", VIRTUALIZATION_KVM },
+ { "OpenStack", VIRTUALIZATION_KVM }, /* Detect OpenStack instance as KVM in non x86 architecture */
+ { "KubeVirt", VIRTUALIZATION_KVM }, /* Detect KubeVirt instance as KVM in non x86 architecture */
+ { "Amazon EC2", VIRTUALIZATION_AMAZON },
+ { "QEMU", VIRTUALIZATION_QEMU },
+ { "VMware", VIRTUALIZATION_VMWARE }, /* https://kb.vmware.com/s/article/1009458 */
+ { "VMW", VIRTUALIZATION_VMWARE },
+ { "innotek GmbH", VIRTUALIZATION_ORACLE },
+ { "VirtualBox", VIRTUALIZATION_ORACLE },
+ { "Xen", VIRTUALIZATION_XEN },
+ { "Bochs", VIRTUALIZATION_BOCHS },
+ { "Parallels", VIRTUALIZATION_PARALLELS },
/* https://wiki.freebsd.org/bhyve */
- { "BHYVE", VIRTUALIZATION_BHYVE },
- { "Hyper-V", VIRTUALIZATION_MICROSOFT },
- { "Apple Virtualization", VIRTUALIZATION_APPLE },
+ { "BHYVE", VIRTUALIZATION_BHYVE },
+ { "Hyper-V", VIRTUALIZATION_MICROSOFT },
+ { "Apple Virtualization", VIRTUALIZATION_APPLE },
+ { "Google Compute Engine", VIRTUALIZATION_GOOGLE }, /* https://cloud.google.com/run/docs/container-contract#sandbox */
};
int r;
return true;
}
- if (__get_cpuid(7, &eax, &ebx, &ecx, &edx)) {
+ if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) {
if (given_flag_in_set(flag, leaf7_ebx, ELEMENTSOF(leaf7_ebx), ebx))
return true;
}
[VIRTUALIZATION_POWERVM] = "powervm",
[VIRTUALIZATION_APPLE] = "apple",
[VIRTUALIZATION_SRE] = "sre",
+ [VIRTUALIZATION_GOOGLE] = "google",
[VIRTUALIZATION_VM_OTHER] = "vm-other",
[VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
VIRTUALIZATION_POWERVM,
VIRTUALIZATION_APPLE,
VIRTUALIZATION_SRE,
+ VIRTUALIZATION_GOOGLE,
VIRTUALIZATION_VM_OTHER,
VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER,
return 0;
}
-static int load_etc_kernel_install_conf(void) {
+static int load_kernel_install_conf_one(const char *dir) {
_cleanup_free_ char *layout = NULL, *p = NULL;
int r;
- p = path_join(arg_root, etc_kernel(), "install.conf");
+ assert(dir);
+
+ p = path_join(arg_root, dir, "install.conf");
if (!p)
return log_oom();
free_and_replace(arg_install_layout, layout);
}
+ return 1;
+}
+
+static int load_kernel_install_conf(void) {
+ const char *conf_root;
+ int r;
+
+ conf_root = getenv("KERNEL_INSTALL_CONF_ROOT");
+ if (conf_root)
+ return load_kernel_install_conf_one(conf_root);
+
+ FOREACH_STRING(p, "/etc/kernel", "/usr/lib/kernel") {
+ r = load_kernel_install_conf_one(p);
+ if (r != 0)
+ return r;
+ }
+
return 0;
}
if (r < 0)
return r;
- r = load_etc_kernel_install_conf();
+ r = load_kernel_install_conf();
if (r < 0)
return r;
if (!arg_make_entry_directory && arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID)
return 0;
- p = path_join(arg_root, etc_kernel(), "entry-token");
+ p = path_join(arg_root, getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/", "entry-token");
if (!p)
return log_oom();
r = boot_entry_token_ensure(
arg_root,
- etc_kernel(),
+ getenv("KERNEL_INSTALL_CONF_ROOT"),
arg_machine_id,
/* machine_id_is_random = */ false,
&arg_entry_token_type,
int get_file_version(int fd, char **ret);
int settle_entry_token(void);
-
-static inline const char* etc_kernel(void) {
- return getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/";
-}
}
/* Patch in the data we found */
- *ret_device_path = device_path_replace_node(partition_path, part_node, (EFI_DEVICE_PATH *) &hd);
+ *ret_device_path = device_path_replace_node(partition_path, part_node, &hd.Header);
return EFI_SUCCESS;
}
if (strv_isempty(arg_phase)) {
/* If no phases are specifically selected, pick everything from the beginning of the initrd
* to the beginning of shutdown. */
- if (strv_extend_strv(&arg_phase,
- STRV_MAKE("enter-initrd",
- "enter-initrd:leave-initrd",
- "enter-initrd:leave-initrd:sysinit",
- "enter-initrd:leave-initrd:sysinit:ready"),
- /* filter_duplicates= */ false) < 0)
+ if (strv_extend_many(&arg_phase,
+ "enter-initrd",
+ "enter-initrd:leave-initrd",
+ "enter-initrd:leave-initrd:sysinit",
+ "enter-initrd:leave-initrd:sysinit:ready") < 0)
return log_oom();
} else {
strv_sort(arg_phase);
if (r < 0)
return log_error_errno(r, "Failed to read '%s': %m", p);
- r = unhexmem(strstrip(s), SIZE_MAX, &v, &sz);
+ r = unhexmem(strstrip(s), &v, &sz);
if (r < 0)
return log_error_errno(r, "Failed to decode PCR value '%s': %m", s);
if (r < 0)
return log_error_errno(r, "Failed to read '%s': %m", p);
- r = unhexmem(strstrip(s), SIZE_MAX, &h, &l);
+ r = unhexmem(strstrip(s), &h, &l);
if (r < 0)
return log_error_errno(r, "Failed to decode PCR value '%s': %m", s);
};
}
-int cgroup_context_add_io_device_weight_dup(CGroupContext *c, CGroupIODeviceWeight *w) {
+int cgroup_context_add_io_device_weight_dup(CGroupContext *c, const CGroupIODeviceWeight *w) {
_cleanup_free_ CGroupIODeviceWeight *n = NULL;
assert(c);
assert(w);
- n = new0(CGroupIODeviceWeight, 1);
+ n = new(CGroupIODeviceWeight, 1);
if (!n)
return -ENOMEM;
- n->path = strdup(w->path);
+ *n = (CGroupIODeviceWeight) {
+ .path = strdup(w->path),
+ .weight = w->weight,
+ };
if (!n->path)
return -ENOMEM;
- n->weight = w->weight;
LIST_PREPEND(device_weights, c->io_device_weights, TAKE_PTR(n));
return 0;
}
-int cgroup_context_add_io_device_limit_dup(CGroupContext *c, CGroupIODeviceLimit *l) {
+int cgroup_context_add_io_device_limit_dup(CGroupContext *c, const CGroupIODeviceLimit *l) {
_cleanup_free_ CGroupIODeviceLimit *n = NULL;
assert(c);
assert(l);
n = new0(CGroupIODeviceLimit, 1);
- if (!l)
+ if (!n)
return -ENOMEM;
n->path = strdup(l->path);
return 0;
}
-int cgroup_context_add_io_device_latency_dup(CGroupContext *c, CGroupIODeviceLatency *l) {
+int cgroup_context_add_io_device_latency_dup(CGroupContext *c, const CGroupIODeviceLatency *l) {
_cleanup_free_ CGroupIODeviceLatency *n = NULL;
assert(c);
assert(l);
- n = new0(CGroupIODeviceLatency, 1);
+ n = new(CGroupIODeviceLatency, 1);
if (!n)
return -ENOMEM;
- n->path = strdup(l->path);
+ *n = (CGroupIODeviceLatency) {
+ .path = strdup(l->path),
+ .target_usec = l->target_usec,
+ };
if (!n->path)
return -ENOMEM;
- n->target_usec = l->target_usec;
-
LIST_PREPEND(device_latencies, c->io_device_latencies, TAKE_PTR(n));
return 0;
}
-int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, CGroupBlockIODeviceWeight *w) {
+int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, const CGroupBlockIODeviceWeight *w) {
_cleanup_free_ CGroupBlockIODeviceWeight *n = NULL;
assert(c);
assert(w);
- n = new0(CGroupBlockIODeviceWeight, 1);
+ n = new(CGroupBlockIODeviceWeight, 1);
if (!n)
return -ENOMEM;
- n->path = strdup(w->path);
+ *n = (CGroupBlockIODeviceWeight) {
+ .path = strdup(w->path),
+ .weight = w->weight,
+ };
if (!n->path)
return -ENOMEM;
- n->weight = w->weight;
-
LIST_PREPEND(device_weights, c->blockio_device_weights, TAKE_PTR(n));
return 0;
}
-int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) {
+int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, const CGroupBlockIODeviceBandwidth *b) {
_cleanup_free_ CGroupBlockIODeviceBandwidth *n = NULL;
assert(c);
assert(b);
- n = new0(CGroupBlockIODeviceBandwidth, 1);
+ n = new(CGroupBlockIODeviceBandwidth, 1);
if (!n)
return -ENOMEM;
return 0;
}
-int cgroup_context_add_device_allow_dup(CGroupContext *c, CGroupDeviceAllow *a) {
+int cgroup_context_add_device_allow_dup(CGroupContext *c, const CGroupDeviceAllow *a) {
_cleanup_free_ CGroupDeviceAllow *n = NULL;
assert(c);
assert(a);
- n = new0(CGroupDeviceAllow, 1);
+ n = new(CGroupDeviceAllow, 1);
if (!n)
return -ENOMEM;
- n->path = strdup(a->path);
+ *n = (CGroupDeviceAllow) {
+ .path = strdup(a->path),
+ .permissions = a->permissions,
+ };
if (!n->path)
return -ENOMEM;
- n->permissions = a->permissions;
-
LIST_PREPEND(device_allow, c->device_allow, TAKE_PTR(n));
return 0;
}
-static int cgroup_context_add_socket_bind_item_dup(CGroupContext *c, CGroupSocketBindItem *i, CGroupSocketBindItem *h) {
+static int cgroup_context_add_socket_bind_item_dup(CGroupContext *c, const CGroupSocketBindItem *i, CGroupSocketBindItem *h) {
_cleanup_free_ CGroupSocketBindItem *n = NULL;
assert(c);
assert(i);
- n = new0(CGroupSocketBindItem, 1);
+ n = new(CGroupSocketBindItem, 1);
if (!n)
return -ENOMEM;
return 0;
}
-int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, CGroupSocketBindItem *i) {
+int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, const CGroupSocketBindItem *i) {
return cgroup_context_add_socket_bind_item_dup(c, i, c->socket_bind_allow);
}
-int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, CGroupSocketBindItem *i) {
+int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, const CGroupSocketBindItem *i) {
return cgroup_context_add_socket_bind_item_dup(c, i, c->socket_bind_deny);
}
dst->tasks_accounting = src->tasks_accounting;
dst->ip_accounting = src->ip_accounting;
- dst->memory_oom_group = dst->memory_oom_group;
+ dst->memory_oom_group = src->memory_oom_group;
dst->cpu_weight = src->cpu_weight;
dst->startup_cpu_weight = src->startup_cpu_weight;
assert_not_reached();
}
- cc = unit_get_cgroup_context(u);
+ cc = ASSERT_PTR(unit_get_cgroup_context(u));
switch (type) {
/* Note: on legacy/hybrid hierarchies memory_max stays CGROUP_LIMIT_MAX unless configured
* explicitly. Effective value of MemoryLimit= (cgroup v1) is not implemented. */
int cgroup_context_add_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p);
int cgroup_context_add_or_update_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p);
int cgroup_context_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *path);
-int cgroup_context_add_io_device_limit_dup(CGroupContext *c, CGroupIODeviceLimit *l);
-int cgroup_context_add_io_device_weight_dup(CGroupContext *c, CGroupIODeviceWeight *w);
-int cgroup_context_add_io_device_latency_dup(CGroupContext *c, CGroupIODeviceLatency *l);
-int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, CGroupBlockIODeviceWeight *w);
-int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, CGroupBlockIODeviceBandwidth *b);
-int cgroup_context_add_device_allow_dup(CGroupContext *c, CGroupDeviceAllow *a);
-int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, CGroupSocketBindItem *i);
-int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, CGroupSocketBindItem *i);
-
-static inline int cgroup_context_add_bpf_foreign_program_dup(CGroupContext *c, CGroupBPFForeignProgram *p) {
+static inline int cgroup_context_add_bpf_foreign_program_dup(CGroupContext *c, const CGroupBPFForeignProgram *p) {
return cgroup_context_add_bpf_foreign_program(c, p->attach_type, p->bpffs_path);
}
+int cgroup_context_add_io_device_limit_dup(CGroupContext *c, const CGroupIODeviceLimit *l);
+int cgroup_context_add_io_device_weight_dup(CGroupContext *c, const CGroupIODeviceWeight *w);
+int cgroup_context_add_io_device_latency_dup(CGroupContext *c, const CGroupIODeviceLatency *l);
+int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, const CGroupBlockIODeviceWeight *w);
+int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, const CGroupBlockIODeviceBandwidth *b);
+int cgroup_context_add_device_allow_dup(CGroupContext *c, const CGroupDeviceAllow *a);
+int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, const CGroupSocketBindItem *i);
+int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, const CGroupSocketBindItem *i);
void unit_modify_nft_set(Unit *u, bool add);
r = strv_extend_strv(&c->supplementary_groups, l, true);
if (r < 0)
- return -ENOMEM;
+ return r;
joined = strv_join(c->supplementary_groups, " ");
if (!joined)
r = strv_extend_strv(dirs, l, true);
if (r < 0)
- return -ENOMEM;
+ return r;
unit_write_settingf(u, flags, name, "%s=%s", name, joined);
}
_cleanup_free_ char *joined = NULL;
r = strv_extend_strv(&c->exec_search_path, l, true);
if (r < 0)
- return -ENOMEM;
+ return r;
joined = strv_join(c->exec_search_path, ":");
if (!joined)
return log_oom();
unit = manager_get_unit_by_pidref(m, &p);
if (!unit) {
- log_unit_warning_errno(from, SYNTHETIC_ERRNO(ENOENT), "Failed to get unit from PIDFD, ingoring: %m");
+ log_unit_warning_errno(from, SYNTHETIC_ERRNO(ENOENT), "Failed to get unit from PIDFD, ignoring: %m");
continue;
}
#include "dbus-kill.h"
#include "dbus-mount.h"
#include "dbus-util.h"
+#include "fstab-util.h"
#include "mount.h"
#include "string-util.h"
#include "unit.h"
sd_bus_error *error) {
Unit *u = UNIT(m);
+ int r;
assert(m);
assert(name);
if (streq(name, "Where"))
return bus_set_transient_path(u, name, &m->where, message, flags, error);
- if (streq(name, "What"))
- return bus_set_transient_string(u, name, &m->parameters_fragment.what, message, flags, error);
+ if (streq(name, "What")) {
+ _cleanup_free_ char *path = NULL;
+ const char *v;
+
+ r = sd_bus_message_read(message, "s", &v);
+ if (r < 0)
+ return r;
+
+ if (!isempty(v)) {
+ path = fstab_node_to_udev_node(v);
+ if (!path)
+ return -ENOMEM;
+
+ /* path_is_valid is not used - see the comment for config_parse_mount_node */
+ if (strlen(path) >= PATH_MAX)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Resolved What=%s too long", path);
+ }
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ free_and_replace(m->parameters_fragment.what, path);
+ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "What=%s", strempty(m->parameters_fragment.what));
+ }
+
+ return 1;
+ }
if (streq(name, "Options"))
return bus_set_transient_string(u, name, &m->parameters_fragment.options, message, flags, error);
u->documentation = strv_free(u->documentation);
unit_write_settingf(u, flags, name, "%s=", name);
} else {
- strv_extend_strv(&u->documentation, l, false);
+ r = strv_extend_strv(&u->documentation, l, /* filter_duplicates= */ false);
+ if (r < 0)
+ return r;
STRV_FOREACH(p, l)
unit_write_settingf(u, flags, name, "%s=%s", name, *p);
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */
#include "fileio.h"
#include "glob-util.h"
#include "io-util.h"
+#include "iovec-util.h"
#include "label-util.h"
#include "mkdir-label.h"
#include "mount-util.h"
}
if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) {
- if (params->received_credentials_directory)
- if (strv_extend(&l, params->received_credentials_directory) < 0)
- return NULL;
+ if (strv_extend(&l, params->received_credentials_directory) < 0)
+ return NULL;
if (strv_extend_strv(&l, CONF_PATHS_STRV("credstore"), /* filter_duplicates= */ true) < 0)
return NULL;
size_t size,
uint64_t *left) {
- _cleanup_free_ void *plaintext = NULL;
+ _cleanup_(iovec_done_erase) struct iovec plaintext = {};
size_t add;
int r;
if (encrypted) {
- size_t plaintext_size = 0;
-
- r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size,
- &plaintext, &plaintext_size);
+ r = decrypt_credential_and_warn(
+ id,
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ &IOVEC_MAKE(data, size),
+ /* flags= */ 0,
+ &plaintext);
if (r < 0)
return r;
- data = plaintext;
- size = plaintext_size;
+ data = plaintext.iov_base;
+ size = plaintext.iov_len;
}
add = strlen(id) + size;
/* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
* add them, so that they can act as a "default" if the same credential is specified multiple times. */
HASHMAP_FOREACH(sc, context->set_credentials) {
- _cleanup_(erase_and_freep) void *plaintext = NULL;
+ _cleanup_(iovec_done_erase) struct iovec plaintext = {};
const char *data;
size_t size, add;
return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
if (sc->encrypted) {
- r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, NULL, sc->data, sc->size, &plaintext, &size);
+ r = decrypt_credential_and_warn(
+ sc->id,
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ &IOVEC_MAKE(sc->data, sc->size),
+ /* flags= */ 0,
+ &plaintext);
if (r < 0)
return r;
- data = plaintext;
+ data = plaintext.iov_base;
+ size = plaintext.iov_len;
} else {
data = sc->data;
size = sc->size;
* absolute, when they are processed in namespace.c they will be made relative automatically, i.e.:
* 'os-release -> .os-release-stage/os-release' is what will be created. */
if (setup_os_release_symlink) {
- r = strv_extend(&symlinks, "/run/host/.os-release-stage/os-release");
- if (r < 0)
- return r;
-
- r = strv_extend(&symlinks, "/run/host/os-release");
+ r = strv_extend_many(
+ &symlinks,
+ "/run/host/.os-release-stage/os-release",
+ "/run/host/os-release");
if (r < 0)
return r;
}
FOREACH_ARRAY(i, c->directories[dt].items, c->directories[dt].n_items) {
_cleanup_free_ char *path_escaped = NULL;
- path_escaped = shell_escape(i->path, ":");
+ path_escaped = shell_escape(i->path, ":" WHITESPACE);
if (!path_escaped)
return log_oom_debug();
STRV_FOREACH(d, i->symlinks) {
_cleanup_free_ char *link_escaped = NULL;
- link_escaped = shell_escape(*d, ":");
+ link_escaped = shell_escape(*d, ":" WHITESPACE);
if (!link_escaped)
return log_oom_debug();
FOREACH_ARRAY(mount, c->bind_mounts, c->n_bind_mounts) {
_cleanup_free_ char *src_escaped = NULL, *dst_escaped = NULL;
- src_escaped = shell_escape(mount->source, ":");
+ src_escaped = shell_escape(mount->source, ":" WHITESPACE);
if (!src_escaped)
return log_oom_debug();
- dst_escaped = shell_escape(mount->destination, ":");
+ dst_escaped = shell_escape(mount->destination, ":" WHITESPACE);
if (!dst_escaped)
return log_oom_debug();
FOREACH_ARRAY(mount, c->mount_images, c->n_mount_images) {
_cleanup_free_ char *s = NULL, *source_escaped = NULL, *dest_escaped = NULL;
- source_escaped = shell_escape(mount->source, " ");
+ source_escaped = shell_escape(mount->source, WHITESPACE);
if (!source_escaped)
return log_oom_debug();
- dest_escaped = shell_escape(mount->destination, " ");
+ dest_escaped = shell_escape(mount->destination, WHITESPACE);
if (!dest_escaped)
return log_oom_debug();
FOREACH_ARRAY(mount, c->extension_images, c->n_extension_images) {
_cleanup_free_ char *s = NULL, *source_escaped = NULL;
- source_escaped = shell_escape(mount->source, ":");
+ source_escaped = shell_escape(mount->source, ":" WHITESPACE);
if (!source_escaped)
return log_oom_debug();
return r;
} else if ((val = startswith(l, "exec-context-root-hash="))) {
c->root_hash = mfree(c->root_hash);
- r = unhexmem(val, strlen(val), &c->root_hash, &c->root_hash_size);
+ r = unhexmem(val, &c->root_hash, &c->root_hash_size);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-root-hash-sig="))) {
c->root_hash_sig = mfree(c->root_hash_sig);
- r= unbase64mem(val, strlen(val), &c->root_hash_sig, &c->root_hash_sig_size);
+ r= unbase64mem(val, &c->root_hash_sig, &c->root_hash_sig_size);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-root-ephemeral="))) {
_cleanup_free_ char *tuple = NULL, *path = NULL, *only_create = NULL;
const char *p;
- r = extract_first_word(&val, &tuple, WHITESPACE, EXTRACT_RETAIN_ESCAPE);
+ /* Use EXTRACT_UNESCAPE_RELAX here, as we unescape the colons in subsequent calls */
+ r = extract_first_word(&val, &tuple, WHITESPACE, EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX);
if (r < 0)
return r;
if (r == 0)
if (c->stdin_data)
return -EINVAL; /* duplicated */
- r = unbase64mem(val, strlen(val), &c->stdin_data, &c->stdin_data_size);
+ r = unbase64mem(val, &c->stdin_data, &c->stdin_data_size);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-tty-path="))) {
.encrypted = r,
};
- r = unbase64mem(data, strlen(data), &sc->data, &sc->size);
+ r = unbase64mem(data, &sc->data, &sc->size);
if (r < 0)
return r;
if (r < 0)
return log_unit_error_errno(unit, r, "Failed to load environment files: %m");
- /* Fork with up-to-date SELinux label database, so the child inherits the up-to-date db
- and, until the next SELinux policy changes, we save further reloads in future children. */
- mac_selinux_maybe_reload();
-
/* We won't know the real executable path until we create the mount namespace in the child, but we
want to log from the parent, so we use the possibly inaccurate path here. */
log_command_line(unit, "About to execute", command->path, command->argv);
colon++;
if (base64) {
- r = unbase64mem(colon, SIZE_MAX, &binary, &l);
+ r = unbase64mem(colon, &binary, &l);
if (r < 0) {
log_warning_errno(r, "Failed to decode binary credential '%s' data, ignoring: %m", n);
return 0;
/* Optionally base64 decode the data, if requested, to allow binary credentials */
if (unbase64) {
- r = unbase64mem(eq + 1, nul - (eq + 1), &buf, &buflen);
+ r = unbase64mem_full(eq + 1, nul - (eq + 1), /* secure = */ false, &buf, &buflen);
if (r < 0) {
log_warning_errno(r, "Failed to base64 decode credential '%s', ignoring: %m", cn);
continue;
{{ EXEC_CONTEXT_CONFIG_ITEMS('Socket') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Socket') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Socket') }}
-Mount.What, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.what)
+Mount.What, config_parse_mount_node, 0, offsetof(Mount, parameters_fragment.what)
Mount.Where, config_parse_unit_path_printf, 0, offsetof(Mount, where)
Mount.Options, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.options)
Mount.Type, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.fstype)
Automount.ExtraOptions, config_parse_unit_string_printf, 0, offsetof(Automount, extra_options)
Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode)
Automount.TimeoutIdleSec, config_parse_sec_fix_0, 0, offsetof(Automount, timeout_idle_usec)
-Swap.What, config_parse_unit_path_printf, 0, offsetof(Swap, parameters_fragment.what)
+Swap.What, config_parse_mount_node, 0, offsetof(Swap, parameters_fragment.what)
Swap.Priority, config_parse_swap_priority, 0, 0
Swap.Options, config_parse_unit_string_printf, 0, offsetof(Swap, parameters_fragment.options)
Swap.TimeoutSec, config_parse_sec_fix_0, 0, offsetof(Swap, timeout_usec)
#include "fileio.h"
#include "firewall-util.h"
#include "fs-util.h"
+#include "fstab-util.h"
#include "hexdecoct.h"
#include "iovec-util.h"
#include "ioprio-util.h"
return 0;
}
- r = unbase64mem(rvalue, SIZE_MAX, &p, &sz);
+ r = unbase64mem(rvalue, &p, &sz);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to decode base64 data, ignoring: %s", rvalue);
}
/* We have a roothash to decode, eg: RootHash=012345789abcdef */
- r = unhexmem(rvalue, strlen(rvalue), &roothash_decoded, &roothash_decoded_size);
+ r = unhexmem(rvalue, &roothash_decoded, &roothash_decoded_size);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to decode RootHash=, ignoring: %s", rvalue);
return 0;
}
/* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */
- r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size);
+ r = unbase64mem(value, &roothash_sig_decoded, &roothash_sig_decoded_size);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to decode RootHashSignature=, ignoring: %s", rvalue);
return 0;
return 0;
}
- r = unit_full_printf_full(u, rvalue, PATH_MAX, &n);
+ r = unit_path_printf(u, rvalue, &n);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue);
return 0;
if (r == 0)
break;
- r = unit_full_printf_full(u, source, PATH_MAX, &sresolved);
+ r = unit_path_printf(u, source, &sresolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to resolve unit specifiers in \"%s\", ignoring: %m", source);
return 0;
}
+int config_parse_mount_node(
+ 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) {
+
+ const Unit *u = ASSERT_PTR(userdata);
+ _cleanup_free_ char *resolved = NULL, *path = NULL;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = unit_full_printf(u, rvalue, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue);
+ return 0;
+ }
+
+ path = fstab_node_to_udev_node(resolved);
+ if (!path)
+ return log_oom();
+
+ /* The source passed is not necessarily something we understand, and we pass it as-is to mount/swapon,
+ * so path_is_valid is not used. But let's check for basic sanety, i.e. if the source is longer than
+ * PATH_MAX, you're likely doing something wrong. */
+ if (strlen(path) >= PATH_MAX) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Resolved mount path '%s' too long, ignoring.", path);
+ return 0;
+ }
+
+ return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, path, data, userdata);
+}
+
static int merge_by_names(Unit *u, Set *names, const char *id) {
char *k;
int r;
{ config_parse_job_mode_isolate, "BOOLEAN" },
{ config_parse_personality, "PERSONALITY" },
{ config_parse_log_filter_patterns, "REGEX" },
+ { config_parse_mount_node, "NODE" },
};
const char *prev = NULL;
CONFIG_PARSER_PROTOTYPE(config_parse_open_file);
CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch);
CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
+CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
if (proc_cmdline_value_missing(key, value))
return 0;
- r = unbase64mem(value, SIZE_MAX, &p, &sz);
+ r = unbase64mem(value, &p, &sz);
if (r < 0)
log_warning_errno(r, "Failed to parse systemd.random_seed= argument, ignoring: %s", value);
(void) exec_shared_runtime_deserialize_one(m, val, fds);
else if ((val = startswith(l, "subscribed="))) {
- if (strv_extend(&m->deserialized_subscribed, val) < 0)
- return -ENOMEM;
+ r = strv_extend(&m->deserialized_subscribed, val);
+ if (r < 0)
+ return r;
} else if ((val = startswith(l, "varlink-server-socket-address="))) {
if (!m->varlink_server && MANAGER_IS_SYSTEM(m)) {
r = manager_varlink_init(m);
}
static int short_uid_range(const char *path) {
- _cleanup_(uid_range_freep) UidRange *p = NULL;
+ _cleanup_(uid_range_freep) UIDRange *p = NULL;
int r;
assert(path);
return log_debug_errno(r, "Failed to parse mount option '%s': %m", str);
ro = flags & MS_RDONLY;
- if (ro)
- flags ^= MS_RDONLY;
+ flags &= ~MS_RDONLY;
MountEntry *me = mount_list_extend(ml);
if (!me)
if (isempty(p->trigger_path_filename))
return 0;
- r = strv_extend(strv, "trigger_path");
- if (r < 0)
- return r;
-
- r = strv_extend(strv, p->trigger_path_filename);
+ r = strv_extend_many(strv, "trigger_path", p->trigger_path_filename);
if (r < 0)
return r;
case STATE_EXEC_COMMAND_ARGS:
r = strv_extend(&argv, arg);
if (r < 0)
- return -ENOMEM;
+ return r;
break;
default:
assert_not_reached();
#include "cgroup-util.h"
#include "format-util.h"
#include "macro.h"
+#include "sd-path.h"
#include "specifier.h"
#include "string-util.h"
#include "strv.h"
return 0;
}
+static int specifier_shared_data_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
+ const Unit *u = ASSERT_PTR(userdata);
+
+ assert(ret);
+
+ return sd_path_lookup(MANAGER_IS_SYSTEM(u->manager) ? SD_PATH_SYSTEM_SHARED : SD_PATH_USER_SHARED, NULL, ret);
+}
+
int unit_name_printf(const Unit *u, const char* format, char **ret) {
/*
* This will use the passed string as format string and replace the following specifiers (which should all be
*
* %C: the cache directory root (e.g. /var/cache or $XDG_CACHE_HOME)
* %d: the credentials directory ($CREDENTIALS_DIRECTORY)
+ * %D: the shared data root (e.g. /usr/share or $XDG_DATA_HOME)
* %E: the configuration directory root (e.g. /etc or $XDG_CONFIG_HOME)
* %L: the log directory root (e.g. /var/log or $XDG_STATE_HOME/log)
* %S: the state directory root (e.g. /var/lib or $XDG_STATE_HOME)
{ 'C', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_CACHE) },
{ 'd', specifier_credentials_dir, NULL },
+ { 'D', specifier_shared_data_dir, NULL },
{ 'E', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_CONFIGURATION) },
{ 'L', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_LOGS) },
{ 'S', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_STATE) },
return 0;
if (!isempty(details->trigger_unit_name)) {
- r = strv_extend(strv, "trigger_unit");
- if (r < 0)
- return r;
-
- r = strv_extend(strv, details->trigger_unit_name);
+ r = strv_extend_many(strv, "trigger_unit", details->trigger_unit_name);
if (r < 0)
return r;
}
- if (ACTIVATION_DETAILS_VTABLE(details)->append_env) {
+ if (ACTIVATION_DETAILS_VTABLE(details)->append_pair) {
r = ACTIVATION_DETAILS_VTABLE(details)->append_pair(details, strv);
if (r < 0)
return r;
#include "strv.h"
#include "sync-util.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
/* The maximum size up to which we process coredumps. We use 1G on 32-bit systems, and 32G on 64-bit systems */
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
if (r < 0)
return r;
- r = strv_extend_strv(&debugger_call, STRV_MAKE(exe, "-c", path), false);
+ r = strv_extend_many(&debugger_call, exe, "-c", path);
if (r < 0)
return log_oom();
const char *sysroot_cmd;
sysroot_cmd = strjoina("set sysroot ", arg_root);
- r = strv_extend_strv(&debugger_call, STRV_MAKE("-iex", sysroot_cmd), false);
+ r = strv_extend_many(&debugger_call, "-iex", sysroot_cmd);
if (r < 0)
return log_oom();
} else if (streq(arg_debugger, "lldb")) {
const char *sysroot_cmd;
sysroot_cmd = strjoina("platform select --sysroot ", arg_root, " host");
- r = strv_extend_strv(&debugger_call, STRV_MAKE("-O", sysroot_cmd), false);
+ r = strv_extend_many(&debugger_call, "-O", sysroot_cmd);
if (r < 0)
return log_oom();
}
}
if (encrypted) {
- _cleanup_(erase_and_freep) void *plaintext = NULL;
- size_t plaintext_size;
+ _cleanup_(iovec_done_erase) struct iovec plaintext = {};
r = decrypt_credential_and_warn(
*cn,
timestamp,
arg_tpm2_device,
arg_tpm2_signature,
- data, size,
- &plaintext, &plaintext_size);
+ &IOVEC_MAKE(data, size),
+ /* flags= */ 0,
+ &plaintext);
if (r < 0)
return r;
erase_and_free(data);
- data = TAKE_PTR(plaintext);
- size = plaintext_size;
+ data = TAKE_PTR(plaintext.iov_base);
+ size = plaintext.iov_len;
}
r = write_blob(stdout, data, size);
}
static int verb_encrypt(int argc, char **argv, void *userdata) {
+ _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {};
_cleanup_free_ char *base64_buf = NULL, *fname = NULL;
- _cleanup_(erase_and_freep) char *plaintext = NULL;
const char *input_path, *output_path, *name;
- _cleanup_free_ void *output = NULL;
- size_t plaintext_size, output_size;
ssize_t base64_size;
usec_t timestamp;
int r;
input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
if (input_path)
- r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &plaintext, &plaintext_size);
+ r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, (char**) &plaintext.iov_base, &plaintext.iov_len);
else
- r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, &plaintext, &plaintext_size);
+ r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, (char**) &plaintext.iov_base, &plaintext.iov_len);
if (r == -E2BIG)
return log_error_errno(r, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX);
if (r < 0)
arg_tpm2_pcr_mask,
arg_tpm2_public_key,
arg_tpm2_public_key_pcr_mask,
- plaintext, plaintext_size,
- &output, &output_size);
+ &plaintext,
+ /* flags= */ 0,
+ &output);
if (r < 0)
return r;
- base64_size = base64mem_full(output, output_size, arg_pretty ? 69 : 79, &base64_buf);
+ base64_size = base64mem_full(output.iov_base, output.iov_len, arg_pretty ? 69 : 79, &base64_buf);
if (base64_size < 0)
return base64_size;
}
static int verb_decrypt(int argc, char **argv, void *userdata) {
- _cleanup_(erase_and_freep) void *plaintext = NULL;
- _cleanup_free_ char *input = NULL, *fname = NULL;
+ _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {};
+ _cleanup_free_ char *fname = NULL;
_cleanup_fclose_ FILE *output_file = NULL;
const char *input_path, *output_path, *name;
- size_t input_size, plaintext_size;
usec_t timestamp;
FILE *f;
int r;
input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
if (input_path)
- r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &input, &input_size);
+ r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, (char**) &input, &input.iov_len);
else
- r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, &input, &input_size);
+ r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, (char**) &input, &input.iov_len);
if (r == -E2BIG)
return log_error_errno(r, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX);
if (r < 0)
timestamp,
arg_tpm2_device,
arg_tpm2_signature,
- input, input_size,
- &plaintext, &plaintext_size);
+ &input,
+ /* flags= */ 0,
+ &plaintext);
if (r < 0)
return r;
} else
f = stdout;
- r = write_blob(f, plaintext, plaintext_size);
+ r = write_blob(f, plaintext.iov_base, plaintext.iov_len);
if (r < 0)
return r;
}
static int verb_setup(int argc, char **argv, void *userdata) {
- size_t size;
+ _cleanup_(iovec_done_erase) struct iovec host_key = {};
int r;
- r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, NULL, &size);
+ r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, &host_key);
if (r < 0)
return log_error_errno(r, "Failed to setup credentials host key: %m");
- log_info("%zu byte credentials host key set up.", size);
+ log_info("%zu byte credentials host key set up.", host_key.iov_len);
return EXIT_SUCCESS;
}
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
else if (STR_IN_SET(optarg, "host+tpm2-with-public-key", "tpm2-with-public-key+host"))
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK;
- else if (streq(optarg, "tpm2-absent"))
- arg_with_key = CRED_AES256_GCM_BY_TPM2_ABSENT;
+ else if (STR_IN_SET(optarg, "null", "tpm2-absent"))
+ arg_with_key = CRED_AES256_GCM_BY_NULL;
else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg);
assert(link);
- json_variant_sensitive(parameters);
-
r = varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
arg_tpm2_pcr_mask,
arg_tpm2_public_key,
arg_tpm2_public_key_pcr_mask,
- p.text ?: p.data.iov_base, p.text ? strlen(p.text) : p.data.iov_len,
- &output.iov_base, &output.iov_len);
+ p.text ? &IOVEC_MAKE_STRING(p.text) : &p.data,
+ /* flags= */ 0,
+ &output);
if (r < 0)
return r;
assert(link);
- /* Let's also mark the (theoretically encrypted) input as sensitive, in case the NULL encryption scheme was used. */
- json_variant_sensitive(parameters);
-
r = varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
p.timestamp,
arg_tpm2_device,
arg_tpm2_signature,
- p.blob.iov_base, p.blob.iov_len,
- &output.iov_base, &output.iov_len);
+ &p.blob,
+ /* flags= */ 0,
+ &output);
if (r == -EBADMSG)
return varlink_error(link, "io.systemd.Credentials.BadFormat", NULL);
if (r == -EREMOTE)
/* Invocation as Varlink service */
- r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
+ r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
#include "openssl-util.h"
#include "pkcs11-util.h"
+static int uri_set_private_class(const char *uri, char **ret_uri) {
+ _cleanup_(sym_p11_kit_uri_freep) P11KitUri *p11kit_uri = NULL;
+ _cleanup_free_ char *private_uri = NULL;
+ int r;
+
+ r = uri_from_string(uri, &p11kit_uri);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", uri);
+
+ if (sym_p11_kit_uri_get_attribute(p11kit_uri, CKA_CLASS)) {
+ CK_OBJECT_CLASS class = CKO_PRIVATE_KEY;
+ CK_ATTRIBUTE attribute = { CKA_CLASS, &class, sizeof(class) };
+
+ if (sym_p11_kit_uri_set_attribute(p11kit_uri, &attribute) != P11_KIT_URI_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set class for URI '%s': %m", uri);
+
+ if (sym_p11_kit_uri_format(p11kit_uri, P11_KIT_URI_FOR_ANY, &private_uri) != P11_KIT_URI_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to format PKCS#11 URI: %m");
+ }
+
+ *ret_uri = TAKE_PTR(private_uri);
+ return 0;
+}
+
int enroll_pkcs11(
struct crypt_device *cd,
const void *volume_key,
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- _cleanup_free_ char *keyslot_as_string = NULL;
+ _cleanup_free_ char *keyslot_as_string = NULL, *private_uri = NULL;
size_t decrypted_key_size, saved_key_size;
_cleanup_free_ void *saved_key = NULL;
_cleanup_(X509_freep) X509 *cert = NULL;
ssize_t base64_encoded_size;
const char *node;
- int keyslot, r;
+ int r;
assert_se(cd);
assert_se(volume_key);
if (r < 0)
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
- keyslot = crypt_keyslot_add_by_volume_key(
+ int keyslot = crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return log_oom();
+ /* Change 'type=cert' in the provided URI to 'type=private' before storing in a LUKS2 header.
+ This allows users to use output of some PKCS#11 tools directly without modifications. */
+ r = uri_set_private_class(uri, &private_uri);
+ if (r < 0)
+ return r;
+
r = json_build(&v,
- JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")),
- JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
- JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(uri)),
- JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(saved_key, saved_key_size))));
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")),
+ JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
+ JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(private_uri ?: uri)),
+ JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(saved_key, saved_key_size))));
if (r < 0)
return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m");
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-policy-hash' field.");
- r = unhexmem(json_variant_string(w), SIZE_MAX, &thash, &thash_size);
+ r = unhexmem(json_variant_string(w), &thash, &thash_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'tpm2-policy-hash' field.");
bool use_pin,
const char *pcrlock_path) {
- _cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
- _cleanup_free_ void *srk_buf = NULL;
- size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL;
+ _cleanup_(iovec_done) struct iovec srk = {}, blob = {}, pubkey = {};
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
const char *node;
_cleanup_(erase_and_freep) char *pin_str = NULL;
ssize_t base64_encoded_size;
}
TPM2B_PUBLIC public = {};
- r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size);
+ r = tpm2_load_pcr_public_key(pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (pubkey_path || signature_path || r != -ENOENT)
return log_error_errno(r, "Failed to read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
pubkey_pcr_mask = 0;
} else {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public);
+ r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
r = tpm2_calculate_sealing_policy(
hash_pcr_values,
n_hash_pcr_values,
- pubkey ? &public : NULL,
+ iovec_is_set(&pubkey) ? &public : NULL,
use_pin,
pcrlock_path ? &pcrlock_policy : NULL,
&policy);
seal_key_handle,
&device_key_public,
/* attributes= */ NULL,
- /* secret= */ NULL, /* secret_size= */ 0,
+ /* secret= */ NULL,
&policy,
pin_str,
- &secret, &secret_size,
- &blob, &blob_size,
- &srk_buf, &srk_buf_size);
+ &secret,
+ &blob,
+ &srk);
else
r = tpm2_seal(tpm2_context,
seal_key_handle,
&policy,
pin_str,
- &secret, &secret_size,
- &blob, &blob_size,
+ &secret,
+ &blob,
/* ret_primary_alg= */ NULL,
- &srk_buf, &srk_buf_size);
+ &srk);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
}
/* If possible, verify the sealed data object. */
- if ((!pubkey || signature_json) && !any_pcr_value_specified && !device_key) {
- _cleanup_(erase_and_freep) void *secret2 = NULL;
- size_t secret2_size;
+ if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !device_key) {
+ _cleanup_(iovec_done_erase) struct iovec secret2 = {};
log_debug("Unsealing for verification...");
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
hash_pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
pubkey_pcr_mask,
signature_json,
pin_str,
pcrlock_path ? &pcrlock_policy : NULL,
/* primary_alg= */ 0,
- blob, blob_size,
- policy.buffer, policy.size,
- srk_buf, srk_buf_size,
- &secret2, &secret2_size);
+ &blob,
+ &IOVEC_MAKE(policy.buffer, policy.size),
+ &srk,
+ &secret2);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
- if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
+ if (iovec_memcmp(&secret, &secret2) != 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
}
/* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */
- base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
+ base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded);
if (base64_encoded_size < 0)
return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
keyslot,
hash_pcr_mask,
hash_pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
pubkey_pcr_mask,
/* primary_alg= */ 0,
- blob, blob_size,
- policy.buffer, policy.size,
- use_pin ? binary_salt : NULL,
- use_pin ? sizeof(binary_salt) : 0,
- srk_buf, srk_buf_size,
+ &blob,
+ &IOVEC_MAKE(policy.buffer, policy.size),
+ use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
+ &srk,
flags,
&v);
if (r < 0)
if (n > INT_MAX)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Slot index out of range: %u", n);
- a = reallocarray(arg_wipe_slots, sizeof(int), arg_n_wipe_slots + 1);
+ a = reallocarray(arg_wipe_slots, arg_n_wipe_slots + 1, sizeof(int));
if (!a)
return log_oom();
assert(!key);
assert(key_size == 0);
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size);
+ r = unbase64mem(json_variant_string(w), &key, &key_size);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 encoded key.");
}
return 1;
}
- r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unbase64mem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'fido2-credential' field: %m");
return 1;
}
- r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unbase64mem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded salt: %m.");
return 1;
}
- r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unbase64mem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m.");
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
- size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
- _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {};
+ _cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
systemd_tpm2_plugin_params params = {
r = tpm2_parse_luks2_json(
v,
- NULL,
+ /* ret_keyslot= */ NULL,
&hash_pcr_mask,
&pcr_bank,
&pubkey,
- &pubkey_size,
&pubkey_pcr_mask,
&primary_alg,
&blob,
- &blob_size,
&policy_hash,
- &policy_hash_size,
&salt,
- &salt_size,
- &srk_buf,
- &srk_buf_size,
+ &srk,
&flags);
if (r < 0)
return log_debug_open_error(cd, r);
params.device,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
pubkey_pcr_mask,
params.signature_path,
pin_string,
params.pcrlock_path,
primary_alg,
- blob,
- blob_size,
- policy_hash,
- policy_hash_size,
- salt,
- salt_size,
- srk_buf,
- srk_buf_size,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
flags,
- &decrypted_key,
- &decrypted_key_size);
+ &decrypted_key);
if (r < 0)
return log_debug_open_error(cd, r);
/* Before using this key as passphrase we base64 encode it, for compat with homed */
- base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded);
if (base64_encoded_size < 0)
return log_debug_open_error(cd, base64_encoded_size);
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
+ _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags = 0;
&hash_pcr_mask,
&pcr_bank,
&pubkey,
- &pubkey_size,
&pubkey_pcr_mask,
&primary_alg,
&blob,
- &blob_size,
&policy_hash,
- &policy_hash_size,
&salt,
- &salt_size,
- &srk_buf,
- &srk_buf_size,
+ &srk,
&flags);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
if (!pubkey_pcrs_str)
return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m");
- r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str);
+ r = crypt_dump_buffer_to_hex_string(blob.iov_base, blob.iov_len, &blob_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
- r = crypt_dump_buffer_to_hex_string(pubkey, pubkey_size, &pubkey_str);
+ r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
- r = crypt_dump_buffer_to_hex_string(policy_hash, policy_hash_size, &policy_hash_str);
+ r = crypt_dump_buffer_to_hex_string(policy_hash.iov_base, policy_hash.iov_len, &policy_hash_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK));
- crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt));
- crypt_log(cd, "\ttpm2-srk: %s\n", true_false(srk_buf));
+ crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt)));
+ crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk)));
}
/*
return 1;
}
- r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unbase64mem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
return 1;
}
- r = unhexmem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ r = unhexmem(json_variant_string(w), NULL, NULL);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
if (!w)
return -EINVAL;
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size);
+ r = unbase64mem(json_variant_string(w), &cid, &cid_size);
if (r < 0)
return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-credentials' field: %m");
if (!w)
return -EINVAL;
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size);
+ r = unbase64mem(json_variant_string(w), &salt, &salt_size);
if (r < 0)
return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-salt' field: %m");
if (!w)
return -EINVAL;
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size);
+ r = unbase64mem(json_variant_string(w), &key, &key_size);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m.");
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pin,
const char *pcrlock_path,
uint16_t primary_alg,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *blob,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size) {
+ struct iovec *ret_decrypted_key) {
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ char *auto_device = NULL;
_cleanup_(erase_and_freep) char *b64_salted_pin = NULL;
int r;
- assert(salt || salt_size == 0);
+ assert(iovec_is_valid(salt));
assert(ret_decrypted_key);
- assert(ret_decrypted_key_size);
if (!device) {
r = tpm2_find_device_auto(&auto_device);
if ((flags & TPM2_FLAGS_USE_PIN) && !pin)
return -ENOANO;
- if (pin && salt_size > 0) {
+ if (pin && iovec_is_set(salt)) {
uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
CLEANUP_ERASE(salted_pin);
- r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt, salt_size, salted_pin);
+ r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt->iov_base, salt->iov_len, salted_pin);
if (r < 0)
return log_error_errno(r, "Failed to perform PBKDF2: %m");
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ pubkey,
pubkey_pcr_mask,
signature_json,
pin,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg,
- key_data, key_data_size,
- policy_hash, policy_hash_size,
- srk_buf, srk_buf_size,
- ret_decrypted_key, ret_decrypted_key_size);
+ blob,
+ policy_hash,
+ srk,
+ ret_decrypted_key);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
const char *device,
uint32_t pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
const char *pin,
uint16_t primary_alg,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size);
+ struct iovec *decrypted_key);
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size) {
+ struct iovec *ret_decrypted_key) {
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ void *loaded_blob = NULL;
_cleanup_free_ char *auto_device = NULL;
- size_t blob_size;
- const void *blob;
+ struct iovec blob;
int r;
- assert(salt || salt_size == 0);
+ assert(iovec_is_valid(salt));
if (!device) {
r = tpm2_find_device_auto(&auto_device);
device = auto_device;
}
- if (key_data) {
- blob = key_data;
- blob_size = key_data_size;
- } else {
+ if (iovec_is_set(key_data))
+ blob = *key_data;
+ else {
_cleanup_free_ char *bindname = NULL;
/* If we read the salt via AF_UNIX, make this client recognizable */
key_file_size == 0 ? SIZE_MAX : key_file_size,
READ_FULL_FILE_CONNECT_SOCKET,
bindname,
- (char**) &loaded_blob, &blob_size);
+ (char**) &loaded_blob, &blob.iov_len);
if (r < 0)
return r;
- blob = loaded_blob;
+ blob.iov_base = loaded_blob;
}
if (pubkey_pcr_mask != 0) {
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ pubkey,
pubkey_pcr_mask,
signature_json,
/* pin= */ NULL,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg,
- blob,
- blob_size,
+ &blob,
policy_hash,
- policy_hash_size,
- srk_buf,
- srk_buf_size,
- ret_decrypted_key,
- ret_decrypted_key_size);
+ srk,
+ ret_decrypted_key);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
if (r < 0)
return r;
- if (salt_size > 0) {
+ if (iovec_is_set(salt)) {
uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
CLEANUP_ERASE(salted_pin);
- r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt, salt_size, salted_pin);
+ r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt->iov_base, salt->iov_len, salted_pin);
if (r < 0)
return log_error_errno(r, "Failed to perform PBKDF2: %m");
r = tpm2_unseal(tpm2_context,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ pubkey,
pubkey_pcr_mask,
signature_json,
b64_salted_pin,
pcrlock_path ? &pcrlock_policy : NULL,
primary_alg,
- blob,
- blob_size,
+ &blob,
policy_hash,
- policy_hash_size,
- srk_buf,
- srk_buf_size,
- ret_decrypted_key,
- ret_decrypted_key_size);
+ srk,
+ ret_decrypted_key);
if (r < 0) {
log_error_errno(r, "Failed to unseal secret using TPM2: %m");
int start_token,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_buf_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
assert(cd);
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
+ _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags;
&keyslot,
&hash_pcr_mask,
&pcr_bank,
- &pubkey, &pubkey_size,
+ &pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob, &blob_size,
- &policy_hash, &policy_hash_size,
- &salt, &salt_size,
- &srk_buf, &srk_buf_size,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
&flags);
if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
continue;
*ret_hash_pcr_mask = hash_pcr_mask;
*ret_pcr_bank = pcr_bank;
- *ret_pubkey = TAKE_PTR(pubkey);
- *ret_pubkey_size = pubkey_size;
+ *ret_pubkey = TAKE_STRUCT(pubkey);
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
*ret_primary_alg = primary_alg;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
- *ret_policy_hash = TAKE_PTR(policy_hash);
- *ret_policy_hash_size = policy_hash_size;
- *ret_salt = TAKE_PTR(salt);
- *ret_salt_size = salt_size;
+ *ret_blob = TAKE_STRUCT(blob);
+ *ret_policy_hash = TAKE_STRUCT(policy_hash);
+ *ret_salt = TAKE_STRUCT(salt);
*ret_keyslot = keyslot;
*ret_token = token;
- *ret_srk_buf = TAKE_PTR(srk_buf);
- *ret_srk_buf_size = srk_buf_size;
+ *ret_srk = TAKE_STRUCT(srk);
*ret_flags = flags;
return 0;
}
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t salt_srk_buf_size,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size);
+ struct iovec *ret_decrypted_key);
int find_tpm2_auto_data(
struct crypt_device *cd,
int start_token,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token);
const char *device,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
const char *pcrlock_path,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
- const void *key_data,
- size_t key_data_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t salt_srk_buf_size,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
- void **ret_decrypted_key,
- size_t *ret_decrypted_key_size) {
+ struct iovec *ret_decrypted_key) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support not available.");
int start_token,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
_cleanup_free_ void *cid = NULL;
size_t cid_size;
- r = unbase64mem(val, SIZE_MAX, &cid, &cid_size);
+ r = unbase64mem(val, &cid, &cid_size);
if (r < 0)
return log_error_errno(r, "Failed to decode FIDO2 CID data: %m");
struct crypt_device *cd,
const char *name,
const char *key_file,
- const void *key_data,
- size_t key_data_size,
+ const struct iovec *key_data,
usec_t until,
uint32_t flags,
bool pass_volume_key) {
_cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
- _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_free_ char *friendly = NULL;
int keyslot = arg_key_slot, r;
- size_t decrypted_key_size;
assert(cd);
assert(name);
return log_oom();
for (;;) {
- if (key_file || key_data) {
+ if (key_file || iovec_is_set(key_data)) {
/* If key data is specified, use that */
r = acquire_tpm2_key(
arg_tpm2_device,
arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask,
UINT16_MAX,
- /* pubkey= */ NULL, /* pubkey_size= */ 0,
+ /* pubkey= */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature_path= */ NULL,
/* pcrlock_path= */ NULL,
/* primary_alg= */ 0,
key_file, arg_keyfile_size, arg_keyfile_offset,
- key_data, key_data_size,
- /* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
- /* salt= */ NULL, /* salt_size= */ 0,
- /* srk_buf= */ NULL, /* srk_buf_size= */ 0,
+ key_data,
+ /* policy_hash= */ NULL, /* we don't know the policy hash */
+ /* salt= */ NULL,
+ /* srk= */ NULL,
arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0,
until,
arg_headless,
arg_ask_password_flags,
- &decrypted_key, &decrypted_key_size);
+ &decrypted_key);
if (r >= 0)
break;
if (IN_SET(r, -EACCES, -ENOLCK))
}
if (r == -EOPNOTSUPP) { /* Plugin not available, let's process TPM2 stuff right here instead */
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL;
- size_t blob_size, policy_hash_size;
+ _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {};
bool found_some = false;
int token = 0; /* first token to look at */
* works. */
for (;;) {
- _cleanup_free_ void *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
- size_t pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
+ _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {};
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
token, /* search for the token with this index, or any later index than this */
&hash_pcr_mask,
&pcr_bank,
- &pubkey, &pubkey_size,
+ &pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob, &blob_size,
- &policy_hash, &policy_hash_size,
- &salt, &salt_size,
- &srk_buf, &srk_buf_size,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
&tpm2_flags,
&keyslot,
&token);
arg_tpm2_device,
hash_pcr_mask,
pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
pubkey_pcr_mask,
arg_tpm2_signature,
arg_tpm2_pcrlock,
primary_alg,
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
- blob, blob_size,
- policy_hash, policy_hash_size,
- salt, salt_size,
- srk_buf, srk_buf_size,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
tpm2_flags,
until,
arg_headless,
arg_ask_password_flags,
- &decrypted_key, &decrypted_key_size);
+ &decrypted_key);
if (IN_SET(r, -EACCES, -ENOLCK))
return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking.");
if (r != -EPERM)
log_debug("Got one or more potentially relevant udev events, rescanning for TPM2...");
}
- assert(decrypted_key);
if (pass_volume_key)
- r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key.iov_base, decrypted_key.iov_len, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
/* Before using this key as passphrase we base64 encode it, for compat with homed */
- base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded);
if (base64_encoded_size < 0)
return log_oom();
crypt_get_device_name(cd));
if (arg_tpm2_device || arg_tpm2_device_auto)
- return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key);
+ return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, &IOVEC_MAKE(key_data, key_data_size), until, flags, pass_volume_key);
if (arg_fido2_device || arg_fido2_device_auto)
return attach_luks_or_plain_or_bitlk_by_fido2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key);
if (arg_pkcs11_uri || arg_pkcs11_uri_auto)
#include "strv.h"
#include "terminal-util.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#include "vpick.h"
_cleanup_free_ void *p = NULL;
size_t l;
- r = unhexmem(optarg, strlen(optarg), &p, &l);
+ r = unhexmem(optarg, &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg);
if (l < sizeof(sd_id128_t))
void *p;
if ((value = startswith(optarg, "base64:"))) {
- r = unbase64mem(value, strlen(value), &p, &l);
+ r = unbase64mem(value, &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
} else {
struct stat st;
int r;
+ assert(path);
+
fd = open(path, O_PATH|O_CLOEXEC);
if (fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", path);
if (r < 0)
return r;
- r = strv_extend_front(&dirs, c);
+ r = strv_consume_prepend(&dirs, TAKE_PTR(c));
if (r < 0)
return r;
STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep);
STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static bool press_any_key(void) {
" --timezone=TIMEZONE Set timezone\n"
" --hostname=NAME Set hostname\n"
" --setup-machine-id Set a random machine ID\n"
- " --machine-ID=ID Set specified machine ID\n"
+ " --machine-id=ID Set specified machine ID\n"
" --root-password=PASSWORD Set root password from plaintext password\n"
" --root-password-file=FILE Set root password from file\n"
" --root-password-hashed=HASH Set root password from hashed password\n"
" --root-shell=SHELL Set root shell\n"
+ " --kernel-command-line=CMDLINE\n"
+ " Set kernel command line\n"
" --prompt-locale Prompt the user for locale settings\n"
" --prompt-keymap Prompt the user for keymap settings\n"
" --prompt-timezone Prompt the user for timezone\n"
log_info("Requesting %s/start/%s", target, mode);
/* Start this unit only if we can replace basic.target with it */
- r = bus_call_method(bus, bus_systemd_mgr, "StartUnitReplace", &error, NULL, "sss", "basic.target", target, mode);
+ r = bus_call_method(bus, bus_systemd_mgr, "StartUnitReplace", &error, NULL, "sss", SPECIAL_BASIC_TARGET, target, mode);
/* Don't print a warning if we aren't called during startup */
if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
if (!e)
return log_oom();
- r = json_dispatch(v, dispatch_table, JSON_LOG, e);
+ r = json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, e);
if (r < 0)
return r;
#include "rlimit-util.h"
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-record.h"
#include "user-record-password-quality.h"
#include "user-record-show.h"
#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-record-password-quality.h"
#include "user-record-sign.h"
#include "user-record-util.h"
/* Free the deactivation retry event source if we won't need it anymore. Specifically, we'll free the
* event source whenever the home directory is already deactivated (and we thus where successful) or
* if we start executing an operation that indicates that the home directory is going to be used or
- * operated on again. Also, if the home is referenced again stop the timer */
+ * operated on again. Also, if the home is referenced again stop the timer. */
- if (HOME_STATE_MAY_RETRY_DEACTIVATE(state) &&
- !h->ref_event_source_dont_suspend &&
- !h->ref_event_source_please_suspend)
+ if (HOME_STATE_MAY_RETRY_DEACTIVATE(state) && !home_is_referenced(h))
return;
h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
return;
/* If the home directory is being used now don't start the timer */
- if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
+ if (home_is_referenced(h))
return;
r = sd_event_add_time_relative(
if (h->ref_event_source_dont_suspend == s)
h->ref_event_source_dont_suspend = sd_event_source_disable_unref(h->ref_event_source_dont_suspend);
- if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
+ if (home_is_referenced(h))
return 0;
log_info("Got notification that all sessions of user %s ended, deactivating automatically.", h->user_name);
return 1;
}
+bool home_is_referenced(Home *h) {
+ assert(h);
+
+ return h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend;
+}
+
+bool home_shall_suspend(Home *h) {
+ assert(h);
+
+ /* Suspend if there's at least one client referencing this home directory that wants a suspend and none who does not. */
+ return h->ref_event_source_please_suspend && !h->ref_event_source_dont_suspend;
+}
+
static int home_dispatch_release(Home *h, Operation *o) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(o);
assert(o->type == OPERATION_RELEASE);
- if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
+ if (home_is_referenced(h))
/* If there's now a reference again, then let's abort the release attempt */
r = sd_bus_error_setf(&error, BUS_ERROR_HOME_BUSY, "Home %s is currently referenced.", h->user_name);
else {
assert(o);
assert(o->type == OPERATION_PIPE_EOF);
- if (h->ref_event_source_please_suspend || h->ref_event_source_dont_suspend)
+ if (home_is_referenced(h))
return 1; /* Hmm, there's a reference again, let's cancel this */
switch (home_get_state(h)) {
int home_lock(Home *h, sd_bus_error *error);
int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error);
+bool home_is_referenced(Home *h);
+bool home_shall_suspend(Home *h);
HomeState home_get_state(Home *h);
int home_get_disk_status(Home *h, uint64_t *ret_disk_size,uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, uint64_t *ret_disk_floor, statfs_f_type_t *ret_fstype, mode_t *ret_access_mode);
HASHMAP_FOREACH(h, m->homes_by_name) {
- /* Automatically suspend all homes that have at least one client referencing it that asked
- * for "please suspend", and no client that asked for "please do not suspend". */
- if (h->ref_event_source_dont_suspend ||
- !h->ref_event_source_please_suspend)
+ if (!home_shall_suspend(h))
continue;
if (!o) {
assert(m);
assert(!m->varlink_server);
- r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
+ r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE);
if (r < 0)
return log_error_errno(r, "Failed to allocate varlink server object: %m");
if (!e)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator: %m", xa);
- r = unbase64mem(value, e - value, &salt, &salt_size);
+ r = unbase64mem_full(value, e - value, /* secure = */ false, &salt, &salt_size);
if (r < 0)
return log_error_errno(r, "Failed to decode salt of %s: %m", xa);
- r = unbase64mem(e+1, n - (e - value) - 1, &encrypted, &encrypted_size);
+
+ r = unbase64mem_full(e + 1, n - (e - value) - 1, /* secure = */ false, &encrypted, &encrypted_size);
if (r < 0)
return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
#include "user-record.h"
#include "user-util.h"
+typedef enum AcquireHomeFlags {
+ ACQUIRE_MUST_AUTHENTICATE = 1 << 0,
+ ACQUIRE_PLEASE_SUSPEND = 1 << 1,
+} AcquireHomeFlags;
+
static int parse_argv(
pam_handle_t *handle,
int argc, const char **argv,
- bool *please_suspend,
+ AcquireHomeFlags *flags,
bool *debug) {
assert(argc >= 0);
k = parse_boolean(v);
if (k < 0)
pam_syslog(handle, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v);
- else if (please_suspend)
- *please_suspend = k;
+ else if (flags)
+ SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k);
} else if (streq(argv[i], "debug")) {
if (debug)
static int parse_env(
pam_handle_t *handle,
- bool *please_suspend) {
+ AcquireHomeFlags *flags) {
const char *v;
int r;
r = parse_boolean(v);
if (r < 0)
pam_syslog(handle, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v);
- else if (please_suspend)
- *please_suspend = r;
+ else if (flags)
+ SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r);
return 0;
}
/* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */
if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL,
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL,
_("Home of user %s is currently absent, please plug in the necessary storage device or backing file system."), user_name);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_PERM_DENIED,
"Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
} else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES,
"Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
/* This didn't work? Ask for an (additional?) password */
if (strv_isempty(secret->password))
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: "));
else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
/* Hmm, homed asks for recovery key (because no regular password is defined maybe)? Provide it. */
if (strv_isempty(secret->password))
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: "));
else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
assert(secret);
if (strv_isempty(secret->password)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
} else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
return PAM_AUTHTOK_ERR;
}
-
r = user_record_set_password(secret, STRV_MAKE(newp), true);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store password: %m");
assert(secret);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name);
r = user_record_set_pkcs11_protected_authentication_path_permitted(secret, true);
if (r < 0)
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name);
r = user_record_set_fido2_user_presence_permitted(secret, true);
if (r < 0)
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name);
r = user_record_set_fido2_user_verification_permitted(secret, true);
if (r < 0)
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"));
return PAM_SERVICE_ERR;
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
static int acquire_home(
pam_handle_t *handle,
- bool please_authenticate,
- bool please_suspend,
+ AcquireHomeFlags flags,
bool debug,
PamBusData **bus_data) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL;
- bool do_auth = please_authenticate, home_not_active = false, home_locked = false;
+ bool do_auth = FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE), home_not_active = false, home_locked = false;
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
_cleanup_close_ int acquired_fd = -EBADF;
_cleanup_free_ char *fd_field = NULL;
return pam_bus_log_create_error(handle, r);
}
- r = sd_bus_message_append(m, "b", please_suspend);
+ r = sd_bus_message_append(m, "b", FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND));
if (r < 0)
return pam_bus_log_create_error(handle, r);
* failure. */
if (home_not_active)
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name);
if (home_locked)
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name);
- if (please_authenticate || debug)
- pam_syslog(handle, please_authenticate ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
+ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) || debug)
+ pam_syslog(handle, FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR;
}
if (r != PAM_SUCCESS)
return r;
}
-
} else {
int fd;
}
if (++n_attempts >= 5) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL,
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL,
_("Too many unsuccessful login attempts for user %s, refusing."), ur->user_name);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES,
"Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r));
}
/* Later PAM modules may need the auth token, but only during pam_authenticate. */
- if (please_authenticate && !strv_isempty(secret->password)) {
+ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) {
r = pam_set_item(handle, PAM_AUTHTOK, *secret->password);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@");
_public_ PAM_EXTERN int pam_sm_authenticate(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
- bool debug = false, suspend_please = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
- if (parse_env(handle, &suspend_please) < 0)
+ if (parse_env(handle, &flags) < 0)
return PAM_AUTH_ERR;
if (parse_argv(handle,
argc, argv,
- &suspend_please,
+ &flags,
&debug) < 0)
return PAM_AUTH_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed authenticating");
- return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug, NULL);
+ return acquire_home(handle, ACQUIRE_MUST_AUTHENTICATE|flags, debug, /* bus_data= */ NULL);
}
-_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int sm_flags, int argc, const char **argv) {
return PAM_SUCCESS;
}
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
/* Let's release the D-Bus connection once this function exits, after all the session might live
* quite a long time, and we are not going to process the bus connection in that time, so let's
* better close before the daemon kicks us off because we are not processing anything. */
_cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
- bool debug = false, suspend_please = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
int r;
- if (parse_env(handle, &suspend_please) < 0)
+ if (parse_env(handle, &flags) < 0)
return PAM_SESSION_ERR;
if (parse_argv(handle,
argc, argv,
- &suspend_please,
+ &flags,
&debug) < 0)
return PAM_SESSION_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed session start");
- r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug, &d);
+ r = acquire_home(handle, flags, debug, &d);
if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
return PAM_SUCCESS;
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@");
- r = pam_putenv(handle, suspend_please ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0");
+ r = pam_putenv(handle, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0");
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@");
_public_ PAM_EXTERN int pam_sm_close_session(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_public_ PAM_EXTERN int pam_sm_acct_mgmt(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc,
const char **argv) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
- bool debug = false, please_suspend = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
usec_t t;
int r;
- if (parse_env(handle, &please_suspend) < 0)
+ if (parse_env(handle, &flags) < 0)
return PAM_AUTH_ERR;
if (parse_argv(handle,
argc, argv,
- &please_suspend,
+ &flags,
&debug) < 0)
return PAM_AUTH_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed account management");
- r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug, NULL);
+ r = acquire_home(handle, flags, debug, NULL);
if (r != PAM_SUCCESS)
return r;
break;
case -ENOLCK:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access."));
return PAM_ACCT_EXPIRED;
case -EL2HLT:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access."));
return PAM_ACCT_EXPIRED;
case -EL3HLT:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access."));
return PAM_ACCT_EXPIRED;
default:
if (r < 0) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
return PAM_ACCT_EXPIRED;
}
usec_t n = now(CLOCK_REALTIME);
if (t > n) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."),
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."),
FORMAT_TIMESPAN(t - n, USEC_PER_SEC));
return PAM_MAXTRIES;
switch (r) {
case -EKEYREVOKED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password change required."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password change required."));
return PAM_NEW_AUTHTOK_REQD;
case -EOWNERDEAD:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required."));
return PAM_NEW_AUTHTOK_REQD;
/* Strictly speaking this is only about password expiration, and we might want to allow
* authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */
case -EKEYREJECTED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login."));
return PAM_AUTHTOK_EXPIRED;
case -EKEYEXPIRED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change."));
break;
case -ESTALE:
default:
if (r < 0) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
return PAM_AUTHTOK_EXPIRED;
}
_public_ PAM_EXTERN int pam_sm_chauthtok(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc,
const char **argv) {
}
/* Now everything is cached and checked, let's exit from the preliminary check */
- if (FLAGS_SET(flags, PAM_PRELIM_CHECK))
+ if (FLAGS_SET(sm_flags, PAM_PRELIM_CHECK))
return PAM_SUCCESS;
old_secret = user_record_new();
#include "main-func.h"
#include "parse-argument.h"
#include "pretty-print.h"
+#include "socket-util.h"
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
#include "verbs.h"
usec_t firmware_date;
sd_id128_t machine_id;
sd_id128_t boot_id;
+ uint32_t vsock_cid;
} StatusInfo;
static const char* chassis_string_to_glyph(const char *chassis) {
return table_log_add_error(r);
}
+ if (i->vsock_cid != VMADDR_CID_ANY) {
+ r = table_add_many(table,
+ TABLE_FIELD, "AF_VSOCK CID",
+ TABLE_UINT32, i->vsock_cid);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
if (!isempty(i->virtualization)) {
r = table_add_many(table,
TABLE_FIELD, "Virtualization",
}
static int show_all_names(sd_bus *bus) {
- StatusInfo info = {};
+ StatusInfo info = {
+ .vsock_cid = VMADDR_CID_ANY,
+ };
static const struct bus_properties_map hostname_map[] = {
{ "Hostname", "s", NULL, offsetof(StatusInfo, hostname) },
{ "FirmwareDate", "t", NULL, offsetof(StatusInfo, firmware_date) },
{ "MachineID", "ay", bus_map_id128, offsetof(StatusInfo, machine_id) },
{ "BootID", "ay", bus_map_id128, offsetof(StatusInfo, boot_id) },
+ { "VSockCID", "u", NULL, offsetof(StatusInfo, vsock_cid) },
{}
}, manager_map[] = {
{ "Virtualization", "s", NULL, offsetof(StatusInfo, virtualization) },
#include <sys/types.h>
#include <unistd.h>
+#include "sd-device.h"
+
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "bus-get-properties.h"
#include "bus-log-control-api.h"
#include "bus-polkit.h"
#include "constants.h"
+#include "daemon-util.h"
#include "env-file-label.h"
#include "env-file.h"
#include "env-util.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
-#include "sd-device.h"
#include "selinux-util.h"
#include "service-util.h"
#include "signal-util.h"
+#include "socket-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
#include "user-util.h"
+#include "varlink-io.systemd.Hostname.h"
#include "virt.h"
#define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
struct stat etc_os_release_stat;
struct stat etc_machine_info_stat;
+ sd_event *event;
+ sd_bus *bus;
+ VarlinkServer *varlink_server;
Hashmap *polkit_registry;
} Context;
context_reset(c, UINT64_MAX);
hashmap_free(c->polkit_registry);
+ sd_event_unref(c->event);
+ sd_bus_flush_close_unref(c->bus);
+ varlink_server_unref(c->varlink_server);
}
static void context_read_etc_hostname(Context *c) {
return bus_property_get_id128(bus, path, interface, property, reply, &id, error);
}
+static int property_get_vsock_cid(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ unsigned local_cid = VMADDR_CID_ANY;
+
+ (void) vsock_get_local_cid(&local_cid);
+
+ return sd_bus_message_append(reply, "u", (uint32_t) local_cid);
+}
+
static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) {
Context *c = ASSERT_PTR(userdata);
const char *name;
return sd_bus_send(NULL, reply, NULL);
}
-static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) {
- _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *text = NULL,
+static int build_describe_response(Context *c, bool privileged, JsonVariant **ret) {
+ _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL,
*chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL,
*firmware_vendor = NULL;
usec_t firmware_date = USEC_INFINITY, eol = USEC_INFINITY;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
sd_id128_t machine_id, boot_id, product_uuid = SD_ID128_NULL;
- Context *c = ASSERT_PTR(userdata);
- bool privileged;
+ unsigned local_cid = VMADDR_CID_ANY;
struct utsname u;
int r;
- assert(m);
-
- r = bus_verify_polkit_async(
- m,
- "org.freedesktop.hostname1.get-description",
- /* details= */ NULL,
- &c->polkit_registry,
- error);
- if (r == 0)
- return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
-
- /* We ignore all authentication errors here, since most data is unprivileged, the one exception being
- * the product ID which we'll check explicitly. */
- privileged = r > 0;
+ assert(c);
+ assert(ret);
context_read_etc_hostname(c);
context_read_machine_info(c);
if (r < 0)
return log_error_errno(r, "Failed to get boot ID: %m");
+ (void) vsock_get_local_cid(&local_cid);
+
r = json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("Hostname", JSON_BUILD_STRING(hn)),
JSON_BUILD_PAIR("StaticHostname", JSON_BUILD_STRING(c->data[PROP_STATIC_HOSTNAME])),
JSON_BUILD_PAIR_ID128("MachineID", machine_id),
JSON_BUILD_PAIR_ID128("BootID", boot_id),
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_ID128(product_uuid)),
- JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL)));
-
+ JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL),
+ JSON_BUILD_PAIR_CONDITION(local_cid != VMADDR_CID_ANY, "VSockCID", JSON_BUILD_UNSIGNED(local_cid)),
+ JSON_BUILD_PAIR_CONDITION(local_cid == VMADDR_CID_ANY, "VSockCID", JSON_BUILD_NULL)));
if (r < 0)
return log_error_errno(r, "Failed to build JSON data: %m");
+ *ret = TAKE_PTR(v);
+ return 0;
+}
+
+static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ Context *c = ASSERT_PTR(userdata);
+ _cleanup_free_ char *text = NULL;
+ bool privileged;
+ int r;
+
+ assert(m);
+
+ r = bus_verify_polkit_async(
+ m,
+ "org.freedesktop.hostname1.get-description",
+ /* details= */ NULL,
+ &c->polkit_registry,
+ error);
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ /* We ignore all authentication errors here, since most data is unprivileged, the one exception being
+ * the product ID which we'll check explicitly. */
+ privileged = r > 0;
+
+ r = build_describe_response(c, privileged, &v);
+ if (r < 0)
+ return r;
+
r = json_variant_format(v, 0, &text);
if (r < 0)
return log_error_errno(r, "Failed to format JSON data: %m");
SD_BUS_PROPERTY("FirmwareDate", "t", property_get_firmware_date, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MachineID", "ay", property_get_machine_id, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BootID", "ay", property_get_boot_id, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("VSockCID", "u", property_get_vsock_cid, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD_WITH_ARGS("SetHostname",
SD_BUS_ARGS("s", hostname, "b", interactive),
.vtables = BUS_VTABLES(hostname_vtable),
};
-static int connect_bus(Context *c, sd_event *event, sd_bus **ret) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+static int connect_bus(Context *c) {
int r;
assert(c);
- assert(event);
- assert(ret);
+ assert(c->event);
+ assert(!c->bus);
- r = sd_bus_default_system(&bus);
+ r = sd_bus_default_system(&c->bus);
if (r < 0)
return log_error_errno(r, "Failed to get system bus connection: %m");
- r = bus_add_implementation(bus, &manager_object, c);
+ r = bus_add_implementation(c->bus, &manager_object, c);
if (r < 0)
return r;
- r = bus_log_control_api_register(bus);
+ r = bus_log_control_api_register(c->bus);
if (r < 0)
return r;
- r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL);
+ r = sd_bus_request_name_async(c->bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to request name: %m");
- r = sd_bus_attach_event(bus, event, 0);
+ r = sd_bus_attach_event(c->bus, c->event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
- *ret = TAKE_PTR(bus);
+ return 0;
+}
+
+static int vl_method_describe(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+ static const JsonDispatch dispatch_table[] = {
+ VARLINK_DISPATCH_POLKIT_FIELD,
+ {}
+ };
+
+ Context *c = ASSERT_PTR(userdata);
+ bool privileged;
+ int r;
+
+ assert(link);
+ assert(parameters);
+
+ r = varlink_dispatch(link, parameters, dispatch_table, /* userdata= */ NULL);
+ if (r != 0)
+ return r;
+
+ r = varlink_verify_polkit_async(
+ link,
+ c->bus,
+ "org.freedesktop.hostname1.get-hardware-serial",
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ &c->polkit_registry);
+ if (r == 0)
+ return 0; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ /* We ignore all authentication errors here, since most data is unprivileged, the one exception being
+ * the product ID which we'll check explicitly. */
+ privileged = r > 0;
+
+ if (json_variant_elements(parameters) > 0)
+ return varlink_error_invalid_parameter(link, parameters);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ r = build_describe_response(c, privileged, &v);
+ if (r < 0)
+ return r;
+
+ return varlink_reply(link, v);
+}
+
+static int connect_varlink(Context *c) {
+ int r;
+
+ assert(c);
+ assert(c->event);
+ assert(!c->varlink_server);
+
+ r = varlink_server_new(&c->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+ varlink_server_set_userdata(c->varlink_server, c);
+
+ r = varlink_server_add_interface(c->varlink_server, &vl_interface_io_systemd_Hostname);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add Hostname interface to varlink server: %m");
+
+ r = varlink_server_bind_method_many(
+ c->varlink_server,
+ "io.systemd.Hostname.Describe", vl_method_describe);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind Varlink method calls: %m");
+
+ r = varlink_server_attach_event(c->varlink_server, c->event, SD_EVENT_PRIORITY_NORMAL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach Varlink server to event loop: %m");
+
+ r = varlink_server_listen_auto(c->varlink_server);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m");
+ if (r == 0) {
+ r = varlink_server_listen_address(c->varlink_server, "/run/systemd/io.systemd.Hostname", 0666);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind to Varlink socket: %m");
+ }
+
return 0;
}
_cleanup_(context_destroy) Context context = {
.hostname_source = _HOSTNAME_INVALID, /* appropriate value will be set later */
};
- _cleanup_(sd_event_unrefp) sd_event *event = NULL;
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
log_setup();
if (r < 0)
return r;
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
-
- r = sd_event_default(&event);
+ r = sd_event_default(&context.event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
- (void) sd_event_set_watchdog(event, true);
+ (void) sd_event_set_watchdog(context.event, true);
- r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
+ r = sd_event_set_signal_exit(context.event, true);
if (r < 0)
- return log_error_errno(r, "Failed to install SIGINT handler: %m");
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
- r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
+ r = connect_bus(&context);
if (r < 0)
- return log_error_errno(r, "Failed to install SIGTERM handler: %m");
+ return r;
- r = connect_bus(&context, event, &bus);
+ r = connect_varlink(&context);
if (r < 0)
return r;
- r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
+ r = sd_notify(false, NOTIFY_READY);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
+
+ r = bus_event_loop_with_idle(
+ context.event,
+ context.bus,
+ "org.freedesktop.hostname1",
+ DEFAULT_EXIT_USEC,
+ /* check_idle= */ NULL,
+ /* userdata= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
#include "alloc-util.h"
#include "build.h"
+#include "format-table.h"
#include "gpt.h"
#include "id128-print.h"
#include "main-func.h"
+#include "parse-argument.h"
#include "pretty-print.h"
#include "strv.h"
-#include "format-table.h"
#include "terminal-util.h"
#include "verbs.h"
static Id128PrettyPrintMode arg_mode = ID128_PRINT_ID128;
static sd_id128_t arg_app = {};
static bool arg_value = false;
+static PagerFlags arg_pager_flags = 0;
+static bool arg_legend = true;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static int verb_new(int argc, char **argv, void *userdata) {
return id128_print_new(arg_mode);
}
if (table) {
- r = table_print(table, NULL);
+ r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
if (r < 0)
- return table_log_print_error(r);
+ return r;
}
return 0;
" help Show this help\n"
"\nOptions:\n"
" -h --help Show this help\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --no-legend Do not show the headers and footers\n"
+ " --json=FORMAT Output inspection data in JSON (takes one of\n"
+ " pretty, short, off)\n"
+ " -j Equivalent to --json=pretty (on TTY) or\n"
+ " --json=short (otherwise)\n"
" -p --pretty Generate samples of program code\n"
" -P --value Only print the value\n"
" -a --app-specific=ID Generate app-specific IDs\n"
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_NO_LEGEND,
+ ARG_JSON,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "json", required_argument, NULL, ARG_JSON },
{ "pretty", no_argument, NULL, 'p' },
{ "value", no_argument, NULL, 'P' },
{ "app-specific", required_argument, NULL, 'a' },
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hpa:uP", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hpa:uPj", options, NULL)) >= 0)
switch (c) {
case 'h':
case ARG_VERSION:
return version();
+ case ARG_NO_PAGER:
+ arg_pager_flags |= PAGER_DISABLE;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ break;
+
+ case 'j':
+ arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
+ break;
+
+ case ARG_JSON:
+ r = parse_json_argument(optarg, &arg_json_format_flags);
+ if (r <= 0)
+ return r;
+
+ break;
case 'p':
arg_mode = ID128_PRINT_PRETTY;
arg_value = false;
#include "bus-polkit.h"
#include "common-signal.h"
#include "constants.h"
+#include "daemon-util.h"
#include "env-util.h"
#include "fd-util.h"
#include "float.h"
return hashmap_isempty(m->transfers);
}
-static int manager_run(Manager *m) {
- assert(m);
-
- return bus_event_loop_with_idle(
- m->event,
- m->bus,
- "org.freedesktop.import1",
- DEFAULT_EXIT_USEC,
- manager_check_idle,
- m);
-}
-
static void manager_parse_env(Manager *m) {
int r;
if (r < 0)
return r;
- r = manager_run(m);
+ r = sd_notify(false, NOTIFY_READY);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
+
+ r = bus_event_loop_with_idle(
+ m->event,
+ m->bus,
+ "org.freedesktop.import1",
+ DEFAULT_EXIT_USEC,
+ manager_check_idle,
+ m);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
/* If this is not a valid verification mode, maybe it's a literally specified
* SHA256 hash? We can handle that too... */
- r = unhexmem(optarg, (size_t) -1, &h, &n);
+ r = unhexmem(optarg, &h, &n);
if (r < 0 || n == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid verification setting: %s", optarg);
if (r < 0)
return log_error_errno(r, "socket_address_print(): %m");
- r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b);
+ r = socknameinfo_pretty(&addr->sockaddr.sa, addr->size, &b);
if (r < 0)
return log_error_errno(r, "Resolving hostname failed: %m");
#include "alloc-util.h"
#include "build.h"
+#include "env-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "main-func.h"
if (argc <= optind)
(void) execl("/bin/cat", "/bin/cat", NULL);
else {
- _cleanup_free_ char *s = NULL;
struct stat st;
if (fstat(STDERR_FILENO, &st) < 0)
"Failed to fstat(%s): %m",
FORMAT_PROC_FD_PATH(STDERR_FILENO));
- if (asprintf(&s, DEV_FMT ":" INO_FMT, (dev_t)st.st_dev, st.st_ino) < 0)
- return log_oom();
-
- if (setenv("JOURNAL_STREAM", s, /* overwrite = */ true) < 0)
- return log_error_errno(errno, "Failed to set environment variable JOURNAL_STREAM: %m");
+ r = setenvf("JOURNAL_STREAM", /* overwrite = */ true, DEV_FMT ":" INO_FMT, (dev_t) st.st_dev, st.st_ino);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set environment variable JOURNAL_STREAM: %m");
(void) execvp(argv[optind], argv + optind);
}
#include "chattr-util.h"
#include "constants.h"
#include "devnum-util.h"
+#include "dirent-util.h"
#include "dissect-image.h"
#include "fd-util.h"
#include "fileio.h"
static usec_t arg_since = 0, arg_until = 0;
static bool arg_since_set = false, arg_until_set = false;
static char **arg_syslog_identifier = NULL;
+static char **arg_exclude_identifier = NULL;
static char **arg_system_units = NULL;
static char **arg_user_units = NULL;
static const char *arg_field = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep);
STATIC_DESTRUCTOR_REGISTER(arg_verify_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_syslog_identifier, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_exclude_identifier, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_system_units, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_user_units, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
ACTION_ROTATE_AND_VACUUM,
ACTION_LIST_FIELDS,
ACTION_LIST_FIELD_NAMES,
+ ACTION_LIST_NAMESPACES,
} arg_action = ACTION_SHOW;
static int add_matches_for_device(sd_journal *j, const char *devpath) {
" -u --unit=UNIT Show logs from the specified unit\n"
" --user-unit=UNIT Show logs from the specified user unit\n"
" -t --identifier=STRING Show entries with the specified syslog identifier\n"
+ " -T --exclude-identifier=STRING\n"
+ " Hide entries with the specified syslog identifier\n"
" -p --priority=RANGE Show entries with the specified priority\n"
" --facility=FACILITY... Show entries with the specified facilities\n"
" -g --grep=PATTERN Show entries with MESSAGE matching PATTERN\n"
ARG_NO_HOSTNAME,
ARG_OUTPUT_FIELDS,
ARG_NAMESPACE,
+ ARG_LIST_NAMESPACES,
};
static const struct option options[] = {
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "header", no_argument, NULL, ARG_HEADER },
{ "identifier", required_argument, NULL, 't' },
+ { "exclude-identifier", required_argument, NULL, 'T' },
{ "priority", required_argument, NULL, 'p' },
{ "facility", required_argument, NULL, ARG_FACILITY },
{ "grep", required_argument, NULL, 'g' },
{ "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME },
{ "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS },
{ "namespace", required_argument, NULL, ARG_NAMESPACE },
+ { "list-namespaces", no_argument, NULL, ARG_LIST_NAMESPACES },
{}
};
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:u:NF:xrM:i:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:NF:xrM:i:", options, NULL)) >= 0)
switch (c) {
break;
+ case ARG_LIST_NAMESPACES:
+ arg_action = ACTION_LIST_NAMESPACES;
+ break;
+
case 'D':
arg_directory = optarg;
break;
return log_oom();
break;
+ case 'T':
+ r = strv_extend(&arg_exclude_identifier, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
case 'u':
r = strv_extend(&arg_system_units, optarg);
if (r < 0)
return 0;
}
+static int list_namespaces(const char *root) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ sd_id128_t machine;
+ char machine_id[SD_ID128_STRING_MAX];
+ int r;
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get machine ID: %m");
+
+ sd_id128_to_string(machine, machine_id);
+
+ table = table_new("namespace");
+ if (!table)
+ return log_oom();
+
+ (void) table_set_sort(table, (size_t) 0);
+
+ FOREACH_STRING(dir, "/var/log/journal", "/run/log/journal") {
+ _cleanup_free_ char *path = NULL;
+ _cleanup_closedir_ DIR *dirp = NULL;
+
+ path = path_join(root, dir);
+ if (!path)
+ return log_oom();
+
+ dirp = opendir(path);
+ if (!dirp) {
+ log_debug_errno(errno, "Failed to open directory %s, ignoring: %m", path);
+ continue;
+ }
+
+ FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", path)) {
+ char *dot;
+
+ if (!startswith(de->d_name, machine_id))
+ continue;
+
+ dot = strchr(de->d_name, '.');
+ if (!dot)
+ continue;
+
+ if (!log_namespace_name_valid(dot + 1))
+ continue;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, dot + 1);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+ }
+
+ r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet);
+ if (r < 0)
+ return table_log_print_error(r);
+
+ return 0;
+}
+
static int list_boots(sd_journal *j) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ BootId *boots = NULL;
return 0;
}
+static int add_exclude_identifier(sd_journal *j) {
+ _cleanup_set_free_ Set *excludes = NULL;
+ int r;
+
+ assert(j);
+
+ r = set_put_strdupv(&excludes, arg_exclude_identifier);
+ if (r < 0)
+ return r;
+
+ return set_free_and_replace(j->exclude_syslog_identifiers, excludes);
+}
+
#if HAVE_GCRYPT
static int format_journal_url(
const void *seed,
case ACTION_ROTATE:
return rotate();
+ case ACTION_LIST_NAMESPACES:
+ return list_namespaces(arg_root);
+
case ACTION_SHOW:
case ACTION_PRINT_HEADER:
case ACTION_VERIFY:
if (r < 0)
return log_error_errno(r, "Failed to add filter for syslog identifiers: %m");
+ r = add_exclude_identifier(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add exclude filter for syslog identifiers: %m");
+
r = add_priorities(j);
if (r < 0)
return r;
#include "string-table.h"
#include "string-util.h"
#include "syslog-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#include "varlink-io.systemd.Journal.h"
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
typedef enum Action {
assert(source);
assert(ret);
+ assert(source->rfd >= 0 || source->rfd == AT_FDCWD);
_cleanup_(context_done) Context copy = (Context) {
- .rfd = -EBADF,
+ .rfd = AT_FDCWD,
.action = source->action,
.machine_id = source->machine_id,
.machine_id_is_random = source->machine_id_is_random,
.entry_token_type = source->entry_token_type,
};
- copy.rfd = fd_reopen(source->rfd, O_CLOEXEC|O_DIRECTORY|O_PATH);
- if (copy.rfd < 0)
- return copy.rfd;
+ if (source->rfd >= 0) {
+ copy.rfd = fd_reopen(source->rfd, O_CLOEXEC|O_DIRECTORY|O_PATH);
+ if (copy.rfd < 0)
+ return copy.rfd;
+ }
r = strdup_or_null(source->layout_other, ©.layout_other);
if (r < 0)
r = strdup_or_null(source->kernel, ©.kernel);
if (r < 0)
return r;
- copy.initrds = strv_copy(source->initrds);
- if (!copy.initrds)
- return -ENOMEM;
+ r = strv_copy_unless_empty(source->initrds, ©.initrds);
+ if (r < 0)
+ return r;
r = strdup_or_null(source->initrd_generator, ©.initrd_generator);
if (r < 0)
return r;
r = strdup_or_null(source->staging_area, ©.staging_area);
if (r < 0)
return r;
- copy.plugins = strv_copy(source->plugins);
- if (!copy.plugins)
- return -ENOMEM;
- copy.argv = strv_copy(source->argv);
- if (!copy.argv)
- return -ENOMEM;
- copy.envp = strv_copy(source->envp);
- if (!copy.envp)
- return -ENOMEM;
+ r = strv_copy_unless_empty(source->plugins, ©.plugins);
+ if (r < 0)
+ return r;
+ r = strv_copy_unless_empty(source->argv, ©.argv);
+ if (r < 0)
+ return r;
+ r = strv_copy_unless_empty(source->envp, ©.envp);
+ if (r < 0)
+ return r;
*ret = copy;
copy = CONTEXT_NULL;
return 0;
}
-static int context_ensure_conf_root(Context *c) {
- int r;
-
- assert(c);
-
- if (c->conf_root)
- return 0;
-
- r = chaseat(c->rfd, "/etc/kernel", CHASE_AT_RESOLVE_IN_ROOT, &c->conf_root, /* ret_fd = */ NULL);
- if (r < 0)
- log_debug_errno(r, "Failed to chase /etc/kernel, ignoring: %m");
-
- return 0;
-}
-
static int context_load_install_conf_one(Context *c, const char *path) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char
return r;
}
- STRV_FOREACH(p, STRV_MAKE("/etc/kernel", "/usr/lib/kernel")) {
- r = context_load_install_conf_one(c, *p);
+ FOREACH_STRING(p, "/etc/kernel", "/usr/lib/kernel") {
+ r = context_load_install_conf_one(c, p);
if (r != 0)
return r;
}
if (r < 0)
return r;
- r = context_ensure_conf_root(c);
- if (r < 0)
- return r;
-
r = context_load_install_conf(c);
if (r < 0)
return r;
return log_oom();
} else if (c->action == ACTION_INSPECT) {
- r = strv_extend(&a, c->kernel ?: "[KERNEL_IMAGE]");
- if (r < 0)
- return log_oom();
-
- r = strv_extend(&a, "[INITRD...]");
+ r = strv_extend_many(
+ &a,
+ c->kernel ?: "[KERNEL_IMAGE]",
+ "[INITRD...]");
if (r < 0)
return log_oom();
}
assert(argv);
if (arg_root)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root=.");
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root= or --image=.");
if (bypass())
return 0;
assert(argv);
+ if (arg_root)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add-all' does not support --root= or --image=.");
+
if (bypass())
return 0;
c->action = ACTION_ADD;
- fd = open("/usr/lib/modules", O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+ fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL);
if (fd < 0)
- return log_error_errno(fd, "Failed to open /usr/lib/modules/: %m");
+ return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
_cleanup_free_ DirectoryEntries *de = NULL;
r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
FOREACH_ARRAY(d, de->entries, de->n_entries) {
-
- _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name);
- if (!j)
- return log_oom();
-
r = dirent_ensure_type(fd, *d);
if (r < 0) {
if (r != -ENOENT) /* don't log if just gone by now */
- log_debug_errno(r, "Failed to check if '%s' is a directory, ignoring: %m", j);
+ log_debug_errno(r, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root), (*d)->d_name);
continue;
}
if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
if (errno != ENOENT)
- log_debug_errno(errno, "Failed to check if '/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", (*d)->d_name);
+ log_debug_errno(errno, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root), (*d)->d_name);
log_notice("Not adding version '%s', because kernel image not found.", (*d)->d_name);
continue;
if (r < 0)
return log_error_errno(r, "Failed to copy execution context: %m");
+ /* do_add() will look up the path in the correct root directory so we don't need to prefix it
+ * with arg_root here. */
_cleanup_free_ char *full = path_join("/usr/lib/modules/", fn);
if (!full)
return log_oom();
}
if (n > 0)
- log_info("Installed %zu kernels.", n);
+ log_debug("Installed %zu kernel(s).", n);
else if (ret == 0)
ret = log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No kernels to install found.");
assert(argv);
if (arg_root)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root=.");
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root= or --image=.");
if (argc > 2)
log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
}
static int verb_list(int argc, char *argv[], void *userdata) {
+ Context *c = ASSERT_PTR(userdata);
_cleanup_close_ int fd = -EBADF;
int r;
- fd = open("/usr/lib/modules", O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+ fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL);
if (fd < 0)
- return log_error_errno(fd, "Failed to open /usr/lib/modules/: %m");
+ return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root));
_cleanup_free_ DirectoryEntries *de = NULL;
r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
FOREACH_ARRAY(d, de->entries, de->n_entries) {
-
_cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name);
if (!j)
return log_oom();
r = dirent_ensure_type(fd, *d);
if (r < 0) {
if (r != -ENOENT) /* don't log if just gone by now */
- log_debug_errno(r, "Failed to check if '%s' is a directory, ignoring: %m", j);
+ log_debug_errno(r, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root), j);
continue;
}
bool exists;
if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
if (errno != ENOENT)
- log_debug_errno(errno, "Failed to check if '/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", (*d)->d_name);
+ log_debug_errno(errno, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root), (*d)->d_name);
exists = false;
} else
#include "sd-dhcp-client-id.h"
#include "dhcp-duid-internal.h"
+#include "json.h"
#include "macro.h"
#include "siphash24.h"
#include "sparse-endian.h"
void client_id_hash_func(const sd_dhcp_client_id *client_id, struct siphash *state);
int client_id_compare_func(const sd_dhcp_client_id *a, const sd_dhcp_client_id *b);
+
+int json_dispatch_client_id(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
#include "alloc-util.h"
#include "dhcp-client-id-internal.h"
+#include "iovec-util.h"
#include "unaligned.h"
#include "utf8.h"
assert_return(client_id, -EINVAL);
assert_return(data, -EINVAL);
- assert_return(client_id_data_size_is_valid(data_size), -EINVAL);
+
+ if (!client_id_data_size_is_valid(data_size))
+ return -EINVAL;
client_id->id.type = type;
memcpy(client_id->id.data, data, data_size);
assert_return(client_id, -EINVAL);
assert_return(data, -EINVAL);
- assert_return(client_id_size_is_valid(data_size), -EINVAL);
/* Unlike sd_dhcp_client_id_set(), this takes whole client ID including its type. */
+ if (!client_id_size_is_valid(data_size))
+ return -EINVAL;
+
memcpy(client_id->raw, data, data_size);
client_id->size = data_size;
r = asprintf(&t, "IAID:0x%x/DUID", iaid);
}
break;
+ default:
+ assert_not_reached();
}
if (r < 0)
return -ENOMEM;
int r;
assert_return(data, -EINVAL);
- assert_return(client_id_size_is_valid(data_size), -EINVAL);
assert_return(ret, -EINVAL);
r = sd_dhcp_client_id_set_raw(&client_id, data, data_size);
return memcmp_nn(a->raw, a->size, b->raw, b->size);
}
+
+int json_dispatch_client_id(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ sd_dhcp_client_id *client_id = ASSERT_PTR(userdata);
+ _cleanup_(iovec_done) struct iovec iov = {};
+ int r;
+
+ r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp_client_id_set_raw(client_id, iov.iov_base, iov.iov_len);
+ if (r < 0)
+ return json_log(variant, flags, r, "Failed to set DHCP client ID from JSON field '%s': %m", strna(name));
+
+ return 0;
+}
usec_t t1_time;
usec_t t2_time;
usec_t expire_time;
- uint64_t attempt;
- uint64_t max_attempts;
+ uint64_t discover_attempt;
+ uint64_t request_attempt;
+ uint64_t max_discover_attempts;
+ uint64_t max_request_attempts;
OrderedHashmap *extra_options;
OrderedHashmap *vendor_options;
sd_event_source *timeout_t1;
uint32_t revents,
void *userdata);
static void client_stop(sd_dhcp_client *client, int error);
+static int client_restart(sd_dhcp_client *client);
int dhcp_client_set_state_callback(
sd_dhcp_client *client,
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
- client->max_attempts = max_attempts;
+ client->max_discover_attempts = max_attempts;
return 0;
}
(void) event_source_disable(client->timeout_expire);
(void) event_source_disable(client->timeout_ipv6_only_mode);
- client->attempt = 0;
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
client_set_state(client, DHCP_STATE_STOPPED);
client->xid = 0;
case DHCP_STATE_INIT:
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_SELECTING:
+ if (client->discover_attempt >= client->max_discover_attempts)
+ goto error;
+
+ client->discover_attempt++;
+ next_timeout = client_compute_request_timeout(time_now, client->discover_attempt);
+ break;
case DHCP_STATE_REQUESTING:
case DHCP_STATE_BOUND:
- if (client->attempt >= client->max_attempts)
+ if (client->request_attempt >= client->max_request_attempts)
goto error;
- client->attempt++;
- next_timeout = client_compute_request_timeout(time_now, client->attempt);
+ client->request_attempt++;
+ next_timeout = client_compute_request_timeout(time_now, client->request_attempt);
break;
case DHCP_STATE_STOPPED:
r = client_send_discover(client);
if (r >= 0) {
client_set_state(client, DHCP_STATE_SELECTING);
- client->attempt = 0;
- } else if (client->attempt >= client->max_attempts)
+ client->discover_attempt = 0;
+ } else if (client->discover_attempt >= client->max_discover_attempts)
goto error;
break;
case DHCP_STATE_SELECTING:
r = client_send_discover(client);
- if (r < 0 && client->attempt >= client->max_attempts)
+ if (r < 0 && client->discover_attempt >= client->max_discover_attempts)
goto error;
break;
case DHCP_STATE_RENEWING:
case DHCP_STATE_REBINDING:
r = client_send_request(client);
- if (r < 0 && client->attempt >= client->max_attempts)
+ if (r < 0 && client->request_attempt >= client->max_request_attempts)
goto error;
if (client->state == DHCP_STATE_INIT_REBOOT)
goto error;
}
- if (client->attempt >= TRANSIENT_FAILURE_ATTEMPTS)
+ if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS)
client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE);
return 0;
error:
+ /* Avoid REQUEST infinite loop. Per RFC 2131 section 3.1.5: if the client receives
+ neither a DHCPACK or a DHCPNAK message after employing the retransmission algorithm,
+ the client reverts to INIT state and restarts the initialization process */
+ if (client->request_attempt >= client->max_request_attempts) {
+ log_dhcp_client(client, "Max REQUEST attempts reached. Restarting...");
+ client_restart(client);
+ return 0;
+ }
client_stop(client, r);
/* Errors were dealt with when stopping the client, don't spill
client->fd = safe_close(client->fd);
client_set_state(client, DHCP_STATE_REBINDING);
- client->attempt = 0;
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
&client->hw_addr, &client->bcast_addr,
client_set_state(client, DHCP_STATE_RENEWING);
else if (client->state != DHCP_STATE_INIT)
client_set_state(client, DHCP_STATE_INIT_REBOOT);
- client->attempt = 0;
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
return client_initialize_time_events(client);
}
assert(client);
client_set_state(client, DHCP_STATE_REQUESTING);
- client->attempt = 0;
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
return event_reset_time(client->event, &client->timeout_resend,
CLOCK_BOOTTIME, 0, 0,
notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
client_set_state(client, DHCP_STATE_BOUND);
- client->attempt = 0;
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
client->last_addr = client->lease->address;
assert(client->lease);
client->start_delay = 0;
- client->attempt = 1;
+ client->discover_attempt = 1;
+ client->request_attempt = 1;
client_set_state(client, DHCP_STATE_RENEWING);
return client_initialize_time_events(client);
.mtu = DHCP_MIN_PACKET_SIZE,
.port = DHCP_PORT_CLIENT,
.anonymize = !!anonymize,
- .max_attempts = UINT64_MAX,
+ .max_discover_attempts = UINT64_MAX,
+ .max_request_attempts = 5,
.ip_service_type = -1,
};
/* NOTE: this could be moved to a function. */
assert_return(duid, -EINVAL);
assert_return(data, -EINVAL);
- assert_return(duid_data_size_is_valid(data_size), -EINVAL);
+
+ if (!duid_data_size_is_valid(data_size))
+ return -EINVAL;
unaligned_write_be16(&duid->duid.type, duid_type);
memcpy(duid->duid.data, data, data_size);
assert_return(duid, -EINVAL);
assert_return(data, -EINVAL);
- assert_return(duid_size_is_valid(data_size), -EINVAL);
/* Unlike sd_dhcp_duid_set(), this takes whole DUID including its type. */
+ if (!duid_size_is_valid(data_size))
+ return -EINVAL;
+
memcpy(duid->raw, data, data_size);
duid->size = data_size;
const char *t;
assert(data);
- assert(duid_data_size_is_valid(data_size));
assert(ret);
+ if (!duid_data_size_is_valid(data_size))
+ return -EINVAL;
+
x = hexmem(data, data_size);
if (!x)
return -ENOMEM;
_cleanup_free_ void *data = NULL;
size_t data_size;
- r = unhexmem(client_id_hex, SIZE_MAX, &data, &data_size);
+ r = unhexmem(client_id_hex, &data, &data_size);
if (r < 0)
log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex);
}
if (vendor_specific_hex) {
- r = unhexmem(vendor_specific_hex, SIZE_MAX, &lease->vendor_specific, &lease->vendor_specific_len);
+ r = unhexmem(vendor_specific_hex, &lease->vendor_specific, &lease->vendor_specific_len);
if (r < 0)
log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex);
}
if (!options[i])
continue;
- r = unhexmem(options[i], SIZE_MAX, &data, &len);
+ r = unhexmem(options[i], &data, &len);
if (r < 0) {
log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]);
continue;
static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
if (static_lease)
- return static_lease;
+ goto verify;
/* when no lease is found based on the client id fall back to chaddr */
if (!client_id_data_size_is_valid(req->message->hlen))
if (sd_dhcp_client_id_set(&client_id, /* type = */ 1, req->message->chaddr, req->message->hlen) < 0)
return NULL;
- return hashmap_get(server->static_leases_by_client_id, &client_id);
+ static_lease = hashmap_get(server->static_leases_by_client_id, &client_id);
+ if (!static_lease)
+ return NULL;
+
+verify:
+ /* Check if the address is in the same subnet. */
+ if ((static_lease->address & server->netmask) != server->subnet)
+ return NULL;
+
+ /* Check if the address is different from the server address. */
+ if (static_lease->address == server->address)
+ return NULL;
+
+ return static_lease;
}
int sd_dhcp_server_set_static_lease(
server->address = address->s_addr;
server->netmask = netmask;
server->subnet = address->s_addr & netmask;
-
- /* Drop any leases associated with the old address range */
- hashmap_clear(server->bound_leases_by_address);
- hashmap_clear(server->bound_leases_by_client_id);
-
- if (server->callback)
- server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
}
return 0;
return 0;
/* for now pick a random free address from the pool */
- if (static_lease)
+ if (static_lease) {
+ if (existing_lease != hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address)))
+ /* The address is already assigned to another host. Refusing. */
+ return 0;
+
+ /* Found a matching static lease. */
address = static_lease->address;
- else if (existing_lease)
+
+ } else if (existing_lease && address_is_in_pool(server, existing_lease->address))
+
+ /* If we previously assigned an address to the host, then reuse it. */
address = existing_lease->address;
+
else {
struct siphash state;
uint64_t hash;
/* Silently ignore Rapid Commit option in REQUEST message. */
req->rapid_commit = false;
- /* disallow our own address */
- if (address == server->address)
- return 0;
-
if (static_lease) {
- /* Found a static lease for the client ID. */
-
if (static_lease->address != address)
- /* The client requested an address which is different from the static lease. Refuse. */
+ /* The client requested an address which is different from the static lease. Refusing. */
+ return server_send_nak_or_ignore(server, init_reboot, req);
+
+ if (existing_lease != hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address)))
+ /* The requested address is already assigned to another host. Refusing. */
return server_send_nak_or_ignore(server, init_reboot, req);
+ /* Found a static lease for the client ID. */
return server_ack_request(server, req, address);
}
- if (address_is_in_pool(server, address)) {
+ if (address_is_in_pool(server, address))
/* The requested address is in the pool. */
-
- if (existing_lease && existing_lease->address != address)
- /* We previously assigned an address, but the client requested another one. Refuse. */
- return server_send_nak_or_ignore(server, init_reboot, req);
-
return server_ack_request(server, req, address);
- }
+ /* Refuse otherwise. */
return server_send_nak_or_ignore(server, init_reboot, req);
}
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
test.option_server_id.address = htobe32(INADDR_LOOPBACK);
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4);
- assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30);
- assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
/* request address reserved for static lease (unmatching client ID) */
test.option_client_id.id[6] = 'H';
if (copy < 0)
return -errno;
- f = reallocarray(m->fds, sizeof(int), m->n_fds + 1);
+ f = reallocarray(m->fds, m->n_fds + 1, sizeof(int));
if (!f) {
m->poisoned = true;
safe_close(copy);
if (l % 2 != 0)
return 0;
- r = unhexmem(p, l, (void **) &token, &len);
+ r = unhexmem_full(p, l, /* secure = */ false, (void**) &token, &len);
if (r < 0)
return 0;
if (l % 2 != 0)
return 0;
- r = unhexmem(p, l, (void**) &token, &len);
+ r = unhexmem_full(p, l, /* secure = */ false, (void**) &token, &len);
if (r < 0)
return 0;
assert(b->sockaddr.sa.sa_family == AF_UNIX);
assert(b->sockaddr.un.sun_path[0] != 0);
- /* Sets up an inotify fd in case watch_bind is enabled: wait until the configured AF_UNIX file system socket
- * appears before connecting to it. The implemented is pretty simplistic: we just subscribe to relevant changes
- * to all prefix components of the path, and every time we get an event for that we try to reconnect again,
- * without actually caring what precisely the event we got told us. If we still can't connect we re-subscribe
- * to all relevant changes of anything in the path, so that our watches include any possibly newly created path
- * components. */
+ /* Sets up an inotify fd in case watch_bind is enabled: wait until the configured AF_UNIX file system
+ * socket appears before connecting to it. The implemented is pretty simplistic: we just subscribe to
+ * relevant changes to all components of the path, and every time we get an event for that we try to
+ * reconnect again, without actually caring what precisely the event we got told us. If we still
+ * can't connect we re-subscribe to all relevant changes of anything in the path, so that our watches
+ * include any possibly newly created path components. */
if (b->inotify_fd < 0) {
b->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
if (r < 0)
goto fail;
- /* Watch all parent directories, and don't mind any prefix that doesn't exist yet. For the innermost directory
- * that exists we want to know when files are created or moved into it. For all parents of it we just care if
- * they are removed or renamed. */
+ /* Watch all components of the path, and don't mind any prefix that doesn't exist yet. For the
+ * innermost directory that exists we want to know when files are created or moved into it. For all
+ * parents of it we just care if they are removed or renamed. */
if (!GREEDY_REALLOC(new_watches, n + 1)) {
r = -ENOMEM;
goto fail;
}
- /* Start with the top-level directory, which is a bit simpler than the rest, since it can't be a symlink, and
- * always exists */
+ /* Start with the top-level directory, which is a bit simpler than the rest, since it can't be a
+ * symlink, and always exists */
wd = inotify_add_watch(b->inotify_fd, "/", IN_CREATE|IN_MOVED_TO);
if (wd < 0) {
r = log_debug_errno(errno, "Failed to add inotify watch on /: %m");
static int parse_address_key(const char **p, const char *key, char **value) {
_cleanup_free_ char *r = NULL;
- size_t l, n = 0;
+ size_t n = 0;
const char *a;
assert(p);
assert(value);
if (key) {
- l = strlen(key);
- if (strncmp(*p, key, l) != 0)
- return 0;
-
- if ((*p)[l] != '=')
+ a = startswith(*p, key);
+ if (!a || *a != '=')
return 0;
if (*value)
return -EINVAL;
- a = *p + l + 1;
+ a++;
} else
a = *p;
return -ENOMEM;
}
- a = strjoin("unixexec:path=ssh,argv1=-xT", p ? ",argv2=-p,argv3=" : "", strempty(p),
- ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e,
- ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c);
+ const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh";
+ _cleanup_free_ char *ssh_escaped = bus_address_escape(ssh);
+ if (!ssh_escaped)
+ return -ENOMEM;
+
+ a = strjoin("unixexec:path=", ssh_escaped, ",argv1=-xT",
+ p ? ",argv2=-p,argv3=" : "", strempty(p),
+ ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e,
+ ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c);
if (!a)
return -ENOMEM;
for (template_pos = path_template; *template_pos; ) {
const char *sep;
- size_t length;
+ size_t length, path_length;
char *label;
/* verify everything until the next '%' matches verbatim */
sep = strchrnul(template_pos, '%');
length = sep - template_pos;
- if (strncmp(path_pos, template_pos, length))
+ if (!strneq(path_pos, template_pos, length))
return 0;
path_pos += length;
/* verify the suffixes match */
sep = strchrnul(path_pos, '/');
- if (sep - path_pos < (ssize_t)length ||
- strncmp(sep - length, template_pos, length))
+ path_length = sep - path_pos;
+ if (length > path_length || !strneq(sep - length, template_pos, length))
return 0;
template_pos += length; /* skip over matched label */
union sockaddr_union snl_trusted_sender;
bool bound;
- UidRange *mapped_userns_uid_range;
+ UIDRange *mapped_userns_uid_range;
Hashmap *subsystem_filter;
Set *tag_filter;
#include "alloc-util.h"
#include "log.h"
#include "macro.h"
+#include "strv.h"
#define device_unref_and_replace(a, b) \
unref_and_replace_full(a, b, sd_device_ref, sd_device_unref)
bool device_in_subsystem(sd_device *device, const char *subsystem);
bool device_is_devtype(sd_device *device, const char *devtype);
+
+static inline bool device_property_can_set(const char *property) {
+ return property &&
+ !STR_IN_SET(property,
+ "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER",
+ "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS");
+}
env = secure_getenv("MEMORY_PRESSURE_WRITE");
if (env) {
- r = unbase64mem(env, SIZE_MAX, &write_buffer, &write_buffer_size);
+ r = unbase64mem(env, &write_buffer, &write_buffer_size);
if (r < 0)
return r;
}
#include "stdio-util.h"
#include "string-util.h"
#include "sync-util.h"
+#include "virt.h"
int id128_from_string_nonzero(const char *s, sd_id128_t *ret) {
sd_id128_t t;
/* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is
* particularly relevant in VM environments, where VM managers typically place a VM uuid there. */
+ r = detect_container();
+ if (r < 0)
+ return r;
+ if (r > 0) /* Refuse returning this in containers, as this is not a property of our system then, but
+ * of the host */
+ return -ENOENT;
+
r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
if (r == -ENOENT)
r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
Object **ret_object, /* The found object. */
uint64_t *ret_offset) { /* The offset of the found object. */
- uint64_t a, t = 0, k;
+ uint64_t a, t = 0, k = 0; /* Explicit initialization of k to appease gcc */
ChainCacheItem *ci;
Object *o = NULL;
int r;
uint64_t current_field;
Match *level0, *level1, *level2;
+ Set *exclude_syslog_identifiers;
uint64_t origin_id;
#include "string-util.h"
#include "strv.h"
#include "syslog-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC)
hashmap_free_free(j->errors);
+ set_free(j->exclude_syslog_identifiers);
+
free(j->path);
free(j->prefix);
free(j->namespace);
uid_t *t;
n = MAX(16, 2*r);
- t = reallocarray(l, sizeof(uid_t), n);
+ t = reallocarray(l, n, sizeof(uid_t));
if (!t)
return -ENOMEM;
return IN_SET(type, RTM_NEWMDB, RTM_DELMDB, RTM_GETMDB);
}
+static bool rtnl_message_type_is_nsid(uint16_t type) {
+ return IN_SET(type, RTM_NEWNSID, RTM_DELNSID, RTM_GETNSID);
+}
+
int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) {
struct rtmsg *rtm;
return 0;
}
+
+int sd_rtnl_message_new_nsid(
+ sd_netlink *rtnl,
+ sd_netlink_message **ret,
+ uint16_t nlmsg_type) {
+
+ struct rtgenmsg *rt;
+ int r;
+
+ assert_return(rtnl_message_type_is_nsid(nlmsg_type), -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = message_new(rtnl, ret, nlmsg_type);
+ if (r < 0)
+ return r;
+
+ rt = NLMSG_DATA((*ret)->hdr);
+ rt->rtgen_family = AF_UNSPEC;
+
+ return 0;
+}
assert_return(m, -EINVAL);
- r = netlink_message_read_internal(m, attr_type, &attr_data, NULL);
- if (r < 0)
- return r;
-
- if (ret_data) {
- void *data;
-
- data = memdup(attr_data, r);
- if (!data)
- return -ENOMEM;
-
- *ret_data = data;
- }
-
- if (ret_size)
- *ret_size = r;
-
- return r;
-}
-
-int sd_netlink_message_read_data_suffix0(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data) {
- void *attr_data;
- int r;
-
- assert_return(m, -EINVAL);
-
r = netlink_message_read_internal(m, attr_type, &attr_data, NULL);
if (r < 0)
return r;
#include <linux/if_tunnel.h>
#include <linux/ip.h>
#include <linux/l2tp.h>
+#include <linux/net_namespace.h>
#include <linux/netlink.h>
#include <linux/nexthop.h>
#include <linux/nl80211.h>
DEFINE_POLICY_SET(rtnl_mdb);
+static const NLAPolicy rtnl_nsid_policies[] = {
+ [NETNSA_FD] = BUILD_POLICY(S32),
+ [NETNSA_NSID] = BUILD_POLICY(U32),
+};
+
+DEFINE_POLICY_SET(rtnl_nsid);
+
static const NLAPolicy rtnl_policies[] = {
[RTM_NEWLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)),
[RTM_DELLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)),
[RTM_NEWMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)),
[RTM_DELMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)),
[RTM_GETMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)),
+ [RTM_NEWNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)),
+ [RTM_DELNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)),
+ [RTM_GETNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)),
};
DEFINE_POLICY_SET(rtnl);
assert(ifindex > 0);
if (ret) {
- r = sd_netlink_message_read_string_strdup(message, IFLA_IFNAME, ret);
+ r = sd_netlink_message_read_string_strdup(reply, IFLA_IFNAME, ret);
if (r < 0)
return r;
}
assert_se(!strv_contains(alternative_names, "testlongalternativename"));
assert_se(strv_contains(alternative_names, "test-additional-name"));
assert_se(!strv_contains(alternative_names, "test-shortname"));
+
+ _cleanup_free_ char *resolved = NULL;
+ assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-additional-name", &resolved) == ifindex);
+ assert_se(streq_ptr(resolved, "test-shortname"));
}
DEFINE_TEST_MAIN(LOG_DEBUG);
DEFINE_STRING_TABLE_LOOKUP(link_online_state, LinkOnlineState);
-int parse_operational_state_range(const char *str, LinkOperationalStateRange *out) {
- LinkOperationalStateRange range = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID };
- _cleanup_free_ const char *min = NULL;
+int parse_operational_state_range(const char *s, LinkOperationalStateRange *ret) {
+ LinkOperationalStateRange range = LINK_OPERSTATE_RANGE_INVALID;
+ _cleanup_free_ char *buf = NULL;
const char *p;
- assert(str);
- assert(out);
-
- p = strchr(str, ':');
- if (p) {
- min = strndup(str, p - str);
+ assert(s);
+ assert(ret);
- if (!isempty(p + 1)) {
- range.max = link_operstate_from_string(p + 1);
- if (range.max < 0)
- return -EINVAL;
- }
- } else
- min = strdup(str);
+ /* allowed formats: "min", "min:", "min:max", ":max" */
- if (!min)
- return -ENOMEM;
+ if (isempty(s) || streq(s, ":"))
+ return -EINVAL;
- if (!isempty(min)) {
- range.min = link_operstate_from_string(min);
- if (range.min < 0)
+ p = strchr(s, ':');
+ if (!p || isempty(p + 1))
+ range.max = LINK_OPERSTATE_ROUTABLE;
+ else {
+ range.max = link_operstate_from_string(p + 1);
+ if (range.max < 0)
return -EINVAL;
}
- /* Fail on empty strings. */
- if (range.min == _LINK_OPERSTATE_INVALID && range.max == _LINK_OPERSTATE_INVALID)
- return -EINVAL;
+ if (p) {
+ buf = strndup(s, p - s);
+ if (!buf)
+ return -ENOMEM;
- if (range.min == _LINK_OPERSTATE_INVALID)
+ s = buf;
+ }
+
+ if (isempty(s))
range.min = LINK_OPERSTATE_MISSING;
- if (range.max == _LINK_OPERSTATE_INVALID)
- range.max = LINK_OPERSTATE_ROUTABLE;
+ else {
+ range.min = link_operstate_from_string(s);
+ if (range.min < 0)
+ return -EINVAL;
+ }
- if (range.min > range.max)
+ if (!operational_state_range_is_valid(&range))
return -EINVAL;
- *out = range;
-
+ *ret = range;
return 0;
}
LinkOperationalState max;
} LinkOperationalStateRange;
-#define LINK_OPERSTATE_RANGE_DEFAULT (LinkOperationalStateRange) { LINK_OPERSTATE_DEGRADED, \
- LINK_OPERSTATE_ROUTABLE }
-
-int parse_operational_state_range(const char *str, LinkOperationalStateRange *out);
+#define LINK_OPERSTATE_RANGE_DEFAULT \
+ (const LinkOperationalStateRange) { \
+ .min = LINK_OPERSTATE_DEGRADED, \
+ .max = LINK_OPERSTATE_ROUTABLE, \
+ }
+
+#define LINK_OPERSTATE_RANGE_INVALID \
+ (const LinkOperationalStateRange) { \
+ .min = _LINK_OPERSTATE_INVALID, \
+ .max = _LINK_OPERSTATE_INVALID, \
+ }
+
+int parse_operational_state_range(const char *s, LinkOperationalStateRange *ret);
int network_link_get_operational_state(int ifindex, LinkOperationalState *ret);
+
+static inline bool operational_state_is_valid(LinkOperationalState s) {
+ return s >= 0 && s < _LINK_OPERSTATE_MAX;
+}
+static inline bool operational_state_range_is_valid(const LinkOperationalStateRange *range) {
+ return range &&
+ operational_state_is_valid(range->min) &&
+ operational_state_is_valid(range->max) &&
+ range->min <= range->max;
+}
+static inline bool operational_state_is_in_range(LinkOperationalState s, const LinkOperationalStateRange *range) {
+ return range && range->min <= s && s <= range->max;
+}
#include "bus-polkit.h"
#include "bus-unit-util.h"
#include "constants.h"
+#include "daemon-util.h"
#include "kbd-util.h"
#include "localed-util.h"
#include "macro.h"
if (r < 0)
return r;
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
-
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
(void) sd_event_set_watchdog(event, true);
- r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to install SIGINT handler: %m");
-
- r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
+ r = sd_event_set_signal_exit(event, true);
if (r < 0)
- return log_error_errno(r, "Failed to install SIGTERM handler: %m");
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
r = connect_bus(&context, event, &bus);
if (r < 0)
return r;
+ r = sd_notify(false, NOTIFY_READY);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
+
r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
static bool arg_full = false;
static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static const char *arg_kill_whom = NULL;
static int arg_signal = SIGTERM;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
colors_enabled() * OUTPUT_COLOR;
}
-static int show_table(Table *table, const char *word) {
+static int list_table_print(Table *table, const char *type) {
int r;
assert(table);
- assert(word);
+ assert(type);
- if (!table_isempty(table) || OUTPUT_MODE_IS_JSON(arg_output)) {
- r = table_set_sort(table, (size_t) 0);
- if (r < 0)
- return table_log_sort_error(r);
+ r = table_set_sort(table, (size_t) 0);
+ if (r < 0)
+ return table_log_sort_error(r);
- table_set_header(table, arg_legend);
+ r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
+ if (r < 0)
+ return r;
- if (OUTPUT_MODE_IS_JSON(arg_output))
- r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | JSON_FORMAT_COLOR_AUTO);
+ if (arg_legend) {
+ if (table_isempty(table))
+ printf("No %s.\n", type);
else
- r = table_print(table, NULL);
- if (r < 0)
- return table_log_print_error(r);
+ printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type);
}
- if (arg_legend) {
- if (table_isempty(table))
- printf("No %s.\n", word);
+ return 0;
+}
+
+static int list_sessions_table_add(Table *table, sd_bus_message *reply) {
+ int r;
+
+ assert(table);
+ assert(reply);
+
+ r = sd_bus_message_enter_container(reply, 'a', "(sussussbto)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ for (;;) {
+ const char *session_id, *user, *seat, *class, *tty;
+ uint32_t uid, leader_pid;
+ int idle;
+ uint64_t idle_timestamp_monotonic;
+
+ r = sd_bus_message_read(reply, "(sussussbto)",
+ &session_id,
+ &uid,
+ &user,
+ &seat,
+ &leader_pid,
+ &class,
+ &tty,
+ &idle,
+ &idle_timestamp_monotonic,
+ /* object = */ NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ r = table_add_many(table,
+ TABLE_STRING, session_id,
+ TABLE_UID, (uid_t) uid,
+ TABLE_STRING, user,
+ TABLE_STRING, empty_to_null(seat),
+ TABLE_PID, (pid_t) leader_pid,
+ TABLE_STRING, class,
+ TABLE_STRING, empty_to_null(tty),
+ TABLE_BOOLEAN, idle);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (idle)
+ r = table_add_cell(table, NULL, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, &idle_timestamp_monotonic);
else
- printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word);
+ r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+ if (r < 0)
+ return table_log_add_error(r);
}
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
return 0;
}
-static int list_sessions(int argc, char *argv[], void *userdata) {
+static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, sd_bus *bus) {
static const struct bus_properties_map map[] = {
+ { "Leader", "u", NULL, offsetof(SessionStatusInfo, leader) },
+ { "Class", "s", NULL, offsetof(SessionStatusInfo, class) },
+ { "TTY", "s", NULL, offsetof(SessionStatusInfo, tty) },
{ "IdleHint", "b", NULL, offsetof(SessionStatusInfo, idle_hint) },
{ "IdleSinceHintMonotonic", "t", NULL, offsetof(SessionStatusInfo, idle_hint_timestamp.monotonic) },
- { "State", "s", NULL, offsetof(SessionStatusInfo, state) },
- { "TTY", "s", NULL, offsetof(SessionStatusInfo, tty) },
{},
};
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- _cleanup_(table_unrefp) Table *table = NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
int r;
- assert(argv);
-
- pager_open(arg_pager_flags);
-
- r = bus_call_method(bus, bus_login_mgr, "ListSessions", &error, &reply, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to list sessions: %s", bus_error_message(&error, r));
+ assert(table);
+ assert(reply);
+ assert(bus);
r = sd_bus_message_enter_container(reply, 'a', "(susso)");
if (r < 0)
return bus_log_parse_error(r);
- table = table_new("session", "uid", "user", "seat", "tty", "state", "idle", "since");
- if (!table)
- return log_oom();
-
- /* Right-align the first two fields (since they are numeric) */
- (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100);
- (void) table_set_align_percent(table, TABLE_HEADER_CELL(1), 100);
-
- (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
-
for (;;) {
_cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
const char *id, *user, *seat, *object;
uint32_t uid;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
SessionStatusInfo i = {};
r = sd_bus_message_read(reply, "(susso)", &id, &uid, &user, &seat, &object);
TABLE_UID, (uid_t) uid,
TABLE_STRING, user,
TABLE_STRING, empty_to_null(seat),
+ TABLE_PID, i.leader,
+ TABLE_STRING, i.class,
TABLE_STRING, empty_to_null(i.tty),
- TABLE_STRING, i.state,
TABLE_BOOLEAN, i.idle_hint);
if (r < 0)
return table_log_add_error(r);
if (r < 0)
return bus_log_parse_error(r);
- return show_table(table, "sessions");
+ return 0;
+}
+
+static int list_sessions(int argc, char *argv[], void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(table_unrefp) Table *table = NULL;
+ bool use_ex = true;
+ int r;
+
+ assert(argv);
+
+ r = bus_call_method(bus, bus_login_mgr, "ListSessionsEx", &error, &reply, NULL);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+ sd_bus_error_free(&error);
+
+ use_ex = false;
+ r = bus_call_method(bus, bus_login_mgr, "ListSessions", &error, &reply, NULL);
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to list sessions: %s", bus_error_message(&error, r));
+ }
+
+ table = table_new("session", "uid", "user", "seat", "leader", "class", "tty", "idle", "since");
+ if (!table)
+ return log_oom();
+
+ /* Right-align the first two fields (since they are numeric) */
+ (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100);
+ (void) table_set_align_percent(table, TABLE_HEADER_CELL(1), 100);
+
+ (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ if (use_ex)
+ r = list_sessions_table_add(table, reply);
+ else
+ r = list_sessions_table_add_fallback(table, reply, bus);
+ if (r < 0)
+ return r;
+
+ return list_table_print(table, "sessions");
}
static int list_users(int argc, char *argv[], void *userdata) {
assert(argv);
- pager_open(arg_pager_flags);
-
r = bus_call_method(bus, bus_login_mgr, "ListUsers", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r));
if (r < 0)
return bus_log_parse_error(r);
- return show_table(table, "users");
+ return list_table_print(table, "users");
}
static int list_seats(int argc, char *argv[], void *userdata) {
assert(argv);
- pager_open(arg_pager_flags);
-
r = bus_call_method(bus, bus_login_mgr, "ListSeats", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to list seats: %s", bus_error_message(&error, r));
if (r < 0)
return bus_log_parse_error(r);
- return show_table(table, "seats");
+ return list_table_print(table, "seats");
}
static int show_unit_cgroup(
ARG_VALUE,
ARG_NO_PAGER,
ARG_NO_LEGEND,
+ ARG_JSON,
ARG_KILL_WHOM,
ARG_NO_ASK_PASSWORD,
};
{ "full", no_argument, NULL, 'l' },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "json", required_argument, NULL, ARG_JSON },
{ "kill-whom", required_argument, NULL, ARG_KILL_WHOM },
{ "signal", required_argument, NULL, 's' },
{ "host", required_argument, NULL, 'H' },
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:j", options, NULL)) >= 0)
switch (c) {
if (arg_output < 0)
return log_error_errno(arg_output, "Unknown output '%s'.", optarg);
- if (OUTPUT_MODE_IS_JSON(arg_output))
+ break;
+
+ case 'j':
+ arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
+ arg_legend = false;
+ break;
+
+ case ARG_JSON:
+ r = parse_json_argument(optarg, &arg_json_format_flags);
+ if (r <= 0)
+ return r;
+
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
arg_legend = false;
break;
#include "alloc-util.h"
#include "bus-error.h"
+#include "bus-unit-util.h"
#include "bus-util.h"
#include "conf-parser.h"
#include "format-util.h"
return &handle_action_data_table[action];
}
+static bool handle_action_sleep_supported(HandleAction action) {
+ assert(HANDLE_ACTION_IS_SLEEP(action) && action != HANDLE_SLEEP);
+ return sleep_supported(ASSERT_PTR(handle_action_lookup(action))->sleep_operation) > 0;
+}
+
/* The order in which we try each sleep operation. We should typically prefer operations without a delay,
* i.e. s2h and suspend, and use hibernation at last since it requires minimum hardware support.
* hybrid-sleep is disabled by default, and thus should be ordered before suspend if manually chosen by user,
return 0;
}
-HandleAction handle_action_sleep_select(HandleActionSleepMask mask) {
+HandleAction handle_action_sleep_select(Manager *m) {
+ assert(m);
FOREACH_ARRAY(i, sleep_actions, ELEMENTSOF(sleep_actions)) {
- HandleActionSleepMask a = 1U << *i;
+ HandleActionSleepMask action_mask = 1U << *i;
+ const HandleActionData *a;
+ _cleanup_free_ char *load_state = NULL;
+
+ if (!FLAGS_SET(m->handle_action_sleep_mask, action_mask))
+ continue;
+
+ a = ASSERT_PTR(handle_action_lookup(*i));
- if (!FLAGS_SET(mask, a))
+ if (sleep_supported(a->sleep_operation) <= 0)
continue;
- if (sleep_supported(ASSERT_PTR(handle_action_lookup(*i))->sleep_operation) > 0)
+ (void) unit_load_state(m->bus, a->target, &load_state);
+ if (streq_ptr(load_state, "loaded"))
return *i;
}
if (handle == HANDLE_SLEEP) {
HandleAction a;
- a = handle_action_sleep_select(m->handle_action_sleep_mask);
+ a = handle_action_sleep_select(m);
if (a < 0)
return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"None of the configured sleep operations are supported, ignoring.");
return handle_action_sleep_execute(m, a, ignore_inhibited, is_edge);
}
- bool supported;
-
- if (handle == HANDLE_SUSPEND)
- supported = sleep_supported(SLEEP_SUSPEND) > 0;
- else if (handle == HANDLE_HIBERNATE)
- supported = sleep_supported(SLEEP_HIBERNATE) > 0;
- else if (handle == HANDLE_HYBRID_SLEEP)
- supported = sleep_supported(SLEEP_HYBRID_SLEEP) > 0;
- else if (handle == HANDLE_SUSPEND_THEN_HIBERNATE)
- supported = sleep_supported(SLEEP_SUSPEND_THEN_HIBERNATE) > 0;
- else
- assert_not_reached();
+ bool supported = handle_action_sleep_supported(handle);
if (!supported && handle != HANDLE_SUSPEND) {
supported = sleep_supported(SLEEP_SUSPEND) > 0;
};
int handle_action_get_enabled_sleep_actions(HandleActionSleepMask mask, char ***ret);
-HandleAction handle_action_sleep_select(HandleActionSleepMask mask);
+HandleAction handle_action_sleep_select(Manager *m);
int manager_handle_action(
Manager *m,
#include "async.h"
#include "fd-util.h"
#include "logind-button.h"
+#include "logind-dbus.h"
#include "missing_input.h"
#include "string-util.h"
b->lid_closed = true;
button_lid_switch_handle_action(b->manager, true);
button_install_check_event_source(b);
+ manager_send_changed(b->manager, "LidClosed", NULL);
} else if (ev.code == SW_DOCK) {
log_struct(LOG_INFO,
b->lid_closed = false;
b->check_event_source = sd_event_source_unref(b->check_event_source);
+ manager_send_changed(b->manager, "LidClosed", NULL);
} else if (ev.code == SW_DOCK) {
log_struct(LOG_INFO,
b->lid_closed = bitset_get(switches, SW_LID);
b->docked = bitset_get(switches, SW_DOCK);
+ manager_send_changed(b->manager, "LidClosed", NULL);
if (b->lid_closed)
button_install_check_event_source(b);
dual_timestamp k;
int ih;
+ if (!SESSION_CLASS_CAN_IDLE(s->class))
+ continue;
+
ih = session_get_idle_hint(s, &k);
if (ih < 0)
return ih;
return sd_bus_send(NULL, reply, NULL);
}
+static int method_list_sessions_ex(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(sussussbto)");
+ if (r < 0)
+ return r;
+
+ Session *s;
+ HASHMAP_FOREACH(s, m->sessions) {
+ _cleanup_free_ char *path = NULL;
+ dual_timestamp idle_ts;
+ bool idle;
+
+ assert(s->user);
+
+ path = session_bus_path(s);
+ if (!path)
+ return -ENOMEM;
+
+ r = session_get_idle_hint(s, &idle_ts);
+ if (r < 0)
+ return r;
+ idle = r > 0;
+
+ r = sd_bus_message_append(reply, "(sussussbto)",
+ s->id,
+ (uint32_t) s->user->user_record->uid,
+ s->user->user_record->user_name,
+ s->seat ? s->seat->id : "",
+ (uint32_t) s->leader.pid,
+ session_class_to_string(s->class),
+ s->tty,
+ idle,
+ idle_ts.monotonic,
+ path);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
static int method_list_users(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Manager *m = ASSERT_PTR(userdata);
c = SESSION_USER;
}
- /* Check if we are already in a logind session. Or if we are in user@.service
- * which is a special PAM session that avoids creating a logind session. */
- r = manager_get_user_by_pid(m, leader.pid, NULL);
+ /* Check if we are already in a logind session, and if so refuse. */
+ r = manager_get_session_by_pidref(m, &leader, /* ret_session= */ NULL);
if (r < 0)
return r;
if (r > 0)
return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY,
"Already running in a session or user slice");
- /*
- * Old gdm and lightdm start the user-session on the same VT as
- * the greeter session. But they destroy the greeter session
- * after the user-session and want the user-session to take
- * over the VT. We need to support this for
- * backwards-compatibility, so make sure we allow new sessions
- * on a VT that a greeter is running on. Furthermore, to allow
- * re-logins, we have to allow a greeter to take over a used VT for
- * the exact same reasons.
- */
+ /* Old gdm and lightdm start the user-session on the same VT as the greeter session. But they destroy
+ * the greeter session after the user-session and want the user-session to take over the VT. We need
+ * to support this for backwards-compatibility, so make sure we allow new sessions on a VT that a
+ * greeter is running on. Furthermore, to allow re-logins, we have to allow a greeter to take over a
+ * used VT for the exact same reasons. */
if (c != SESSION_GREETER &&
vtnr > 0 &&
vtnr < MALLOC_ELEMENTSOF(m->seat0->positions) &&
goto fail;
session->original_type = session->type = t;
- session->class = c;
session->remote = remote;
session->vtnr = vtnr;
+ session->class = c;
+
+ /* Once the first session that is of a pinning class shows up we'll change the GC mode for the user
+ * from USER_GC_BY_ANY to USER_GC_BY_PIN, so that the user goes away once the last pinning session
+ * goes away. Background: we want that user@.service – when started manually – remains around (which
+ * itself is a non-pinning session), but gets stopped when the last pinning session goes away. */
+
+ if (SESSION_CLASS_PIN_USER(c))
+ user->gc_mode = USER_GC_BY_PIN;
if (!isempty(tty)) {
session->tty = strdup(tty);
session->create_message = sd_bus_message_ref(message);
- /* Now, let's wait until the slice unit and stuff got created. We send the reply back from
- * session_send_create_reply(). */
+ /* Now call into session_send_create_reply(), which will reply to this method call for us. Or it
+ * won't – in case we just spawned a session scope and/or user service manager, and they aren't ready
+ * yet. We'll call session_create_reply() again once the session scope or the user service manager is
+ * ready, where the function will check again if a reply is then ready to be sent, and then do so if
+ * all is complete - or wait again. */
+ r = session_send_create_reply(session, /* error= */ NULL);
+ if (r < 0)
+ return r;
return 1;
int r;
assert(m);
+ assert(!m->action_job);
assert(a);
if (a->inhibit_what == INHIBIT_SHUTDOWN)
if (r < 0)
goto error;
- r = free_and_strdup(&m->action_job, p);
- if (r < 0)
+ m->action_job = strdup(p);
+ if (!m->action_job) {
+ r = -ENOMEM;
goto error;
+ }
m->delayed_action = a;
if (action == HANDLE_SLEEP) {
HandleAction selected;
- selected = handle_action_sleep_select(m->handle_action_sleep_mask);
+ selected = handle_action_sleep_select(m);
if (selected < 0)
return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
"None of the configured sleep operations are supported");
if (r != 0)
return r;
+ if (m->delayed_action)
+ return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
+ "Action %s already in progress, refusing requested %s operation.",
+ handle_action_to_string(m->delayed_action->handle),
+ handle_action_to_string(a->handle));
+
/* reset case we're shorting a scheduled shutdown */
m->unlink_nologin = false;
reset_scheduled_shutdown(m);
/* Don't allow multiple jobs being executed at the same time */
if (m->delayed_action) {
- r = -EALREADY;
- log_error("Scheduled shutdown to %s failed: shutdown or sleep operation already in progress", a->target);
+ r = log_error_errno(SYNTHETIC_ERRNO(EALREADY),
+ "Scheduled shutdown to %s failed: shutdown or sleep operation already in progress.",
+ a->target);
goto error;
}
sd_bus_error *error) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
- bool multiple_sessions, challenge, blocked;
+ bool multiple_sessions, challenge, blocked, check_unit_state = true;
const HandleActionData *a;
const char *result = NULL;
uid_t uid;
if (action == HANDLE_SLEEP) {
HandleAction selected;
- selected = handle_action_sleep_select(m->handle_action_sleep_mask);
+ selected = handle_action_sleep_select(m);
if (selected < 0)
return sd_bus_reply_method_return(message, "s", "na");
+ check_unit_state = false; /* Already handled by handle_action_sleep_select */
+
assert_se(a = handle_action_lookup(selected));
} else if (HANDLE_ACTION_IS_SLEEP(action)) {
multiple_sessions = r > 0;
blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL);
- if (a->target) {
+ if (check_unit_state && a->target) {
_cleanup_free_ char *load_state = NULL;
r = unit_load_state(m->bus, a->target, &load_state);
SD_BUS_PROPERTY("PreparingForSleep", "b", property_get_preparing, 0, 0),
SD_BUS_PROPERTY("ScheduledShutdown", "(st)", property_get_scheduled_shutdown, 0, 0),
SD_BUS_PROPERTY("Docked", "b", property_get_docked, 0, 0),
- SD_BUS_PROPERTY("LidClosed", "b", property_get_lid_closed, 0, 0),
+ SD_BUS_PROPERTY("LidClosed", "b", property_get_lid_closed, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("OnExternalPower", "b", property_get_on_external_power, 0, 0),
SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(Manager, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectorySize", "t", NULL, offsetof(Manager, runtime_dir_size), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_RESULT("a(susso)", sessions),
method_list_sessions,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ListSessionsEx",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("a(sussussbto)", sessions),
+ method_list_sessions_ex,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("ListUsers",
SD_BUS_NO_ARGS,
SD_BUS_RESULT("a(uso)", users),
if (r == 0)
return 1; /* Will call us back */
- r = session_send_lock(s, strstr(sd_bus_message_get_member(message), "Lock"));
+ r = session_send_lock(s, /* lock= */ strstr(sd_bus_message_get_member(message), "Lock"));
+ if (r == -ENOTTY)
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen.");
if (r < 0)
return r;
r = session_set_idle_hint(s, b);
if (r == -ENOTTY)
- return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical sessions.");
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical and non-user sessions.");
if (r < 0)
return r;
if (uid != 0 && uid != s->user->user_record->uid)
return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may set locked hint");
- session_set_locked_hint(s, b);
+ r = session_set_locked_hint(s, b);
+ if (r == -ENOTTY)
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen.");
+ if (r < 0)
+ return r;
return sd_bus_reply_method_return(message, NULL);
}
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid session type '%s'", t);
+ if (!SESSION_CLASS_CAN_CHANGE_TYPE(s->class))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support changing type.");
+
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You must be in control of this session to set type");
if (!DEVICE_MAJOR_VALID(major) || !DEVICE_MINOR_VALID(minor))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Device major/minor is not valid.");
+ if (!SESSION_CLASS_CAN_TAKE_DEVICE(s->class))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support taking device control.");
+
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You are not in control of this session");
assert(s);
+ if (!SESSION_CLASS_CAN_LOCK(s->class))
+ return -ENOTTY;
+
p = session_bus_path(s);
if (!p)
return -ENOMEM;
HASHMAP_FOREACH(session, m->sessions) {
int k;
+ if (!SESSION_CLASS_CAN_LOCK(session->class))
+ continue;
+
k = session_send_lock(session, lock);
if (k < 0)
r = k;
/* Returns true when the session is ready, i.e. all jobs we enqueued for it are done (regardless if successful or not) */
return !s->scope_job &&
- !s->user->service_job;
+ (!SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) || !s->user->service_job);
}
int session_send_create_reply(Session *s, sd_bus_error *error) {
#include "strv.h"
#include "terminal-util.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#define RELEASE_USEC (20*USEC_PER_SEC)
assert(s);
assert(s->user);
+ if (!SESSION_CLASS_WANTS_SCOPE(s->class))
+ return 0;
+
if (!s->scope) {
- _cleanup_strv_free_ char **after = NULL;
+ _cleanup_strv_free_ char **wants = NULL, **after = NULL;
_cleanup_free_ char *scope = NULL;
const char *description;
description = strjoina("Session ", s->id, " of User ", s->user->user_record->user_name);
+ /* These two have StopWhenUnneeded= set, hence add a dep towards them */
+ wants = strv_new(s->user->runtime_dir_service,
+ SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) ? s->user->service : STRV_IGNORE);
+ if (!wants)
+ return log_oom();
+
/* We usually want to order session scopes after systemd-user-sessions.service since the
* latter unit is used as login session barrier for unprivileged users. However the barrier
* doesn't apply for root as sysadmin should always be able to log in (and without waiting
* of STRV_IGNORE with strv_new() to skip these order constraints when needed. */
after = strv_new("systemd-logind.service",
s->user->runtime_dir_service,
- !uid_is_system(s->user->user_record->uid) ? "systemd-user-sessions.service" : STRV_IGNORE,
+ SESSION_CLASS_IS_EARLY(s->class) ? STRV_IGNORE : "systemd-user-sessions.service",
s->user->service);
if (!after)
return log_oom();
&s->leader,
s->user->slice,
description,
- /* These two have StopWhenUnneeded= set, hence add a dep towards them */
- STRV_MAKE(s->user->runtime_dir_service,
- s->user->service),
+ wants,
after,
user_record_home_directory(s->user->user_record),
properties,
assert(s);
- if (s->manager->stop_idle_session_usec == USEC_INFINITY || IN_SET(s->class, SESSION_GREETER, SESSION_LOCK_SCREEN))
+ if (s->manager->stop_idle_session_usec == USEC_INFINITY || !SESSION_CLASS_CAN_STOP_ON_IDLE(s->class))
return 0;
r = sd_event_add_time_relative(
int session_set_idle_hint(Session *s, bool b) {
assert(s);
- if (!SESSION_TYPE_IS_GRAPHICAL(s->type))
+ if (!SESSION_CLASS_CAN_IDLE(s->class)) /* Only some session classes know the idle concept at all */
+ return -ENOTTY;
+ if (!SESSION_TYPE_IS_GRAPHICAL(s->type)) /* And only graphical session types can set the field explicitly */
return -ENOTTY;
if (s->idle_hint == b)
return s->locked_hint;
}
-void session_set_locked_hint(Session *s, bool b) {
+int session_set_locked_hint(Session *s, bool b) {
assert(s);
+ if (!SESSION_CLASS_CAN_LOCK(s->class))
+ return -ENOTTY;
+
if (s->locked_hint == b)
- return;
+ return 0;
s->locked_hint = b;
+ (void) session_save(s);
+ (void) session_send_changed(s, "LockedHint", NULL);
- session_send_changed(s, "LockedHint", NULL);
+ return 1;
}
void session_set_type(Session *s, SessionType t) {
s->fifo_event_source = sd_event_source_unref(s->fifo_event_source);
s->fifo_fd = safe_close(s->fifo_fd);
-
- if (s->fifo_path) {
- (void) unlink(s->fifo_path);
- s->fifo_path = mfree(s->fifo_path);
- }
+ s->fifo_path = unlink_and_free(s->fifo_path);
}
bool session_may_gc(Session *s, bool drop_not_started) {
DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);
static const char* const session_class_table[_SESSION_CLASS_MAX] = {
- [SESSION_USER] = "user",
- [SESSION_GREETER] = "greeter",
- [SESSION_LOCK_SCREEN] = "lock-screen",
- [SESSION_BACKGROUND] = "background",
+ [SESSION_USER] = "user",
+ [SESSION_USER_EARLY] = "user-early",
+ [SESSION_GREETER] = "greeter",
+ [SESSION_LOCK_SCREEN] = "lock-screen",
+ [SESSION_BACKGROUND] = "background",
+ [SESSION_BACKGROUND_LIGHT] = "background-light",
+ [SESSION_MANAGER] = "manager",
+ [SESSION_MANAGER_EARLY] = "manager-early",
};
DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass);
} SessionState;
typedef enum SessionClass {
- SESSION_USER,
- SESSION_GREETER,
- SESSION_LOCK_SCREEN,
- SESSION_BACKGROUND,
+ SESSION_USER, /* A regular user session */
+ SESSION_USER_EARLY, /* A user session, that is not ordered after systemd-user-sessions.service (i.e. for root) */
+ SESSION_GREETER, /* A login greeter pseudo-session */
+ SESSION_LOCK_SCREEN, /* A lock screen */
+ SESSION_BACKGROUND, /* Things like cron jobs, which are non-interactive */
+ SESSION_BACKGROUND_LIGHT, /* Like SESSION_BACKGROUND, but without the service manager */
+ SESSION_MANAGER, /* The service manager */
+ SESSION_MANAGER_EARLY, /* The service manager for root (which is allowed to run before systemd-user-sessions.service) */
_SESSION_CLASS_MAX,
_SESSION_CLASS_INVALID = -EINVAL,
} SessionClass;
+/* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. It's
+ * generally set for root sessions, but no one else. */
+#define SESSION_CLASS_IS_EARLY(class) IN_SET((class), SESSION_USER_EARLY, SESSION_MANAGER_EARLY)
+
+/* Which session classes want their own scope units? (all of them, except the manager, which comes in its own service unit already */
+#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND, SESSION_BACKGROUND_LIGHT)
+
+/* Which session classes want their own per-user service manager? */
+#define SESSION_CLASS_WANTS_SERVICE_MANAGER(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND)
+
+/* Which session classes can pin our user tracking? */
+#define SESSION_CLASS_PIN_USER(class) (!IN_SET((class), SESSION_MANAGER, SESSION_MANAGER_EARLY))
+
+/* Which session classes decide whether system is idle? (should only cover sessions that have input, and are not idle screens themselves)*/
+#define SESSION_CLASS_CAN_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER))
+
+/* Which session classes have a lock screen concept? */
+#define SESSION_CLASS_CAN_LOCK(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY))
+
+/* Which sessions are candidates to become "display" sessions */
+#define SESSION_CLASS_CAN_DISPLAY(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER))
+
+/* Which sessions classes should be subject to stop-in-idle */
+#define SESSION_CLASS_CAN_STOP_ON_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY))
+
+/* Which session classes can take control of devices */
+#define SESSION_CLASS_CAN_TAKE_DEVICE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN))
+
+/* Which session classes allow changing session types */
+#define SESSION_CLASS_CAN_CHANGE_TYPE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN))
+
typedef enum SessionType {
SESSION_UNSPECIFIED,
SESSION_TTY,
int session_get_idle_hint(Session *s, dual_timestamp *t);
int session_set_idle_hint(Session *s, bool b);
int session_get_locked_hint(Session *s);
-void session_set_locked_hint(Session *s, bool b);
+int session_set_locked_hint(Session *s, bool b);
void session_set_type(Session *s, SessionType t);
int session_set_display(Session *s, const char *display);
int session_set_tty(Session *s, const char *tty);
#include "string-table.h"
#include "strv.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "unit-name.h"
#include "user-util.h"
.manager = m,
.user_record = user_record_ref(ur),
.last_session_timestamp = USEC_INFINITY,
+ .gc_mode = USER_GC_BY_ANY,
};
if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0)
"# This is private data. Do not parse.\n"
"NAME=%s\n"
"STATE=%s\n" /* friendly user-facing state */
- "STOPPING=%s\n", /* low-level state */
+ "STOPPING=%s\n" /* low-level state */
+ "GC_MODE=%s\n",
u->user_record->user_name,
user_state_to_string(user_get_state(u)),
- yes_no(u->stopping));
+ yes_no(u->stopping),
+ user_gc_mode_to_string(u->gc_mode));
/* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */
if (u->runtime_path)
}
int user_load(User *u) {
- _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL;
+ _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL, *gc_mode = NULL;
int r;
assert(u);
"STOPPING", &stopping,
"REALTIME", &realtime,
"MONOTONIC", &monotonic,
- "LAST_SESSION_TIMESTAMP", &last_session_timestamp);
+ "LAST_SESSION_TIMESTAMP", &last_session_timestamp,
+ "GC_MODE", &gc_mode);
if (r == -ENOENT)
return 0;
if (r < 0)
if (last_session_timestamp)
(void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp);
+ u->gc_mode = user_gc_mode_from_string(gc_mode);
+ if (u->gc_mode < 0)
+ u->gc_mode = USER_GC_BY_PIN;
+
return 0;
}
-static void user_start_service(User *u) {
+static bool user_wants_service_manager(User *u) {
+ assert(u);
+
+ LIST_FOREACH(sessions_by_user, s, u->sessions)
+ if (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class))
+ return true;
+
+ return false;
+}
+
+void user_start_service_manager(User *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
* start the per-user slice or the systemd-runtime-dir@.service instance, as those are pulled in both by
* user@.service and the session scopes as dependencies. */
+ if (u->stopping) /* Don't try to start this if the user is going down */
+ return;
+
+ if (!user_wants_service_manager(u)) /* Only start user service manager if there's at least one session which wants it */
+ return;
+
u->service_job = mfree(u->service_job);
r = manager_start_unit(u->manager, u->service, &error, &u->service_job);
u->stopping = false;
if (!u->started)
- log_debug("Starting services for new user %s.", u->user_record->user_name);
+ log_debug("Tracking new user %s.", u->user_record->user_name);
/* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up
* systemd --user. We need to do user_save_internal() because we have not "officially" started yet. */
(void) user_update_slice(u);
/* Start user@UID.service */
- user_start_service(u);
+ user_start_service_manager(u);
if (!u->started) {
if (!dual_timestamp_is_set(&u->timestamp))
dual_timestamp k;
int ih;
+ if (!SESSION_CLASS_CAN_IDLE(s->class))
+ continue;
+
ih = session_get_idle_hint(s, &k);
if (ih < 0)
return ih;
return u->manager->user_stop_delay;
}
+static bool user_pinned_by_sessions(User *u) {
+ assert(u);
+
+ /* Returns true if at least one session exists that shall keep the user tracking alive. That
+ * generally means one session that isn't the service manager still exists. */
+
+ switch (u->gc_mode) {
+
+ case USER_GC_BY_ANY:
+ return u->sessions;
+
+ case USER_GC_BY_PIN:
+ LIST_FOREACH(sessions_by_user, i, u->sessions)
+ if (SESSION_CLASS_PIN_USER(i->class))
+ return true;
+
+ return false;
+
+ default:
+ assert_not_reached();
+ }
+}
+
bool user_may_gc(User *u, bool drop_not_started) {
int r;
if (drop_not_started && !u->started)
return true;
- if (u->sessions)
+ if (user_pinned_by_sessions(u))
return false;
if (u->last_session_timestamp != USEC_INFINITY) {
if (!u->started || u->service_job)
return USER_OPENING;
- if (u->sessions) {
- bool all_closing = true;
+ bool any = false, all_closing = true;
+ LIST_FOREACH(sessions_by_user, i, u->sessions) {
+ SessionState state;
- LIST_FOREACH(sessions_by_user, i, u->sessions) {
- SessionState state;
+ /* Ignore sessions that don't pin the user, i.e. are not supposed to have an effect on user state */
+ if (!SESSION_CLASS_PIN_USER(i->class))
+ continue;
- state = session_get_state(i);
- if (state == SESSION_ACTIVE)
- return USER_ACTIVE;
- if (state != SESSION_CLOSING)
- all_closing = false;
- }
+ state = session_get_state(i);
+ if (state == SESSION_ACTIVE)
+ return USER_ACTIVE;
+ if (state != SESSION_CLOSING)
+ all_closing = false;
- return all_closing ? USER_CLOSING : USER_ONLINE;
+ any = true;
}
+ if (any)
+ return all_closing ? USER_CLOSING : USER_ONLINE;
+
if (user_check_linger_file(u) > 0 && user_unit_active(u))
return USER_LINGERING;
/* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */
assert(s);
- return IN_SET(s->class, SESSION_USER, SESSION_GREETER) && s->started && !s->stopping;
+ return SESSION_CLASS_CAN_DISPLAY(s->class) && s->started && !s->stopping;
}
static int elect_display_compare(Session *s1, Session *s2) {
if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER))
return (s1->class != SESSION_USER) - (s2->class != SESSION_USER);
+ if ((s1->class != SESSION_USER_EARLY) != (s2->class != SESSION_USER_EARLY))
+ return (s1->class != SESSION_USER_EARLY) - (s2->class != SESSION_USER_EARLY);
+
if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID))
return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID);
assert(u);
- if (u->sessions) {
+ if (user_pinned_by_sessions(u)) {
/* There are sessions, turn off the timer */
u->last_session_timestamp = USEC_INFINITY;
u->timer_event_source = sd_event_source_unref(u->timer_event_source);
DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);
+static const char* const user_gc_mode_table[_USER_GC_MODE_MAX] = {
+ [USER_GC_BY_PIN] = "pin",
+ [USER_GC_BY_ANY] = "any",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(user_gc_mode, UserGCMode);
+
int config_parse_tmpfs_size(
const char* unit,
const char *filename,
_USER_STATE_INVALID = -EINVAL,
} UserState;
+typedef enum UserGCMode {
+ USER_GC_BY_ANY, /* any session pins this user */
+ USER_GC_BY_PIN, /* only sessions with an explicitly pinning class pin this user */
+ _USER_GC_MODE_MAX,
+ _USER_GC_MODE_INVALID = -EINVAL,
+} UserGCMode;
+
struct User {
Manager *manager;
/* Set up when the last session of the user logs out */
sd_event_source *timer_event_source;
+ UserGCMode gc_mode;
bool in_gc_queue:1;
bool started:1; /* Whenever the user being started, has been started or is being stopped again. */
bool user_may_gc(User *u, bool drop_not_started);
void user_add_to_gc_queue(User *u);
+void user_start_service_manager(User *u);
int user_start(User *u);
int user_stop(User *u, bool force);
int user_finalize(User *u);
const char* user_state_to_string(UserState s) _const_;
UserState user_state_from_string(const char *s) _pure_;
+const char* user_gc_mode_to_string(UserGCMode m) _const_;
+UserGCMode user_gc_mode_from_string(const char *s) _pure_;
+
CONFIG_PARSER_PROTOTYPE(config_parse_compat_user_tasks_max);
assert(fdname);
assert(fd >= 0);
+ if (!pid_is_valid(s->deserialized_pid)) {
+ r = log_warning_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
+ "Got leader pidfd for session '%s', but LEADER= is not set, refusing.",
+ s->id);
+ goto fail_close;
+ }
+
if (!s->leader_fd_saved)
log_warning("Got leader pidfd for session '%s', but not recorded in session state, proceeding anyway.",
s->id);
log_debug_errno(r, "Leader of session '%s' is gone while deserializing.", s->id);
else
log_warning_errno(r, "Failed to create reference to leader of session '%s': %m", s->id);
-
- close_and_notify_warn(fd, fdname);
- return r;
+ goto fail_close;
}
- assert(pid_is_valid(s->deserialized_pid));
-
if (leader_fdstore.pid != s->deserialized_pid)
log_warning("Leader from pidfd (" PID_FMT ") doesn't match with LEADER=" PID_FMT " for session '%s', proceeding anyway.",
leader_fdstore.pid, s->deserialized_pid, s->id);
return log_warning_errno(r, "Failed to attach leader pidfd for session '%s': %m", s->id);
return 0;
+
+fail_close:
+ close_and_notify_warn(fd, fdname);
+ return r;
}
static int manager_attach_session_fd_one_consume(Manager *m, const char *fdname, int fd) {
_cleanup_free_ char *id = NULL;
- dev_t devno;
+ dev_t devno = 0; /* Explicit initialization to appease gcc */
Session *s;
int r;
send_interface="org.freedesktop.login1.Manager"
send_member="ListSessions"/>
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="ListSessionsEx"/>
+
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Manager"
send_member="ListUsers"/>
send_interface="org.freedesktop.login1.Manager"
send_member="SuspendThenHibernateWithFlags"/>
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="Sleep"/>
+
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Manager"
send_member="CanPowerOff"/>
send_interface="org.freedesktop.login1.Manager"
send_member="CanSuspendThenHibernate"/>
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="CanSleep"/>
+
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Manager"
send_member="ScheduleShutdown"/>
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
- /* Make sure we don't enter a loop by talking to systemd-logind when it is actually waiting for the
- * background to finish start-up. If the service is "systemd-user" we simply set XDG_RUNTIME_DIR and
- * leave. */
-
- if (streq_ptr(service, "systemd-user")) {
- char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
-
- xsprintf(rt, "/run/user/"UID_FMT, ur->uid);
- r = configure_runtime_directory(handle, ur, rt);
- if (r != PAM_SUCCESS)
- return r;
-
- goto success;
- }
-
- /* Otherwise, we ask logind to create a session for us */
-
seat = getenv_harder(handle, "XDG_SEAT", NULL);
cvtnr = getenv_harder(handle, "XDG_VTNR", NULL);
type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
- if (tty && strchr(tty, ':')) {
+ if (streq_ptr(service, "systemd-user")) {
+ /* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to
+ * 'manager' if not set, simply for robustness reasons. */
+ type = "unspecified";
+ class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ?
+ "manager-early" : "manager";
+ tty = NULL;
+
+ } else if (tty && strchr(tty, ':')) {
/* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
* and don't pretend that an X display was a tty. */
if (isempty(display))
!isempty(tty) ? "tty" : "unspecified";
if (isempty(class))
- class = streq(type, "unspecified") ? "background" : "user";
+ class = streq(type, "unspecified") ? "background" :
+ ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) &&
+ streq(type, "tty")) ? "user-early" : "user");
remote = !isempty(remote_host) && !is_localhost(remote_host);
return hashmap_isempty(m->machines);
}
-static int manager_run(Manager *m) {
- assert(m);
-
- return bus_event_loop_with_idle(
- m->event,
- m->bus,
- "org.freedesktop.machine1",
- DEFAULT_EXIT_USEC,
- check_idle, m);
-}
-
static int run(int argc, char *argv[]) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
if (r < 0)
return log_error_errno(r, "Failed to fully start up daemon: %m");
- log_debug("systemd-machined running as pid "PID_FMT, getpid_cached());
r = sd_notify(false, NOTIFY_READY);
if (r < 0)
log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
- r = manager_run(m);
+ r = bus_event_loop_with_idle(
+ m->event,
+ m->bus,
+ "org.freedesktop.machine1",
+ DEFAULT_EXIT_USEC,
+ check_idle, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run main loop: %m");
- (void) sd_notify(false, NOTIFY_STOPPING);
- log_debug("systemd-machined stopped as pid "PID_FMT, getpid_cached());
- return r;
+ return 0;
}
DEFINE_MAIN_FUNCTION(run);
#include <getopt.h>
#include "build.h"
+#include "copy.h"
+#include "creds-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "generator.h"
#include "network-generator.h"
#include "path-util.h"
#include "proc-cmdline.h"
+#include "recurse-dir.h"
#define NETWORKD_UNIT_DIRECTORY "/run/systemd/network"
assert(network);
- r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
+ r = generator_open_unit_file_full(
+ dest_dir,
+ /* source= */ NULL,
+ /* name= */ NULL,
+ &f,
+ /* ret_final_path= */ NULL,
+ &temp_path);
if (r < 0)
return r;
assert(netdev);
- r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
+ r = generator_open_unit_file_full(
+ dest_dir,
+ /* source= */ NULL,
+ /* name= */ NULL,
+ &f,
+ /* ret_final_path= */ NULL,
+ &temp_path);
if (r < 0)
return r;
assert(link);
- r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path);
+ r = generator_open_unit_file_full(
+ dest_dir,
+ /* source= */ NULL,
+ /* name= */ NULL,
+ &f,
+ /* ret_final_path= */ NULL,
+ &temp_path);
if (r < 0)
return r;
return r;
}
+static int pick_up_credentials(void) {
+ _cleanup_close_ int credential_dir_fd = -EBADF;
+ int r, ret = 0;
+
+ credential_dir_fd = open_credentials_dir();
+ if (IN_SET(credential_dir_fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */
+ return 0;
+ if (credential_dir_fd < 0)
+ return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m");
+
+ _cleanup_free_ DirectoryEntries *des = NULL;
+ r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate credentials: %m");
+
+ FOREACH_ARRAY(i, des->entries, des->n_entries) {
+ static const struct {
+ const char *credential_prefix;
+ const char *filename_suffix;
+ } table[] = {
+ { "network.link.", ".link" },
+ { "network.netdev.", ".netdev" },
+ { "network.network.", ".network" },
+ };
+
+ _cleanup_free_ char *fn = NULL;
+ struct dirent *de = *i;
+
+ if (de->d_type != DT_REG)
+ continue;
+
+ FOREACH_ARRAY(t, table, ELEMENTSOF(table)) {
+ const char *e = startswith(de->d_name, t->credential_prefix);
+
+ if (e) {
+ fn = strjoin(e, t->filename_suffix);
+ if (!fn)
+ return log_oom();
+
+ break;
+ }
+ }
+
+ if (!fn)
+ continue;
+
+ if (!filename_is_valid(fn)) {
+ log_warning("Passed credential '%s' would result in invalid filename '%s', ignoring.", de->d_name, fn);
+ continue;
+ }
+
+ _cleanup_free_ char *output = path_join(NETWORKD_UNIT_DIRECTORY, fn);
+ if (!output)
+ return log_oom();
+
+ r = copy_file_at(
+ credential_dir_fd, de->d_name,
+ AT_FDCWD, output,
+ /* open_flags= */ 0,
+ 0644,
+ /* flags= */ 0);
+ if (r < 0)
+ RET_GATHER(ret, log_warning_errno(r, "Failed to copy credential %s → file %s: %m", de->d_name, output));
+ else
+ log_info("Installed %s from credential.", output);
+ }
+
+ return ret;
+}
+
static int help(void) {
printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n"
" -h --help Show this help\n"
static int run(int argc, char *argv[]) {
_cleanup_(context_clear) Context context = {};
- int r;
+ int r, ret = 0;
log_setup();
if (r < 0)
return log_warning_errno(r, "Failed to merge multiple command line options: %m");
- return context_save(&context);
+ RET_GATHER(ret, context_save(&context));
+ RET_GATHER(ret, pick_up_credentials());
+
+ return ret;
}
DEFINE_MAIN_FUNCTION(run);
'networkd-nexthop.c',
'networkd-queue.c',
'networkd-radv.c',
- 'networkd-route-util.c',
'networkd-route.c',
+ 'networkd-route-metric.c',
+ 'networkd-route-nexthop.c',
+ 'networkd-route-util.c',
'networkd-routing-policy-rule.c',
'networkd-setlink.c',
'networkd-speed-meter.c',
dest = a ? &a->sa : &b->sa;
- r = unhexmem_full(rvalue, strlen(rvalue), true, &p, &l);
+ r = unhexmem_full(rvalue, SIZE_MAX, /* secure = */ true, &p, &l);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse key. Ignoring assignment: %m");
return 0;
if (r < 0)
return log_oom();
- r = unhexmem(rvalue, strlen(rvalue), &p, &l);
+ r = unhexmem(rvalue, &p, &l);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
VxLan *v = ASSERT_PTR(userdata);
int r;
- r = parse_ip_port_range(rvalue, &v->port_range.low, &v->port_range.high);
+ r = parse_ip_port_range(rvalue, &v->port_range.low, &v->port_range.high, /* allow_zero = */ false);
if (r < 0)
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse VXLAN port range '%s'. Port should be greater than 0 and less than 65535.", rvalue);
#include "sd-resolve.h"
#include "alloc-util.h"
+#include "creds-util.h"
#include "dns-domain.h"
#include "event-util.h"
#include "fd-util.h"
#include "networkd-util.h"
#include "parse-helpers.h"
#include "parse-util.h"
+#include "path-util.h"
#include "random-util.h"
#include "resolve-private.h"
#include "string-util.h"
const char *lvalue) {
_cleanup_(erase_and_freep) void *key = NULL;
+ _cleanup_(erase_and_freep) char *cred = NULL;
+ const char *cred_name;
size_t len;
int r;
return 0;
}
- if (!streq(lvalue, "PublicKey"))
+ cred_name = startswith(rvalue, "@");
+ if (cred_name) {
+ r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to read credential for wireguard key (%s=), ignoring assignment: %m",
+ lvalue);
+ return 0;
+ }
+
+ } else if (!streq(lvalue, "PublicKey"))
(void) warn_file_is_world_accessible(filename, NULL, unit, line);
- r = unbase64mem_full(rvalue, strlen(rvalue), true, &key, &len);
+ r = unbase64mem_full(cred ?: rvalue, SIZE_MAX, /* secure = */ true, &key, &len);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
void *data,
void *userdata) {
- assert(filename);
- assert(rvalue);
- assert(userdata);
-
Wireguard *w = WIREGUARD(userdata);
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
- _cleanup_free_ char *host = NULL;
- union in_addr_union addr;
- const char *p;
+ _cleanup_free_ char *cred = NULL;
+ const char *cred_name, *endpoint;
uint16_t port;
- int family, r;
+ int r;
+
+ assert(filename);
+ assert(rvalue);
r = wireguard_peer_new_static(w, filename, section_line, &peer);
if (r < 0)
return log_oom();
- r = in_addr_port_ifindex_name_from_string_auto(rvalue, &family, &addr, &port, NULL, NULL);
+ cred_name = startswith(rvalue, "@");
+ if (cred_name) {
+ r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to read credential for wireguard endpoint, ignoring assignment: %m");
+ return 0;
+ }
+
+ endpoint = strstrip(cred);
+ } else
+ endpoint = rvalue;
+
+ union in_addr_union addr;
+ int family;
+
+ r = in_addr_port_ifindex_name_from_string_auto(endpoint, &family, &addr, &port, NULL, NULL);
if (r >= 0) {
if (family == AF_INET)
peer->endpoint.in = (struct sockaddr_in) {
return 0;
}
- p = strrchr(rvalue, ':');
+ _cleanup_free_ char *host = NULL;
+ const char *p;
+
+ p = strrchr(endpoint, ':');
if (!p) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unable to find port of endpoint, ignoring assignment: %s",
- rvalue);
+ rvalue); /* We log the original assignment instead of resolved credential here,
+ as the latter might be previously encrypted and we'd expose them in
+ unprotected logs otherwise. */
return 0;
}
- host = strndup(rvalue, p - rvalue);
+ host = strndup(endpoint, p - endpoint);
if (!host)
return log_oom();
+ p++;
if (!dns_name_is_valid(host)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
return 0;
}
- p++;
r = parse_ip_port(p, &port);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
return 0;
}
+static int wireguard_read_default_key_cred(NetDev *netdev, const char *filename) {
+ Wireguard *w = WIREGUARD(netdev);
+ _cleanup_free_ char *config_name = NULL;
+ int r;
+
+ assert(filename);
+
+ r = path_extract_filename(filename, &config_name);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "%s: Failed to extract config name, ignoring network device: %m",
+ filename);
+
+ char *p = endswith(config_name, ".netdev");
+ if (!p)
+ /* Fuzzer run? Then we just ignore this device. */
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: Invalid netdev config name, refusing default key lookup.",
+ filename);
+ *p = '\0';
+
+ _cleanup_(erase_and_freep) char *cred = NULL;
+
+ r = read_credential(strjoina("network.wireguard.private.", config_name), (void**) &cred, /* ret_size = */ NULL);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "%s: No private key specified and default key isn't available, "
+ "ignoring network device: %m",
+ filename);
+
+ _cleanup_(erase_and_freep) void *key = NULL;
+ size_t len;
+
+ r = unbase64mem_full(cred, SIZE_MAX, /* secure = */ true, &key, &len);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "%s: No private key specified and default key cannot be parsed, "
+ "ignoring network device: %m",
+ filename);
+ if (len != WG_KEY_LEN)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: No private key specified and default key is invalid. "
+ "Ignoring network device.",
+ filename);
+
+ memcpy(w->private_key, key, WG_KEY_LEN);
+ return 0;
+}
+
static int wireguard_verify(NetDev *netdev, const char *filename) {
Wireguard *w = WIREGUARD(netdev);
int r;
"Failed to read private key from %s. Ignoring network device.",
w->private_key_file);
- if (eqzero(w->private_key))
- return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
- "%s: Missing PrivateKey= or PrivateKeyFile=, "
- "Ignoring network device.", filename);
+ if (eqzero(w->private_key)) {
+ r = wireguard_read_default_key_cred(netdev, filename);
+ if (r < 0)
+ return r;
+ }
LIST_FOREACH(peers, peer, w->peers) {
if (wireguard_peer_verify(peer) < 0) {
if (r < 0)
return log_oom();
+ /* For route_section_verify() below. */
+ r = config_section_new(peer->section->filename, peer->section->line, &route->section);
+ if (r < 0)
+ return log_oom();
+
+ route->source = NETWORK_CONFIG_SOURCE_STATIC;
route->family = ipmask->family;
route->dst = ipmask->ip;
route->dst_prefixlen = ipmask->cidr;
- route->scope = RT_SCOPE_UNIVERSE;
route->protocol = RTPROT_STATIC;
+ route->protocol_set = true;
route->table = peer->route_table_set ? peer->route_table : w->route_table;
+ route->table_set = true;
route->priority = peer->route_priority_set ? peer->route_priority : w->route_priority;
- if (route->priority == 0 && route->family == AF_INET6)
- route->priority = IP6_RT_PRIO_USER;
- route->source = NETWORK_CONFIG_SOURCE_STATIC;
+ route->priority_set = true;
- r = set_ensure_consume(&w->routes, &route_hash_ops, TAKE_PTR(route));
+ if (route_section_verify(route) < 0)
+ continue;
+
+ r = set_ensure_put(&w->routes, &route_hash_ops, route);
if (r < 0)
return log_oom();
+ if (r == 0)
+ continue;
+
+ route->wireguard = w;
+ TAKE_PTR(route);
}
}
{},
};
- r = json_dispatch(reply, dispatch_table, JSON_LOG, &id);
+ r = json_dispatch(reply, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &id);
if (r < 0)
return r;
*ret_all = address_state_from_scope(MIN(ipv4_scope, ipv6_scope));
}
+static void address_hash_func(const Address *a, struct siphash *state);
+static int address_compare_func(const Address *a1, const Address *a2);
+static void address_detach(Address *address);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+ address_hash_ops_detach,
+ Address,
+ address_hash_func,
+ address_compare_func,
+ address_detach);
+
+DEFINE_HASH_OPS(
+ address_hash_ops,
+ Address,
+ address_hash_func,
+ address_compare_func);
+
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ address_section_hash_ops,
+ ConfigSection,
+ config_section_hash_func,
+ config_section_compare_func,
+ Address,
+ address_detach);
+
int address_new(Address **ret) {
- _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(address_unrefp) Address *address = NULL;
address = new(Address, 1);
if (!address)
return -ENOMEM;
*address = (Address) {
+ .n_ref = 1,
.family = AF_UNSPEC,
.scope = RT_SCOPE_UNIVERSE,
.lifetime_valid_usec = USEC_INFINITY,
int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret) {
_cleanup_(config_section_freep) ConfigSection *n = NULL;
- _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(address_unrefp) Address *address = NULL;
int r;
assert(network);
/* This will be adjusted in address_section_verify(). */
address->duplicate_address_detection = _ADDRESS_FAMILY_INVALID;
- r = ordered_hashmap_ensure_put(&network->addresses_by_section, &config_section_hash_ops, address->section, address);
+ r = ordered_hashmap_ensure_put(&network->addresses_by_section, &address_section_hash_ops, address->section, address);
if (r < 0)
return r;
return 0;
}
-Address *address_free(Address *address) {
- if (!address)
- return NULL;
+static Address* address_detach_impl(Address *address) {
+ assert(address);
+ assert(!address->link || !address->network);
if (address->network) {
assert(address->section);
if (address->network->dhcp_server_address == address)
address->network->dhcp_server_address = NULL;
+
+ address->network = NULL;
+ return address;
}
- if (address->link)
+ if (address->link) {
set_remove(address->link->addresses, address);
+ address->link = NULL;
+ return address;
+ }
+
+ return NULL;
+}
+
+static void address_detach(Address *address) {
+ assert(address);
+
+ address_unref(address_detach_impl(address));
+}
+
+static Address* address_free(Address *address) {
+ if (!address)
+ return NULL;
+
+ address_detach_impl(address);
+
config_section_free(address->section);
free(address->label);
free(address->netlabel);
return mfree(address);
}
+DEFINE_TRIVIAL_REF_UNREF_FUNC(Address, address, address_free);
+
static bool address_lifetime_is_valid(const Address *a) {
assert(a);
}
}
-DEFINE_HASH_OPS(
- address_hash_ops,
- Address,
- address_hash_func,
- address_compare_func);
-
-DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
- address_hash_ops_free,
- Address,
- address_hash_func,
- address_compare_func,
- address_free);
-
static bool address_can_update(const Address *la, const Address *na) {
assert(la);
assert(la->link);
}
int address_dup(const Address *src, Address **ret) {
- _cleanup_(address_freep) Address *dest = NULL;
+ _cleanup_(address_unrefp) Address *dest = NULL;
int r;
assert(src);
if (!dest)
return -ENOMEM;
- /* clear all pointers */
+ /* clear the reference counter and all pointers */
+ dest->n_ref = 1;
dest->network = NULL;
dest->section = NULL;
dest->link = NULL;
}
}
-static int address_add(Link *link, Address *address) {
+static int address_attach(Link *link, Address *address) {
int r;
assert(link);
assert(address);
+ assert(!address->link);
- r = set_ensure_put(&link->addresses, &address_hash_ops_free, address);
+ r = set_ensure_put(&link->addresses, &address_hash_ops_detach, address);
if (r < 0)
return r;
if (r == 0)
return -EEXIST;
address->link = link;
+ address_ref(address);
return 0;
}
ipv4acd_detach(link, address);
- address_free(address);
+ address_detach(address);
link_update_operstate(link, /* also_update_master = */ true);
link_check_ready(link);
* arbitrary prefixlen will be returned. */
if (family == AF_INET6 || prefixlen != 0) {
- _cleanup_(address_freep) Address *tmp = NULL;
+ _cleanup_(address_unrefp) Address *tmp = NULL;
/* In this case, we can use address_get(). */
return 0;
}
-static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) {
int r;
assert(m);
- assert(link);
+ assert(rreq);
- if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ Link *link = ASSERT_PTR(rreq->link);
+ Address *address = ASSERT_PTR(rreq->userdata);
+
+ if (link->state == LINK_STATE_LINGER)
return 0;
r = sd_netlink_message_get_errno(m);
- if (r < 0 && r != -EADDRNOTAVAIL)
- log_link_message_warning_errno(link, m, r, "Could not drop address");
+ if (r < 0) {
+ log_link_message_full_errno(link, m,
+ (r == -EADDRNOTAVAIL || !address->link) ? LOG_DEBUG : LOG_WARNING,
+ r, "Could not drop address");
+
+ if (address->link) {
+ /* If the address cannot be removed, then assume the address is already removed. */
+ log_address_debug(address, "Forgetting", link);
+
+ Request *req;
+ if (address_get_request(link, address, &req) >= 0)
+ address_enter_removed(req->userdata);
+
+ (void) address_drop(address);
+ }
+ }
return 1;
}
if (r < 0)
return log_link_warning_errno(link, r, "Could not set netlink attributes: %m");
- r = netlink_call_async(link->manager->rtnl, NULL, m,
- address_remove_handler,
- link_netlink_destroy_callback, link);
+ r = link_remove_request_add(link, address, address, link->manager->rtnl, m, address_remove_handler);
if (r < 0)
- return log_link_warning_errno(link, r, "Could not send rtnetlink message: %m");
-
- link_ref(link);
+ return log_link_warning_errno(link, r, "Could not queue rtnetlink message: %m");
address_enter_removing(address);
* notification about the request, then explicitly remove the address. */
if (address_get_request(link, address, &req) >= 0) {
waiting = req->waiting_reply;
- request_detach(link->manager, req);
+ request_detach(req);
address_cancel_requesting(address);
}
return r;
for (sd_netlink_message *addr = reply; addr; addr = sd_netlink_message_next(addr)) {
- _cleanup_(address_freep) Address *a = NULL;
+ _cleanup_(address_unrefp) Address *a = NULL;
unsigned char flags, prefixlen;
struct in6_addr address;
int ifindex;
}
static int address_acquire(Link *link, const Address *original, Address **ret) {
- _cleanup_(address_freep) Address *na = NULL;
+ _cleanup_(address_unrefp) Address *na = NULL;
union in_addr_union in_addr;
int r;
address_netlink_handler_t netlink_handler,
Request **ret) {
- _cleanup_(address_freep) Address *tmp = NULL;
+ _cleanup_(address_unrefp) Address *tmp = NULL;
Address *existing = NULL;
int r;
log_address_debug(tmp, "Requesting", link);
r = link_queue_request_safe(link, REQUEST_TYPE_ADDRESS,
tmp,
- address_free,
+ address_unref,
address_hash_func,
address_compare_func,
address_process_request,
}
int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
- _cleanup_(address_freep) Address *tmp = NULL;
+ _cleanup_(address_unrefp) Address *tmp = NULL;
struct ifa_cacheinfo cinfo;
Link *link;
uint16_t type;
if (!address) {
/* If we did not know the address, then save it. */
- r = address_add(link, tmp);
+ r = address_attach(link, tmp);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to save received address %s, ignoring: %m",
IN_ADDR_PREFIX_TO_STRING(tmp->family, &tmp->in_addr, tmp->prefixlen));
return 0;
}
- address = TAKE_PTR(tmp);
+ address = tmp;
is_new = true;
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
union in_addr_union u;
int r;
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
union in_addr_union buffer;
unsigned char prefixlen;
int r, f;
if (streq(section, "Network")) {
if (isempty(rvalue)) {
/* If an empty string specified in [Network] section, clear previously assigned addresses. */
- network->addresses_by_section = ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free);
+ network->addresses_by_section = ordered_hashmap_free(network->addresses_by_section);
return 0;
}
void *data,
void *userdata) {
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
Network *network = userdata;
int r;
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
usec_t k;
int r;
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
if (address_section_verify(address) < 0) {
/* Drop invalid [Address] sections or Address= settings in [Network].
- * Note that address_free() will drop the address from addresses_by_section. */
- address_free(address);
+ * Note that address_detach() will drop the address from addresses_by_section. */
+ address_detach(address);
continue;
}
IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen),
address->section->line,
dup->section->line, dup->section->line);
- /* address_free() will drop the address from addresses_by_section. */
- address_free(dup);
+
+ /* address_detach() will drop the address from addresses_by_section. */
+ address_detach(dup);
}
- /* Use address_hash_ops, instead of address_hash_ops_free. Otherwise, the Address objects
- * will be freed. */
+ /* Use address_hash_ops, instead of address_hash_ops_detach. Otherwise, the Address objects
+ * will be detached. */
r = set_ensure_put(&addresses, &address_hash_ops, address);
if (r < 0)
return log_oom();
void *userdata) {
Network *network = userdata;
- _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ _cleanup_(address_unref_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
NetworkConfigState state;
union in_addr_union provider; /* DHCP server or router address */
+ unsigned n_ref;
+
int family;
unsigned char prefixlen;
unsigned char scope;
extern const struct hash_ops address_hash_ops;
+Address* address_ref(Address *address);
+Address* address_unref(Address *address);
+
int address_new(Address **ret);
int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret);
-Address* address_free(Address *address);
int address_get(Link *link, const Address *in, Address **ret);
int address_get_harder(Link *link, const Address *in, Address **ret);
int address_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg);
bool address_is_ready(const Address *a);
bool link_check_addresses_ready(Link *link, NetworkConfigSource source);
-DEFINE_SECTION_CLEANUP_FUNCTIONS(Address, address_free);
+DEFINE_SECTION_CLEANUP_FUNCTIONS(Address, address_unref);
int link_drop_managed_addresses(Link *link);
int link_drop_foreign_addresses(Link *link);
assert(link);
- r = route_configure_handler_internal(rtnl, m, link, "Failed to add prefix route for DHCP delegated subnet prefix");
+ r = route_configure_handler_internal(rtnl, m, link, route, "Failed to add prefix route for DHCP delegated subnet prefix");
if (r <= 0)
return r;
return log_link_warning_errno(link, r, "Failed to generate addresses for acquired DHCP delegated prefix: %m");
SET_FOREACH(a, addresses) {
- _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(address_unrefp) Address *address = NULL;
Address *existing;
r = address_new(&address);
assert(link);
- r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv4 delegated prefix");
+ r = route_configure_handler_internal(rtnl, m, link, route, "Failed to set unreachable route for DHCPv4 delegated prefix");
if (r <= 0)
return r;
assert(link);
- r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv6 delegated prefix");
+ r = route_configure_handler_internal(rtnl, m, link, route, "Failed to set unreachable route for DHCPv6 delegated prefix");
if (r <= 0)
return r;
route->source = NETWORK_CONFIG_SOURCE_DHCP_PD;
route->family = AF_INET6;
- route->gw_family = AF_INET6;
- route->gw.in6.s6_addr32[3] = br_address->s_addr;
+ route->nexthop.family = AF_INET6;
+ route->nexthop.gw.in6.s6_addr32[3] = br_address->s_addr;
route->scope = RT_SCOPE_UNIVERSE;
route->protocol = RTPROT_DHCP;
route->priority = IP6_RT_PRIO_USER;
}
} else {
- _cleanup_(address_freep) Address *a = NULL;
+ _cleanup_(address_unrefp) Address *a = NULL;
Address *existing;
unsigned line;
assert(m);
assert(link);
- r = sd_netlink_message_get_errno(m);
- if (r < 0 && r != -EEXIST) {
- log_link_message_warning_errno(link, m, r, "Could not set DHCPv4 route");
- link_enter_failed(link);
- return 1;
- }
+ r = route_configure_handler_internal(rtnl, m, link, route, "Could not set DHCPv4 route");
+ if (r <= 0)
+ return r;
r = dhcp4_check_ready(link);
if (r < 0)
route->priority = link->network->dhcp_route_metric;
if (!route->table_set)
route->table = link_get_dhcp4_route_table(link);
- if (route->mtu == 0)
- route->mtu = link->network->dhcp_route_mtu;
- if (route->quickack < 0)
- route->quickack = link->network->dhcp_quickack;
- if (route->initcwnd == 0)
- route->initcwnd = link->network->dhcp_initial_congestion_window;
- if (route->initrwnd == 0)
- route->initrwnd = link->network->dhcp_advertised_receive_window;
+ r = route_metric_set(&route->metric, RTAX_MTU, link->network->dhcp_route_mtu);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_INITCWND, link->network->dhcp_initial_congestion_window);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_INITRWND, link->network->dhcp_advertised_receive_window);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->dhcp_quickack);
+ if (r < 0)
+ return r;
if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */
link->dhcp4_configured = false;
IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw));
route->scope = RT_SCOPE_HOST;
- route->gw_family = AF_UNSPEC;
- route->gw = IN_ADDR_NULL;
+ route->nexthop.family = AF_UNSPEC;
+ route->nexthop.gw = IN_ADDR_NULL;
route->prefsrc = IN_ADDR_NULL;
} else if (in4_addr_equal(&route->dst.in, &address)) {
IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw));
route->scope = RT_SCOPE_HOST;
- route->gw_family = AF_UNSPEC;
- route->gw = IN_ADDR_NULL;
+ route->nexthop.family = AF_UNSPEC;
+ route->nexthop.gw = IN_ADDR_NULL;
route->prefsrc.in = address;
} else if (in4_addr_is_null(gw)) {
}
route->scope = RT_SCOPE_LINK;
- route->gw_family = AF_UNSPEC;
- route->gw = IN_ADDR_NULL;
+ route->nexthop.family = AF_UNSPEC;
+ route->nexthop.gw = IN_ADDR_NULL;
route->prefsrc.in = address;
} else {
return r;
route->scope = RT_SCOPE_UNIVERSE;
- route->gw_family = AF_INET;
- route->gw.in = *gw;
+ route->nexthop.family = AF_INET;
+ route->nexthop.gw.in = *gw;
route->prefsrc.in = address;
}
return r;
/* Next, add a default gateway. */
- route->gw_family = AF_INET;
- route->gw.in = router;
+ route->nexthop.family = AF_INET;
+ route->nexthop.gw.in = router;
route->prefsrc.in = address;
return dhcp4_request_route(TAKE_PTR(route), link);
if (!rt->gateway_from_dhcp_or_ra)
continue;
- if (rt->gw_family != AF_INET)
+ if (rt->nexthop.family != AF_INET)
continue;
assert(rt->family == AF_INET);
if (r < 0)
return r;
- route->gw.in = gw;
+ route->nexthop.gw.in = gw;
r = dhcp4_request_route(TAKE_PTR(route), link);
if (r < 0)
}
static int dhcp4_request_address(Link *link, bool announce) {
- _cleanup_(address_freep) Address *addr = NULL;
+ _cleanup_(address_unrefp) Address *addr = NULL;
struct in_addr address, server;
uint8_t prefixlen;
Address *existing;
usec_t lifetime_preferred_usec,
usec_t lifetime_valid_usec) {
- _cleanup_(address_freep) Address *addr = NULL;
+ _cleanup_(address_unrefp) Address *addr = NULL;
Address *existing;
int r;
}
static int address_new_from_ipv4ll(Link *link, Address **ret) {
- _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(address_unrefp) Address *address = NULL;
struct in_addr addr;
int r;
}
static int ipv4ll_address_lost(Link *link) {
- _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(address_unrefp) Address *address = NULL;
int r;
assert(link);
}
static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) {
- _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(address_unrefp) Address *address = NULL;
int r;
assert(ll);
JSON_BUILD_PAIR_INTEGER("Family", route->family),
JSON_BUILD_PAIR_IN_ADDR("Destination", &route->dst, route->family),
JSON_BUILD_PAIR_UNSIGNED("DestinationPrefixLength", route->dst_prefixlen),
- JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->gw, route->gw_family),
+ JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->nexthop.gw, route->nexthop.family),
JSON_BUILD_PAIR_CONDITION(route->src_prefixlen > 0,
"Source", JSON_BUILD_IN_ADDR(&route->src, route->family)),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("SourcePrefixLength", route->src_prefixlen),
JSON_BUILD_PAIR_UNSIGNED("Priority", route->priority),
JSON_BUILD_PAIR_UNSIGNED("Table", route->table),
JSON_BUILD_PAIR_STRING("TableString", table),
- JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route->mtu),
+ JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route_metric_get(&route->metric, RTAX_MTU)),
JSON_BUILD_PAIR_UNSIGNED("Preference", route->pref),
JSON_BUILD_PAIR_UNSIGNED("Flags", route->flags),
JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)),
#include "udev-util.h"
#include "vrf.h"
+void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret) {
+ assert(link);
+ assert(ret);
+
+ if (link->network && operational_state_range_is_valid(&link->network->required_operstate_for_online))
+ *ret = link->network->required_operstate_for_online;
+ else if (link->iftype == ARPHRD_CAN)
+ *ret = (const LinkOperationalStateRange) {
+ .min = LINK_OPERSTATE_CARRIER,
+ .max = LINK_OPERSTATE_CARRIER,
+ };
+ else
+ *ret = LINK_OPERSTATE_RANGE_DEFAULT;
+}
+
bool link_ipv6_enabled(Link *link) {
assert(link);
}
void link_enter_failed(Link *link) {
+ int r;
+
assert(link);
if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
link_set_state(link, LINK_STATE_FAILED);
- (void) link_stop_engines(link, false);
+ if (!ratelimit_below(&link->automatic_reconfigure_ratelimit)) {
+ log_link_warning(link, "The interface entered the failed state frequently, refusing to reconfigure it automatically.");
+ goto stop;
+ }
+
+ log_link_info(link, "Trying to reconfigure the interface.");
+ r = link_reconfigure(link, /* force = */ true);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to reconfigure interface: %m");
+ goto stop;
+ }
+
+ return;
+
+stop:
+ (void) link_stop_engines(link, /* may_keep_dhcp = */ false);
}
void link_check_ready(Link *link) {
if (!link->activated)
return (void) log_link_debug(link, "%s(): link is not activated.", __func__);
- if (link->iftype == ARPHRD_CAN) {
+ if (link->iftype == ARPHRD_CAN)
/* let's shortcut things for CAN which doesn't need most of checks below. */
- link_set_state(link, LINK_STATE_CONFIGURED);
- return;
- }
+ goto ready;
if (!link->stacked_netdevs_created)
return (void) log_link_debug(link, "%s(): stacked netdevs are not created.", __func__);
link_unref(set_remove(master->slaves, link));
}
-static void link_drop_requests(Link *link) {
+static int link_drop_requests(Link *link) {
Request *req;
+ int ret = 0;
assert(link);
assert(link->manager);
- ORDERED_SET_FOREACH(req, link->manager->request_queue)
- if (req->link == link)
- request_detach(link->manager, req);
+ ORDERED_SET_FOREACH(req, link->manager->request_queue) {
+ if (req->link != link)
+ continue;
+
+ /* If the request is already called, but its reply is not received, then we need to
+ * drop the configuration (e.g. address) here. Note, if the configuration is known,
+ * it will be handled later by link_drop_foreign_addresses() or so. */
+ if (req->waiting_reply && link->state != LINK_STATE_LINGER)
+ switch (req->type) {
+ case REQUEST_TYPE_ADDRESS: {
+ Address *address = ASSERT_PTR(req->userdata);
+
+ if (address_get(link, address, NULL) < 0)
+ RET_GATHER(ret, address_remove(address, link));
+ break;
+ }
+ case REQUEST_TYPE_NEIGHBOR: {
+ Neighbor *neighbor = ASSERT_PTR(req->userdata);
+
+ if (neighbor_get(link, neighbor, NULL) < 0)
+ RET_GATHER(ret, neighbor_remove(neighbor, link));
+ break;
+ }
+ case REQUEST_TYPE_NEXTHOP: {
+ NextHop *nexthop = ASSERT_PTR(req->userdata);
+
+ if (nexthop_get_by_id(link->manager, nexthop->id, NULL) < 0)
+ RET_GATHER(ret, nexthop_remove(nexthop, link->manager));
+ break;
+ }
+ default:
+ ;
+ }
+
+ request_detach(req);
+ }
+
+ return ret;
}
static Link *link_drop(Link *link) {
/* Drop all references from other links and manager. Note that async netlink calls may have
* references to the link, and they will be dropped when we receive replies. */
- link_drop_requests(link);
+ (void) link_drop_requests(link);
link_free_bound_to_list(link);
link_free_bound_by_list(link);
if (r < 0)
return r;
- link_drop_requests(link);
+ r = link_drop_requests(link);
+ if (r < 0)
+ return r;
if (network && !force && network->keep_configuration != KEEP_CONFIGURATION_YES)
/* When a new/updated .network file is assigned, first make all configs (addresses,
usec = 5 * USEC_PER_SEC;
else
- /* Otherwise, use the currently set value. */
+ /* Otherwise, use the implied default value. */
usec = link->network->ignore_carrier_loss_usec;
if (usec == USEC_INFINITY)
else
operstate = LINK_OPERSTATE_ENSLAVED;
+ LinkOperationalStateRange req;
+ link_required_operstate_for_online(link, &req);
+
/* Only determine online state for managed links with RequiredForOnline=yes */
if (!link->network || !link->network->required_for_online)
online_state = _LINK_ONLINE_STATE_INVALID;
- else if (operstate < link->network->required_operstate_for_online.min ||
- operstate > link->network->required_operstate_for_online.max)
+
+ else if (!operational_state_is_in_range(operstate, &req))
online_state = LINK_ONLINE_STATE_OFFLINE;
+
else {
AddressFamily required_family = link->network->required_family_for_online;
bool needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4;
* to offline in the blocks below. */
online_state = LINK_ONLINE_STATE_ONLINE;
- if (link->network->required_operstate_for_online.min >= LINK_OPERSTATE_DEGRADED) {
+ if (req.min >= LINK_OPERSTATE_DEGRADED) {
if (needs_ipv4 && ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED)
online_state = LINK_ONLINE_STATE_OFFLINE;
if (needs_ipv6 && ipv6_address_state < LINK_ADDRESS_STATE_DEGRADED)
online_state = LINK_ONLINE_STATE_OFFLINE;
}
- if (link->network->required_operstate_for_online.min >= LINK_OPERSTATE_ROUTABLE) {
+ if (req.min >= LINK_OPERSTATE_ROUTABLE) {
if (needs_ipv4 && ipv4_address_state < LINK_ADDRESS_STATE_ROUTABLE)
online_state = LINK_ONLINE_STATE_OFFLINE;
if (needs_ipv6 && ipv6_address_state < LINK_ADDRESS_STATE_ROUTABLE)
if (master_ifindex == link->ifindex)
master_ifindex = 0;
- if (master_ifindex == link->master_ifindex)
- return 0;
-
- if (link->master_ifindex == 0)
- log_link_debug(link, "Attached to master interface: %i", master_ifindex);
- else if (master_ifindex == 0)
- log_link_debug(link, "Detached from master interface: %i", link->master_ifindex);
- else
- log_link_debug(link, "Master interface changed: %i %s %i", link->master_ifindex,
- special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), master_ifindex);
-
- link_drop_from_master(link);
+ if (master_ifindex != link->master_ifindex) {
+ if (link->master_ifindex == 0)
+ log_link_debug(link, "Attached to master interface: %i", master_ifindex);
+ else if (master_ifindex == 0)
+ log_link_debug(link, "Detached from master interface: %i", link->master_ifindex);
+ else
+ log_link_debug(link, "Master interface changed: %i %s %i", link->master_ifindex,
+ special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), master_ifindex);
- link->master_ifindex = master_ifindex;
+ link_drop_from_master(link);
+ link->master_ifindex = master_ifindex;
+ }
r = link_append_to_master(link);
if (r < 0)
.n_ref = 1,
.state = LINK_STATE_PENDING,
.online_state = _LINK_ONLINE_STATE_INVALID,
+ .automatic_reconfigure_ratelimit = (const RateLimit) { .interval = 10 * USEC_PER_SEC, .burst = 5 },
.ifindex = ifindex,
.iftype = iftype,
.ifname = TAKE_PTR(ifname),
#include "networkd-ipv6ll.h"
#include "networkd-util.h"
#include "ordered-set.h"
+#include "ratelimit.h"
#include "resolve-util.h"
#include "set.h"
LinkAddressState ipv4_address_state;
LinkAddressState ipv6_address_state;
LinkOnlineState online_state;
+ RateLimit automatic_reconfigure_ratelimit;
unsigned static_address_messages;
unsigned static_address_label_messages;
int link_flags_to_string_alloc(uint32_t flags, char **ret);
const char *kernel_operstate_to_string(int t) _const_;
+
+void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret);
return sd_bus_message_append(reply, "t", id);
}
+static int property_get_namespace_nsid(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ uint32_t nsid = UINT32_MAX;
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ /* Returns our own "nsid", which is another ID for the network namespace, different from the inode
+ * number. */
+
+ r = netns_get_nsid(/* netnsfd= */ -EBADF, &nsid);
+ if (r < 0)
+ log_warning_errno(r, "Failed to query network nsid, ignoring: %m");
+
+ return sd_bus_message_append(reply, "u", nsid);
+}
+
static const sd_bus_vtable manager_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("IPv6AddressState", "s", property_get_address_state, offsetof(Manager, ipv6_address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("OnlineState", "s", property_get_online_state, offsetof(Manager, online_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("NamespaceId", "t", property_get_namespace_id, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("NamespaceNSID", "u", property_get_namespace_nsid, 0, 0),
SD_BUS_METHOD_WITH_ARGS("ListLinks",
SD_BUS_NO_ARGS,
}
static int vl_method_get_namespace_id(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
- uint64_t id;
+ uint64_t inode = 0;
+ uint32_t nsid = UINT32_MAX;
+ int r;
assert(link);
if (json_variant_elements(parameters) > 0)
return varlink_error_invalid_parameter(link, parameters);
+ /* Network namespaces have two identifiers: the inode number (which all namespace types have), and
+ * the "nsid" (aka the "cookie"), which only network namespaces know as a concept, and which is not
+ * assigned by default, but once it is, is fixed. Let's return both, to avoid any confusion which one
+ * this is. */
+
struct stat st;
- if (stat("/proc/self/ns/net", &st) < 0) {
+ if (stat("/proc/self/ns/net", &st) < 0)
log_warning_errno(errno, "Failed to stat network namespace, ignoring: %m");
- id = 0;
- } else
- id = st.st_ino;
+ else
+ inode = st.st_ino;
+
+ r = netns_get_nsid(/* netnsfd= */ -EBADF, &nsid);
+ if (r < 0)
+ log_warning_errno(r, "Failed to query network nsid, ignoring: %m");
return varlink_replyb(link,
JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR_UNSIGNED("NamespaceId", id)));
+ JSON_BUILD_PAIR_UNSIGNED("NamespaceId", inode),
+ JSON_BUILD_PAIR_CONDITION(nsid == UINT32_MAX, "NamespaceNSID", JSON_BUILD_NULL),
+ JSON_BUILD_PAIR_CONDITION(nsid != UINT32_MAX, "NamespaceNSID", JSON_BUILD_UNSIGNED(nsid))));
}
int manager_connect_varlink(Manager *m) {
static int manager_post_handler(sd_event_source *s, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
+ (void) manager_process_remove_requests(manager);
(void) manager_process_requests(manager);
(void) manager_clean_all(manager);
return 0;
(void) link_stop_engines(link, true);
m->request_queue = ordered_set_free(m->request_queue);
+ m->remove_request_queue = ordered_set_free(m->remove_request_queue);
m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref);
m->new_wlan_ifindices = set_free(m->new_wlan_ifindices);
FirewallContext *fw_ctx;
+ bool request_queued;
OrderedSet *request_queue;
+ OrderedSet *remove_request_queue;
Hashmap *tuntap_fds_by_name;
};
assert(link);
- r = route_configure_handler_internal(rtnl, m, link, "Could not set NDisc route");
+ r = route_configure_handler_internal(rtnl, m, link, route, "Could not set NDisc route");
if (r <= 0)
return r;
ndisc_set_route_priority(link, route);
if (!route->protocol_set)
route->protocol = RTPROT_RA;
- if (route->quickack < 0)
- route->quickack = link->network->ipv6_accept_ra_quickack;
- if (route->mtu == 0)
- route->mtu = mtu;
- if (route->hop_limit == 0)
- route->hop_limit = hop_limit;
+ r = route_metric_set(&route->metric, RTAX_MTU, mtu);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_HOPLIMIT, hop_limit);
+ if (r < 0)
+ return r;
+ r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->ipv6_accept_ra_quickack);
+ if (r < 0)
+ return r;
is_new = route_get(NULL, link, route, NULL) < 0;
}
static int ndisc_request_address(Address *in, Link *link, sd_ndisc_router *rt) {
- _cleanup_(address_freep) Address *address = in;
+ _cleanup_(address_unrefp) Address *address = in;
struct in6_addr router;
bool is_new;
int r;
route->family = AF_INET6;
route->pref = preference;
- route->gw_family = AF_INET6;
- route->gw.in6 = gateway;
+ route->nexthop.family = AF_INET6;
+ route->nexthop.gw.in6 = gateway;
route->lifetime_usec = lifetime_usec;
r = ndisc_request_route(TAKE_PTR(route), link, rt);
if (!route_gw->gateway_from_dhcp_or_ra)
continue;
- if (route_gw->gw_family != AF_INET6)
+ if (route_gw->nexthop.family != AF_INET6)
continue;
r = route_dup(route_gw, &route);
if (r < 0)
return r;
- route->gw.in6 = gateway;
+ route->nexthop.gw.in6 = gateway;
if (!route->pref_set)
route->pref = preference;
route->lifetime_usec = lifetime_usec;
}
static int ndisc_router_process_icmp6_ratelimit(Link *link, sd_ndisc_router *rt) {
- char buf[DECIMAL_STR_MAX(usec_t)];
- usec_t icmp6_ratelimit;
+ usec_t icmp6_ratelimit, msec;
int r;
assert(link);
return 0;
}
+ /* We do not allow 0 here. */
if (!timestamp_is_set(icmp6_ratelimit))
return 0;
+ msec = DIV_ROUND_UP(icmp6_ratelimit, USEC_PER_MSEC);
+ if (msec <= 0 || msec > INT_MAX)
+ return 0;
+
/* Limit the maximal rates for sending ICMPv6 packets. 0 to disable any limiting, otherwise the
* minimal space between responses in milliseconds. Default: 1000. */
- xsprintf(buf, USEC_FMT, DIV_ROUND_UP(icmp6_ratelimit, USEC_PER_MSEC));
-
- r = sysctl_write_ip_property(AF_INET6, NULL, "icmp/ratelimit", buf);
+ r = sysctl_write_ip_property_int(AF_INET6, NULL, "icmp/ratelimit", (int) msec);
if (r < 0)
log_link_warning_errno(link, r, "Failed to apply ICMP6 ratelimit, ignoring: %m");
return log_link_warning_errno(link, r, "Failed to generate SLAAC addresses: %m");
SET_FOREACH(a, addresses) {
- _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(address_unrefp) Address *address = NULL;
r = address_new(&address);
if (r < 0)
route->family = AF_INET6;
route->pref = preference;
- route->gw.in6 = gateway;
- route->gw_family = AF_INET6;
+ route->nexthop.gw.in6 = gateway;
+ route->nexthop.family = AF_INET6;
route->dst.in6 = dst;
route->dst_prefixlen = prefixlen;
route->lifetime_usec = lifetime_usec;
#include "networkd-queue.h"
#include "set.h"
-Neighbor *neighbor_free(Neighbor *neighbor) {
- if (!neighbor)
- return NULL;
+static Neighbor* neighbor_detach_impl(Neighbor *neighbor) {
+ assert(neighbor);
+ assert(!neighbor->link || !neighbor->network);
if (neighbor->network) {
assert(neighbor->section);
ordered_hashmap_remove(neighbor->network->neighbors_by_section, neighbor->section);
+ neighbor->network = NULL;
+ return neighbor;
}
- config_section_free(neighbor->section);
-
- if (neighbor->link)
+ if (neighbor->link) {
set_remove(neighbor->link->neighbors, neighbor);
+ neighbor->link = NULL;
+ return neighbor;
+ }
+ return NULL;
+}
+
+static void neighbor_detach(Neighbor *neighbor) {
+ neighbor_unref(neighbor_detach_impl(neighbor));
+}
+
+static Neighbor* neighbor_free(Neighbor *neighbor) {
+ if (!neighbor)
+ return NULL;
+
+ neighbor_detach_impl(neighbor);
+
+ config_section_free(neighbor->section);
return mfree(neighbor);
}
-DEFINE_SECTION_CLEANUP_FUNCTIONS(Neighbor, neighbor_free);
+DEFINE_TRIVIAL_REF_UNREF_FUNC(Neighbor, neighbor, neighbor_free);
+DEFINE_SECTION_CLEANUP_FUNCTIONS(Neighbor, neighbor_unref);
+
+static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state);
+static int neighbor_compare_func(const Neighbor *a, const Neighbor *b);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+ neighbor_hash_ops_detach,
+ Neighbor,
+ neighbor_hash_func,
+ neighbor_compare_func,
+ neighbor_detach);
+
+DEFINE_PRIVATE_HASH_OPS(
+ neighbor_hash_ops,
+ Neighbor,
+ neighbor_hash_func,
+ neighbor_compare_func);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ neighbor_section_hash_ops,
+ ConfigSection,
+ config_section_hash_func,
+ config_section_compare_func,
+ Neighbor,
+ neighbor_detach);
+
+static int neighbor_new(Neighbor **ret) {
+ Neighbor *neighbor;
+
+ assert(ret);
+
+ neighbor = new(Neighbor, 1);
+ if (!neighbor)
+ return -ENOMEM;
+
+ *neighbor = (Neighbor) {
+ .n_ref = 1,
+ };
+
+ *ret = TAKE_PTR(neighbor);
+ return 0;
+}
static int neighbor_new_static(Network *network, const char *filename, unsigned section_line, Neighbor **ret) {
_cleanup_(config_section_freep) ConfigSection *n = NULL;
- _cleanup_(neighbor_freep) Neighbor *neighbor = NULL;
+ _cleanup_(neighbor_unrefp) Neighbor *neighbor = NULL;
int r;
assert(network);
return 0;
}
- neighbor = new(Neighbor, 1);
- if (!neighbor)
- return -ENOMEM;
+ r = neighbor_new(&neighbor);
+ if (r < 0)
+ return r;
- *neighbor = (Neighbor) {
- .network = network,
- .family = AF_UNSPEC,
- .section = TAKE_PTR(n),
- .source = NETWORK_CONFIG_SOURCE_STATIC,
- };
+ neighbor->network = network;
+ neighbor->section = TAKE_PTR(n);
+ neighbor->source = NETWORK_CONFIG_SOURCE_STATIC;
- r = ordered_hashmap_ensure_put(&network->neighbors_by_section, &config_section_hash_ops, neighbor->section, neighbor);
+ r = ordered_hashmap_ensure_put(&network->neighbors_by_section, &neighbor_section_hash_ops, neighbor->section, neighbor);
if (r < 0)
return r;
}
static int neighbor_dup(const Neighbor *neighbor, Neighbor **ret) {
- _cleanup_(neighbor_freep) Neighbor *dest = NULL;
+ _cleanup_(neighbor_unrefp) Neighbor *dest = NULL;
assert(neighbor);
assert(ret);
if (!dest)
return -ENOMEM;
- /* Unset all pointers */
+ /* Clear the reference counter and all pointers */
+ dest->n_ref = 1;
dest->link = NULL;
dest->network = NULL;
dest->section = NULL;
return memcmp(&a->in_addr, &b->in_addr, FAMILY_ADDRESS_SIZE(a->family));
}
-DEFINE_PRIVATE_HASH_OPS(
- neighbor_hash_ops,
- Neighbor,
- neighbor_hash_func,
- neighbor_compare_func);
-
-DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
- neighbor_hash_ops_free,
- Neighbor,
- neighbor_hash_func,
- neighbor_compare_func,
- neighbor_free);
-
static int neighbor_get_request(Link *link, const Neighbor *neighbor, Request **ret) {
Request *req;
return 0;
}
-static int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) {
+int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) {
Neighbor *existing;
assert(link);
return 0;
}
-static int neighbor_add(Link *link, Neighbor *neighbor) {
+static int neighbor_attach(Link *link, Neighbor *neighbor) {
int r;
assert(link);
assert(neighbor);
+ assert(!neighbor->link);
- r = set_ensure_put(&link->neighbors, &neighbor_hash_ops_free, neighbor);
+ r = set_ensure_put(&link->neighbors, &neighbor_hash_ops_detach, neighbor);
if (r < 0)
return r;
if (r == 0)
return -EEXIST;
neighbor->link = link;
+ neighbor_ref(neighbor);
return 0;
}
}
static int link_request_neighbor(Link *link, const Neighbor *neighbor) {
- _cleanup_(neighbor_freep) Neighbor *tmp = NULL;
+ _cleanup_(neighbor_unrefp) Neighbor *tmp = NULL;
Neighbor *existing = NULL;
int r;
log_neighbor_debug(tmp, "Requesting", link);
r = link_queue_request_safe(link, REQUEST_TYPE_NEIGHBOR,
tmp,
- neighbor_free,
+ neighbor_unref,
neighbor_hash_func,
neighbor_compare_func,
neighbor_process_request,
return 0;
}
-static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) {
int r;
assert(m);
- assert(link);
+ assert(rreq);
- if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
- return 1;
+ Link *link = ASSERT_PTR(rreq->link);
+ Neighbor *neighbor = ASSERT_PTR(rreq->userdata);
+
+ if (link->state == LINK_STATE_LINGER)
+ return 0;
r = sd_netlink_message_get_errno(m);
- if (r < 0 && r != -ESRCH)
+ if (r < 0) {
/* Neighbor may not exist because it already got deleted, ignore that. */
- log_link_message_warning_errno(link, m, r, "Could not remove neighbor");
+ log_link_message_full_errno(link, m,
+ (r == -ESRCH || !neighbor->link) ? LOG_DEBUG : LOG_WARNING,
+ r, "Could not remove neighbor");
+
+ if (neighbor->link) {
+ /* If the neighbor cannot be removed, then assume the neighbor is already removed. */
+ log_neighbor_debug(neighbor, "Forgetting", link);
+
+ Request *req;
+ if (neighbor_get_request(link, neighbor, &req) >= 0)
+ neighbor_enter_removed(req->userdata);
+
+ neighbor_detach(neighbor);
+ }
+ }
return 1;
}
-static int neighbor_remove(Neighbor *neighbor) {
+int neighbor_remove(Neighbor *neighbor, Link *link) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
- Request *req;
- Link *link;
int r;
assert(neighbor);
- assert(neighbor->link);
- assert(neighbor->link->manager);
- assert(neighbor->link->manager->rtnl);
-
- link = neighbor->link;
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
log_neighbor_debug(neighbor, "Removing", link);
if (r < 0)
return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m");
- r = netlink_call_async(link->manager->rtnl, NULL, m, neighbor_remove_handler,
- link_netlink_destroy_callback, link);
+ r = link_remove_request_add(link, neighbor, neighbor, link->manager->rtnl, m, neighbor_remove_handler);
if (r < 0)
- return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
-
- link_ref(link);
+ return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m");
neighbor_enter_removing(neighbor);
- if (neighbor_get_request(neighbor->link, neighbor, &req) >= 0)
- neighbor_enter_removing(req->userdata);
-
return 0;
}
if (!neighbor_is_marked(neighbor))
continue;
- RET_GATHER(r, neighbor_remove(neighbor));
+ RET_GATHER(r, neighbor_remove(neighbor, link));
}
return r;
if (!neighbor_exists(neighbor))
continue;
- RET_GATHER(r, neighbor_remove(neighbor));
+ RET_GATHER(r, neighbor_remove(neighbor, link));
}
return r;
}
int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
- _cleanup_(neighbor_freep) Neighbor *tmp = NULL;
+ _cleanup_(neighbor_unrefp) Neighbor *tmp = NULL;
Neighbor *neighbor = NULL;
Request *req = NULL;
uint16_t type, state;
* kernel sends messages about neighbors after a link is removed. So, just ignore it. */
return 0;
- tmp = new0(Neighbor, 1);
- if (!tmp)
+ r = neighbor_new(&tmp);
+ if (r < 0)
return log_oom();
/* First, retrieve the fundamental information about the neighbor. */
if (neighbor) {
neighbor_enter_removed(neighbor);
log_neighbor_debug(neighbor, "Forgetting removed", link);
- neighbor_free(neighbor);
+ neighbor_detach(neighbor);
} else
log_neighbor_debug(tmp, "Kernel removed unknown", link);
/* If we did not know the neighbor, then save it. */
if (!neighbor) {
- r = neighbor_add(link, tmp);
+ r = neighbor_attach(link, tmp);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to save received neighbor, ignoring: %m");
return 0;
}
- neighbor = TAKE_PTR(tmp);
+ neighbor = tmp;
is_new = true;
}
Neighbor *dup;
if (neighbor_section_verify(neighbor) < 0) {
- /* Drop invalid [Neighbor] sections. Note that neighbor_free() will drop the
+ /* Drop invalid [Neighbor] sections. Note that neighbor_detach() will drop the
* neighbor from neighbors_by_section. */
- neighbor_free(neighbor);
+ neighbor_detach(neighbor);
continue;
}
IN_ADDR_TO_STRING(neighbor->family, &neighbor->in_addr),
neighbor->section->line,
dup->section->line, dup->section->line);
- /* neighbor_free() will drop the neighbor from neighbors_by_section. */
- neighbor_free(dup);
+ /* neighbor_detach() will drop the neighbor from neighbors_by_section. */
+ neighbor_detach(dup);
}
- /* Use neighbor_hash_ops, instead of neighbor_hash_ops_free. Otherwise, the Neighbor objects
- * will be freed. */
+ /* Use neighbor_hash_ops, instead of neighbor_hash_ops_detach. Otherwise, the Neighbor objects
+ * will be detached. */
r = set_ensure_put(&neighbors, &neighbor_hash_ops, neighbor);
if (r < 0)
return log_oom();
void *data,
void *userdata) {
- _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL;
+ _cleanup_(neighbor_unref_or_set_invalidp) Neighbor *n = NULL;
Network *network = ASSERT_PTR(userdata);
int r;
void *data,
void *userdata) {
- _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL;
+ _cleanup_(neighbor_unref_or_set_invalidp) Neighbor *n = NULL;
Network *network = ASSERT_PTR(userdata);
int r;
NetworkConfigSource source;
NetworkConfigState state;
+ unsigned n_ref;
+
int family;
union in_addr_union in_addr;
struct hw_addr_data ll_addr;
} Neighbor;
-Neighbor *neighbor_free(Neighbor *neighbor);
+Neighbor* neighbor_ref(Neighbor *neighbor);
+Neighbor* neighbor_unref(Neighbor *neighbor);
+
+int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret);
+int neighbor_remove(Neighbor *neighbor, Link *link);
int network_drop_invalid_neighbors(Network *network);
Route.Scope, config_parse_route_scope, 0, 0
Route.PreferredSource, config_parse_preferred_src, 0, 0
Route.Table, config_parse_route_table, 0, 0
-Route.MTUBytes, config_parse_route_mtu, AF_UNSPEC, 0
-Route.GatewayOnLink, config_parse_route_boolean, 0, 0
-Route.GatewayOnlink, config_parse_route_boolean, 0, 0
+Route.GatewayOnLink, config_parse_route_gateway_onlink, 0, 0
+Route.GatewayOnlink, config_parse_route_gateway_onlink, 0, 0
Route.IPv6Preference, config_parse_ipv6_route_preference, 0, 0
Route.Protocol, config_parse_route_protocol, 0, 0
Route.Type, config_parse_route_type, 0, 0
-Route.TCPRetransmissionTimeoutSec, config_parse_route_tcp_rto, 0, 0
-Route.HopLimit, config_parse_route_hop_limit, 0, 0
-Route.InitialCongestionWindow, config_parse_route_tcp_window, 0, 0
-Route.InitialAdvertisedReceiveWindow, config_parse_route_tcp_window, 0, 0
-Route.TCPAdvertisedMaximumSegmentSize, config_parse_tcp_advmss, 0, 0
-Route.TCPCongestionControlAlgorithm, config_parse_tcp_congestion, 0, 0
-Route.QuickAck, config_parse_route_boolean, 0, 0
-Route.FastOpenNoCookie, config_parse_route_boolean, 0, 0
-Route.TTLPropagate, config_parse_warn_compat, DISABLED_LEGACY, 0
Route.MultiPathRoute, config_parse_multipath_route, 0, 0
Route.NextHop, config_parse_route_nexthop, 0, 0
+Route.MTUBytes, config_parse_route_metric_mtu, RTAX_MTU, 0
+Route.TCPAdvertisedMaximumSegmentSize, config_parse_route_metric_advmss, RTAX_ADVMSS, 0
+Route.HopLimit, config_parse_route_metric_hop_limit, RTAX_HOPLIMIT, 0
+Route.InitialCongestionWindow, config_parse_route_metric_tcp_window, RTAX_INITCWND, 0
+Route.TCPRetransmissionTimeoutSec, config_parse_route_metric_tcp_rto, RTAX_RTO_MIN, 0
+Route.InitialAdvertisedReceiveWindow, config_parse_route_metric_tcp_window, RTAX_INITRWND, 0
+Route.QuickAck, config_parse_route_metric_boolean, RTAX_QUICKACK, 0
+Route.TCPCongestionControlAlgorithm, config_parse_route_metric_tcp_congestion, RTAX_CC_ALGO, 0
+Route.FastOpenNoCookie, config_parse_route_metric_boolean, RTAX_FASTOPEN_NO_COOKIE, 0
+Route.TTLPropagate, config_parse_warn_compat, DISABLED_LEGACY, 0
NextHop.Id, config_parse_nexthop_id, 0, 0
NextHop.Gateway, config_parse_nexthop_gateway, 0, 0
NextHop.Family, config_parse_nexthop_family, 0, 0
log_warning("%s: Cannot set routes when Bond= is specified, ignoring routes.",
network->filename);
- network->addresses_by_section = ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free);
+ network->addresses_by_section = ordered_hashmap_free(network->addresses_by_section);
network->routes_by_section = hashmap_free_with_destructor(network->routes_by_section, route_free);
}
network->ignore_carrier_loss_usec = USEC_INFINITY;
}
- if (!network->ignore_carrier_loss_set) {
- network->ignore_carrier_loss_set = true;
+ if (!network->ignore_carrier_loss_set) /* Set implied default. */
network->ignore_carrier_loss_usec = network->configure_without_carrier ? USEC_INFINITY : 0;
- }
if (IN_SET(network->activation_policy, ACTIVATION_POLICY_DOWN, ACTIVATION_POLICY_ALWAYS_DOWN, ACTIVATION_POLICY_MANUAL)) {
if (network->required_for_online < 0 ||
.n_ref = 1,
.required_for_online = -1,
- .required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT,
+ .required_operstate_for_online = LINK_OPERSTATE_RANGE_INVALID,
.activation_policy = _ACTIVATION_POLICY_INVALID,
.group = -1,
.arp = -1,
/* static configs */
set_free_free(network->ipv6_proxy_ndp_addresses);
- ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free);
+ ordered_hashmap_free(network->addresses_by_section);
hashmap_free_with_destructor(network->routes_by_section, route_free);
- ordered_hashmap_free_with_destructor(network->nexthops_by_section, nexthop_free);
+ ordered_hashmap_free(network->nexthops_by_section);
hashmap_free_with_destructor(network->bridge_fdb_entries_by_section, bridge_fdb_free);
hashmap_free_with_destructor(network->bridge_mdb_entries_by_section, bridge_mdb_free);
- ordered_hashmap_free_with_destructor(network->neighbors_by_section, neighbor_free);
+ ordered_hashmap_free(network->neighbors_by_section);
hashmap_free_with_destructor(network->address_labels_by_section, address_label_free);
hashmap_free_with_destructor(network->prefixes_by_section, prefix_free);
hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free);
void *userdata) {
Network *network = ASSERT_PTR(userdata);
- LinkOperationalStateRange range;
- bool required = true;
int r;
assert(filename);
if (isempty(rvalue)) {
network->required_for_online = -1;
- network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT;
+ network->required_operstate_for_online = LINK_OPERSTATE_RANGE_INVALID;
return 0;
}
- r = parse_operational_state_range(rvalue, &range);
+ r = parse_operational_state_range(rvalue, &network->required_operstate_for_online);
if (r < 0) {
r = parse_boolean(rvalue);
if (r < 0) {
return 0;
}
- required = r;
- range = LINK_OPERSTATE_RANGE_DEFAULT;
+ network->required_for_online = r;
+ network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT;
+ return 0;
}
- network->required_for_online = required;
- network->required_operstate_for_online = range;
-
+ network->required_for_online = true;
return 0;
}
#include "stdio-util.h"
#include "string-util.h"
-NextHop *nexthop_free(NextHop *nexthop) {
- if (!nexthop)
- return NULL;
+static void nexthop_detach_from_group_members(NextHop *nexthop) {
+ assert(nexthop);
+ assert(nexthop->manager);
+ assert(nexthop->id > 0);
+
+ struct nexthop_grp *nhg;
+ HASHMAP_FOREACH(nhg, nexthop->group) {
+ NextHop *nh;
+
+ if (nexthop_get_by_id(nexthop->manager, nhg->id, &nh) < 0)
+ continue;
+
+ set_remove(nh->nexthops, UINT32_TO_PTR(nexthop->id));
+ }
+}
+
+static void nexthop_attach_to_group_members(NextHop *nexthop) {
+ int r;
+
+ assert(nexthop);
+ assert(nexthop->manager);
+ assert(nexthop->id > 0);
+
+ struct nexthop_grp *nhg;
+ HASHMAP_FOREACH(nhg, nexthop->group) {
+ NextHop *nh;
+
+ r = nexthop_get_by_id(nexthop->manager, nhg->id, &nh);
+ if (r < 0) {
+ if (nexthop->manager->manage_foreign_nexthops)
+ log_debug_errno(r, "Nexthop (id=%"PRIu32") has unknown group member (%"PRIu32"), ignoring.",
+ nexthop->id, nhg->id);
+ continue;
+ }
+
+ r = set_ensure_put(&nh->nexthops, NULL, UINT32_TO_PTR(nexthop->id));
+ if (r < 0)
+ log_debug_errno(r, "Failed to save nexthop ID (%"PRIu32") to group member (%"PRIu32"), ignoring: %m",
+ nexthop->id, nhg->id);
+ }
+}
+
+static NextHop* nexthop_detach_impl(NextHop *nexthop) {
+ assert(nexthop);
+ assert(!nexthop->manager || !nexthop->network);
if (nexthop->network) {
assert(nexthop->section);
ordered_hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section);
+ nexthop->network = NULL;
+ return nexthop;
}
- config_section_free(nexthop->section);
-
if (nexthop->manager) {
assert(nexthop->id > 0);
+
+ nexthop_detach_from_group_members(nexthop);
+
hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
+ nexthop->manager = NULL;
+ return nexthop;
}
+ return NULL;
+}
+
+static void nexthop_detach(NextHop *nexthop) {
+ nexthop_unref(nexthop_detach_impl(nexthop));
+}
+
+static NextHop* nexthop_free(NextHop *nexthop) {
+ if (!nexthop)
+ return NULL;
+
+ nexthop_detach_impl(nexthop);
+
+ config_section_free(nexthop->section);
hashmap_free_free(nexthop->group);
+ set_free(nexthop->nexthops);
return mfree(nexthop);
}
-DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_free);
+DEFINE_TRIVIAL_REF_UNREF_FUNC(NextHop, nexthop, nexthop_free);
+DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_unref);
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
nexthop_hash_ops,
trivial_hash_func,
trivial_compare_func,
NextHop,
- nexthop_free);
+ nexthop_detach);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ nexthop_section_hash_ops,
+ ConfigSection,
+ config_section_hash_func,
+ config_section_compare_func,
+ NextHop,
+ nexthop_detach);
static int nexthop_new(NextHop **ret) {
- _cleanup_(nexthop_freep) NextHop *nexthop = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL;
nexthop = new(NextHop, 1);
if (!nexthop)
return -ENOMEM;
*nexthop = (NextHop) {
- .family = AF_UNSPEC,
+ .n_ref = 1,
.onlink = -1,
};
static int nexthop_new_static(Network *network, const char *filename, unsigned section_line, NextHop **ret) {
_cleanup_(config_section_freep) ConfigSection *n = NULL;
- _cleanup_(nexthop_freep) NextHop *nexthop = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL;
int r;
assert(network);
nexthop->section = TAKE_PTR(n);
nexthop->source = NETWORK_CONFIG_SOURCE_STATIC;
- r = ordered_hashmap_ensure_put(&network->nexthops_by_section, &config_section_hash_ops, nexthop->section, nexthop);
+ r = ordered_hashmap_ensure_put(&network->nexthops_by_section, &nexthop_section_hash_ops, nexthop->section, nexthop);
if (r < 0)
return r;
}
static int nexthop_dup(const NextHop *src, NextHop **ret) {
- _cleanup_(nexthop_freep) NextHop *dest = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *dest = NULL;
struct nexthop_grp *nhg;
int r;
if (!dest)
return -ENOMEM;
- /* unset all pointers */
+ /* clear the reference counter and all pointers */
+ dest->n_ref = 1;
dest->manager = NULL;
dest->network = NULL;
dest->section = NULL;
assert(manager);
+ if (id == 0)
+ return -EINVAL;
+
req = ordered_set_get(
manager->request_queue,
&(Request) {
}
static int nexthop_add_new(Manager *manager, uint32_t id, NextHop **ret) {
- _cleanup_(nexthop_freep) NextHop *nexthop = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL;
int r;
assert(manager);
yes_no(nexthop->blackhole), strna(group), strna(flags));
}
-static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int nexthop_remove_dependents(NextHop *nexthop, Manager *manager) {
+ int r = 0;
+
+ assert(nexthop);
+ assert(manager);
+
+ /* If a nexthop is removed, the kernel silently removes nexthops that depend on the
+ * removed nexthop. Let's remove them for safety (though, they are already removed in the kernel,
+ * hence that should fail), and forget them. */
+
+ void *id;
+ SET_FOREACH(id, nexthop->nexthops) {
+ NextHop *nh;
+
+ if (nexthop_get_by_id(manager, PTR_TO_UINT32(id), &nh) < 0)
+ continue;
+
+ RET_GATHER(r, nexthop_remove(nh, manager));
+ }
+
+ return r;
+}
+
+static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) {
int r;
assert(m);
+ assert(rreq);
- /* link may be NULL. */
-
- if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
- return 1;
+ Manager *manager = ASSERT_PTR(rreq->manager);
+ NextHop *nexthop = ASSERT_PTR(rreq->userdata);
r = sd_netlink_message_get_errno(m);
- if (r < 0 && r != -ENOENT)
- log_link_message_warning_errno(link, m, r, "Could not drop nexthop, ignoring");
+ if (r < 0) {
+ log_message_full_errno(m,
+ (r == -ENOENT || !nexthop->manager) ? LOG_DEBUG : LOG_WARNING,
+ r, "Could not drop nexthop, ignoring");
+
+ (void) nexthop_remove_dependents(nexthop, manager);
+
+ if (nexthop->manager) {
+ /* If the nexthop cannot be removed, then assume the nexthop is already removed. */
+ log_nexthop_debug(nexthop, "Forgetting", manager);
+
+ Request *req;
+ if (nexthop_get_request_by_id(manager, nexthop->id, &req) >= 0)
+ nexthop_enter_removed(req->userdata);
+
+ nexthop_detach(nexthop);
+ }
+ }
return 1;
}
-static int nexthop_remove(NextHop *nexthop) {
+int nexthop_remove(NextHop *nexthop, Manager *manager) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
- Manager *manager;
Link *link = NULL;
- Request *req;
int r;
assert(nexthop);
assert(nexthop->id > 0);
-
- manager = ASSERT_PTR(nexthop->manager);
+ assert(manager);
/* link may be NULL. */
(void) link_get_by_index(manager, nexthop->ifindex, &link);
if (r < 0)
return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m");
- r = netlink_call_async(manager->rtnl, NULL, m, nexthop_remove_handler,
- link ? link_netlink_destroy_callback : NULL, link);
+ r = manager_remove_request_add(manager, nexthop, nexthop, manager->rtnl, m, nexthop_remove_handler);
if (r < 0)
- return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
-
- link_ref(link); /* link may be NULL, link_ref() is OK with that */
+ return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m");
nexthop_enter_removing(nexthop);
- if (nexthop_get_request_by_id(manager, nexthop->id, &req) >= 0)
- nexthop_enter_removing(req->userdata);
-
return 0;
}
return 1;
}
+int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret) {
+ NextHop *nexthop;
+
+ assert(manager);
+
+ if (id == 0)
+ return -EINVAL;
+
+ if (nexthop_get_request_by_id(manager, id, NULL) >= 0)
+ goto not_ready;
+
+ if (nexthop_get_by_id(manager, id, &nexthop) < 0)
+ goto not_ready;
+
+ if (!nexthop_exists(nexthop))
+ goto not_ready;
+
+ if (ret)
+ *ret = nexthop;
+
+ return true;
+
+not_ready:
+ if (ret)
+ *ret = NULL;
+
+ return false;
+}
+
static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) {
struct nexthop_grp *nhg;
+ int r;
assert(link);
assert(nexthop);
/* All group members must be configured first. */
HASHMAP_FOREACH(nhg, nexthop->group) {
- NextHop *g;
-
- if (nexthop_get_by_id(link->manager, nhg->id, &g) < 0)
- return false;
-
- if (!nexthop_exists(g))
- return false;
+ r = nexthop_is_ready(link->manager, nhg->id, NULL);
+ if (r <= 0)
+ return r;
}
return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw);
}
static int link_request_nexthop(Link *link, const NextHop *nexthop) {
- _cleanup_(nexthop_freep) NextHop *tmp = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *tmp = NULL;
NextHop *existing = NULL;
int r;
log_nexthop_debug(tmp, "Requesting", link->manager);
r = link_queue_request_safe(link, REQUEST_TYPE_NEXTHOP,
tmp,
- nexthop_free,
+ nexthop_unref,
nexthop_hash_func,
nexthop_compare_func,
nexthop_process_request,
if (!nexthop_is_marked(nexthop))
continue;
- RET_GATHER(r, nexthop_remove(nexthop));
+ RET_GATHER(r, nexthop_remove(nexthop, link->manager));
}
return r;
}
}
-static int nexthop_update_group(NextHop *nexthop, const struct nexthop_grp *group, size_t size) {
+static int nexthop_update_group(NextHop *nexthop, sd_netlink_message *message) {
_cleanup_hashmap_free_free_ Hashmap *h = NULL;
- size_t n_group;
+ _cleanup_free_ struct nexthop_grp *group = NULL;
+ size_t size = 0, n_group;
int r;
assert(nexthop);
- assert(group || size == 0);
+ assert(message);
+
+ r = sd_netlink_message_read_data(message, NHA_GROUP, &size, (void**) &group);
+ if (r < 0 && r != -ENODATA)
+ return log_debug_errno(r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
- if (size == 0 || size % sizeof(struct nexthop_grp) != 0)
+ nexthop_detach_from_group_members(nexthop);
+
+ if (size % sizeof(struct nexthop_grp) != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"rtnl: received nexthop message with invalid nexthop group size, ignoring.");
hashmap_free_free(nexthop->group);
nexthop->group = TAKE_PTR(h);
+
+ nexthop_attach_to_group_members(nexthop);
return 0;
}
int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
- _cleanup_free_ void *raw_group = NULL;
- size_t raw_group_size;
uint16_t type;
uint32_t id, ifindex;
NextHop *nexthop = NULL;
if (nexthop) {
nexthop_enter_removed(nexthop);
log_nexthop_debug(nexthop, "Forgetting removed", m);
- nexthop_free(nexthop);
+ (void) nexthop_remove_dependents(nexthop, m);
+ nexthop_detach(nexthop);
} else
log_nexthop_debug(&(const NextHop) { .id = id }, "Kernel removed unknown", m);
if (r < 0)
log_debug_errno(r, "rtnl: could not get nexthop flags, ignoring: %m");
- r = sd_netlink_message_read_data(message, NHA_GROUP, &raw_group_size, &raw_group);
- if (r == -ENODATA)
- nexthop->group = hashmap_free_free(nexthop->group);
- else if (r < 0)
- log_debug_errno(r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
- else
- (void) nexthop_update_group(nexthop, raw_group, raw_group_size);
+ (void) nexthop_update_group(nexthop, message);
if (nexthop->family != AF_UNSPEC) {
r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, nexthop->family, &nexthop->gw);
ORDERED_HASHMAP_FOREACH(nh, network->nexthops_by_section) {
if (nexthop_section_verify(nh) < 0) {
- nexthop_free(nh);
+ nexthop_detach(nh);
continue;
}
dup->section->filename,
nh->id, nh->section->line,
dup->section->line, dup->section->line);
- /* nexthop_free() will drop the nexthop from nexthops_by_section. */
- nexthop_free(dup);
+ /* nexthop_detach() will drop the nexthop from nexthops_by_section. */
+ nexthop_detach(dup);
}
r = hashmap_ensure_put(&nexthops, NULL, UINT32_TO_PTR(nh->id), nh);
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
uint32_t id;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
AddressFamily a;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;
NetworkConfigSource source;
NetworkConfigState state;
- uint8_t protocol;
- int ifindex;
- uint32_t id;
- bool blackhole;
+ unsigned n_ref;
+
+ /* struct nhmsg */
int family;
- union in_addr_union gw;
+ uint8_t protocol;
uint8_t flags;
- int onlink; /* Only used in conf parser and nexthop_section_verify(). */
- Hashmap *group;
+
+ /* attributes */
+ uint32_t id; /* NHA_ID */
+ Hashmap *group; /* NHA_GROUP */
+ bool blackhole; /* NHA_BLACKHOLE */
+ int ifindex; /* NHA_OIF */
+ union in_addr_union gw; /* NHA_GATEWAY */
+
+ /* Only used in conf parser and nexthop_section_verify(). */
+ int onlink;
+
+ /* For managing nexthops that depend on this nexthop. */
+ Set *nexthops;
} NextHop;
-NextHop *nexthop_free(NextHop *nexthop);
+NextHop* nexthop_ref(NextHop *nexthop);
+NextHop* nexthop_unref(NextHop *nexthop);
+
+int nexthop_remove(NextHop *nexthop, Manager *manager);
int network_drop_invalid_nexthops(Network *network);
int link_request_static_nexthops(Link *link, bool only_ipv4);
int nexthop_get_by_id(Manager *manager, uint32_t id, NextHop **ret);
+int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret);
int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m);
int manager_build_nexthop_ids(Manager *manager);
#define REPLY_CALLBACK_COUNT_THRESHOLD 128
+static Request* request_detach_impl(Request *req) {
+ assert(req);
+
+ if (!req->manager)
+ return NULL;
+
+ ordered_set_remove(req->manager->request_queue, req);
+ req->manager = NULL;
+ return req;
+}
+
+void request_detach(Request *req) {
+ request_unref(request_detach_impl(req));
+}
+
static Request *request_free(Request *req) {
if (!req)
return NULL;
/* To prevent from triggering assertions in the hash and compare functions, remove this request
* from the set before freeing userdata below. */
- if (req->manager)
- ordered_set_remove(req->manager->request_queue, req);
+ request_detach_impl(req);
if (req->free_func)
req->free_func(req->userdata);
DEFINE_TRIVIAL_REF_UNREF_FUNC(Request, request, request_free);
-void request_detach(Manager *manager, Request *req) {
- assert(manager);
-
- if (!req)
- return;
-
- req = ordered_set_remove(manager->request_queue, req);
- if (!req)
- return;
-
- req->manager = NULL;
- request_unref(req);
-}
-
static void request_destroy_callback(Request *req) {
assert(req);
- if (req->manager)
- request_detach(req->manager, req);
-
+ request_detach(req);
request_unref(req);
}
Request,
request_hash_func,
request_compare_func,
- request_unref);
+ request_detach);
static int request_new(
Manager *manager,
if (req->counter)
(*req->counter)++;
+ /* If this is called in the ORDERED_SET_FOREACH() loop of manager_process_requests(), we need to
+ * exit from the loop, due to the limitation of the iteration on OrderedSet. */
+ manager->request_queued = true;
+
if (ret)
*ret = req;
}
int manager_process_requests(Manager *manager) {
+ Request *req;
int r;
assert(manager);
- for (;;) {
- bool processed = false;
- Request *req;
-
- ORDERED_SET_FOREACH(req, manager->request_queue) {
- _cleanup_(link_unrefp) Link *link = link_ref(req->link);
+ /* Process only when no remove request is queued. */
+ if (!ordered_set_isempty(manager->remove_request_queue))
+ return 0;
- assert(req->process);
+ manager->request_queued = false;
- if (req->waiting_reply)
- continue; /* Waiting for netlink reply. */
+ ORDERED_SET_FOREACH(req, manager->request_queue) {
+ if (req->waiting_reply)
+ continue; /* Already processed, and waiting for netlink reply. */
- /* Typically, requests send netlink message asynchronously. If there are many requests
- * queued, then this event may make reply callback queue in sd-netlink full. */
- if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
- netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
- fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD)
- return 0;
+ /* Typically, requests send netlink message asynchronously. If there are many requests
+ * queued, then this event may make reply callback queue in sd-netlink full. */
+ if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
+ netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
+ fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD)
+ break;
- r = req->process(req, link, req->userdata);
- if (r == 0)
- continue;
+ /* Avoid the request and link freed by req->process() and request_detach(). */
+ _unused_ _cleanup_(request_unrefp) Request *req_unref = request_ref(req);
+ _cleanup_(link_unrefp) Link *link = link_ref(req->link);
- processed = true;
+ assert(req->process);
+ r = req->process(req, link, req->userdata);
+ if (r < 0) {
+ request_detach(req);
- /* If the request sends netlink message, e.g. for Address or so, the Request object
- * is referenced by the netlink slot, and will be detached later by its destroy callback.
- * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */
- if (!req->waiting_reply)
- request_detach(manager, req);
-
- if (r < 0 && link) {
+ if (link) {
link_enter_failed(link);
- /* link_enter_failed() may remove multiple requests,
- * hence we need to exit from the loop. */
+ /* link_enter_failed() may detach multiple requests from the queue.
+ * Hence, we need to exit from the loop. */
break;
}
}
+ if (r > 0 && !req->waiting_reply)
+ /* If the request sends netlink message, e.g. for Address or so, the Request object is
+ * referenced by the netlink slot, and will be detached later by its destroy callback.
+ * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */
+ request_detach(req);
- /* When at least one request is processed, then another request may be ready now. */
- if (!processed)
- break;
+ if (manager->request_queued)
+ break; /* New request is queued. Exit from the loop. */
}
return 0;
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(request_type, RequestType);
+
+static RemoveRequest* remove_request_free(RemoveRequest *req) {
+ if (!req)
+ return NULL;
+
+ if (req->manager)
+ ordered_set_remove(req->manager->remove_request_queue, req);
+
+ if (req->unref_func)
+ req->unref_func(req->userdata);
+
+ link_unref(req->link);
+ sd_netlink_unref(req->netlink);
+ sd_netlink_message_unref(req->message);
+
+ return mfree(req);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(RemoveRequest*, remove_request_free);
+DEFINE_TRIVIAL_DESTRUCTOR(remove_request_destroy_callback, RemoveRequest, remove_request_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+ remove_request_hash_ops,
+ void,
+ trivial_hash_func,
+ trivial_compare_func,
+ remove_request_free);
+
+int remove_request_add(
+ Manager *manager,
+ Link *link,
+ void *userdata,
+ mfree_func_t unref_func,
+ sd_netlink *netlink,
+ sd_netlink_message *message,
+ remove_request_netlink_handler_t netlink_handler) {
+
+ _cleanup_(remove_request_freep) RemoveRequest *req = NULL;
+ int r;
+
+ assert(manager);
+ assert(userdata);
+ assert(netlink);
+ assert(message);
+
+ req = new(RemoveRequest, 1);
+ if (!req)
+ return -ENOMEM;
+
+ *req = (RemoveRequest) {
+ .link = link_ref(link), /* link may be NULL, but link_ref() handles it gracefully. */
+ .userdata = userdata,
+ .netlink = sd_netlink_ref(netlink),
+ .message = sd_netlink_message_ref(message),
+ .netlink_handler = netlink_handler,
+ };
+
+ r = ordered_set_ensure_put(&manager->remove_request_queue, &remove_request_hash_ops, req);
+ if (r < 0)
+ return r;
+ assert(r > 0);
+
+ req->manager = manager;
+ req->unref_func = unref_func;
+
+ TAKE_PTR(req);
+ return 0;
+}
+
+int manager_process_remove_requests(Manager *manager) {
+ RemoveRequest *req;
+ int r;
+
+ assert(manager);
+
+ while ((req = ordered_set_first(manager->remove_request_queue))) {
+
+ /* Do not make the reply callback queue in sd-netlink full. */
+ if (netlink_get_reply_callback_count(req->netlink) >= REPLY_CALLBACK_COUNT_THRESHOLD)
+ return 0;
+
+ r = netlink_call_async(
+ req->netlink, NULL, req->message,
+ req->netlink_handler,
+ remove_request_destroy_callback,
+ req);
+ if (r < 0) {
+ _cleanup_(link_unrefp) Link *link = link_ref(req->link);
+
+ log_link_warning_errno(link, r, "Failed to call netlink message: %m");
+
+ /* First free the request. */
+ remove_request_free(req);
+
+ /* Then, make the link enter the failed state. */
+ if (link)
+ link_enter_failed(link);
+
+ } else {
+ /* On success, netlink needs to be unref()ed. Otherwise, the netlink and remove
+ * request may not freed on shutting down. */
+ req->netlink = sd_netlink_unref(req->netlink);
+ ordered_set_remove(manager->remove_request_queue, req);
+ }
+ }
+
+ return 0;
+}
Request *request_unref(Request *req);
DEFINE_TRIVIAL_CLEANUP_FUNC(Request*, request_unref);
-void request_detach(Manager *manager, Request *req);
+void request_detach(Request *req);
int netdev_queue_request(
NetDev *netdev,
int request_call_netlink_async(sd_netlink *nl, sd_netlink_message *m, Request *req);
const char* request_type_to_string(RequestType t) _const_;
+
+typedef struct RemoveRequest RemoveRequest;
+typedef int (*remove_request_netlink_handler_t)(sd_netlink *nl, sd_netlink_message *m, RemoveRequest *req);
+
+struct RemoveRequest {
+ Manager *manager;
+ Link *link;
+ void *userdata; /* e.g. Address */
+ mfree_func_t unref_func; /* e.g. address_unref() */
+ sd_netlink *netlink;
+ sd_netlink_message *message;
+ remove_request_netlink_handler_t netlink_handler;
+};
+
+int remove_request_add(
+ Manager *manager,
+ Link *link,
+ void *userdata, /* This is unref()ed when the call failed. */
+ mfree_func_t unref_func,
+ sd_netlink *netlink,
+ sd_netlink_message *message,
+ remove_request_netlink_handler_t netlink_handler);
+
+#define _remove_request_add(manager, link, data, name, nl, m, handler) \
+ ({ \
+ typeof(*data) *_data = (data); \
+ int _r; \
+ \
+ _r = remove_request_add(manager, link, _data, \
+ (mfree_func_t) name##_unref, \
+ nl, m, handler); \
+ if (_r >= 0) \
+ name##_ref(_data); \
+ _r; \
+ })
+
+
+#define link_remove_request_add(link, data, name, nl, m, handler) \
+ ({ \
+ Link *_link = (link); \
+ \
+ _remove_request_add(_link->manager, _link, data, name, \
+ nl, m, handler); \
+ })
+
+#define manager_remove_request_add(manager, data, name, nl, m, handler) \
+ _remove_request_add(manager, NULL, data, name, nl, m, handler)
+
+int manager_process_remove_requests(Manager *manager);
return r;
SET_FOREACH(a, addresses) {
- _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(address_unrefp) Address *address = NULL;
r = address_new(&address);
if (r < 0)
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "netlink-util.h"
+#include "networkd-route.h"
+#include "networkd-route-metric.h"
+#include "parse-util.h"
+#include "string-util.h"
+
+void route_metric_done(RouteMetric *metric) {
+ assert(metric);
+
+ free(metric->metrics);
+ free(metric->metrics_set);
+ free(metric->tcp_congestion_control_algo);
+}
+
+int route_metric_copy(const RouteMetric *src, RouteMetric *dest) {
+ assert(src);
+ assert(dest);
+
+ dest->n_metrics = src->n_metrics;
+ if (src->n_metrics > 0) {
+ assert(src->n_metrics != 1);
+
+ dest->metrics = newdup(uint32_t, src->metrics, src->n_metrics);
+ if (!dest->metrics)
+ return -ENOMEM;
+ } else
+ dest->metrics = NULL;
+
+ dest->n_metrics_set = src->n_metrics_set;
+ if (src->n_metrics_set > 0) {
+ assert(src->n_metrics_set != 1);
+
+ dest->metrics_set = newdup(bool, src->metrics_set, src->n_metrics_set);
+ if (!dest->metrics_set)
+ return -ENOMEM;
+ } else
+ dest->metrics_set = NULL;
+
+ return free_and_strdup(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo);
+}
+
+void route_metric_hash_func(const RouteMetric *metric, struct siphash *state) {
+ assert(metric);
+
+ siphash24_compress_typesafe(metric->n_metrics, state);
+ siphash24_compress_safe(metric->metrics, sizeof(uint32_t) * metric->n_metrics, state);
+ siphash24_compress_string(metric->tcp_congestion_control_algo, state);
+}
+
+int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = memcmp_nn(a->metrics, a->n_metrics * sizeof(uint32_t), b->metrics, b->n_metrics * sizeof(uint32_t));
+ if (r != 0)
+ return r;
+
+ return strcmp_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo);
+}
+
+int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force) {
+ assert(metric);
+
+ if (force) {
+ if (!GREEDY_REALLOC0(metric->metrics_set, attr + 1))
+ return -ENOMEM;
+
+ metric->metrics_set[attr] = true;
+ metric->n_metrics_set = MAX(metric->n_metrics_set, (size_t) (attr + 1));
+ } else {
+ /* Do not override the values specified in conf parsers. */
+ if (metric->n_metrics_set > attr && metric->metrics_set[attr])
+ return 0;
+ }
+
+ if (value != 0) {
+ if (!GREEDY_REALLOC0(metric->metrics, attr + 1))
+ return -ENOMEM;
+
+ metric->metrics[attr] = value;
+ metric->n_metrics = MAX(metric->n_metrics, (size_t) (attr + 1));
+ return 0;
+ }
+
+ if (metric->n_metrics <= attr)
+ return 0;
+
+ metric->metrics[attr] = 0;
+
+ for (size_t i = metric->n_metrics; i > 0; i--)
+ if (metric->metrics[i-1] != 0) {
+ metric->n_metrics = i;
+ return 0;
+ }
+
+ metric->n_metrics = 0;
+ return 0;
+}
+
+static void route_metric_unset(RouteMetric *metric, uint16_t attr) {
+ assert(metric);
+
+ if (metric->n_metrics_set > attr)
+ metric->metrics_set[attr] = false;
+
+ assert_se(route_metric_set_full(metric, attr, 0, /* force = */ false) >= 0);
+}
+
+uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr) {
+ assert(metric);
+
+ if (metric->n_metrics <= attr)
+ return 0;
+
+ return metric->metrics[attr];
+}
+
+int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m) {
+ int r;
+
+ assert(metric);
+ assert(m);
+
+ if (metric->n_metrics <= 0 && isempty(metric->tcp_congestion_control_algo))
+ return 0;
+
+ r = sd_netlink_message_open_container(m, RTA_METRICS);
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < metric->n_metrics; i++) {
+ if (i == RTAX_CC_ALGO)
+ continue;
+
+ if (metric->metrics[i] == 0)
+ continue;
+
+ r = sd_netlink_message_append_u32(m, i, metric->metrics[i]);
+ if (r < 0)
+ return r;
+ }
+
+ if (!isempty(metric->tcp_congestion_control_algo)) {
+ r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, metric->tcp_congestion_control_algo);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *m) {
+ _cleanup_free_ void *data = NULL;
+ size_t len;
+ int r;
+
+ assert(metric);
+ assert(m);
+
+ r = sd_netlink_message_read_data(m, RTA_METRICS, &len, &data);
+ if (r == -ENODATA)
+ return 0;
+ if (r < 0)
+ return log_warning_errno(r, "rtnl: Could not read RTA_METRICS attribute, ignoring: %m");
+
+ for (struct rtattr *rta = data; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ size_t rta_type = RTA_TYPE(rta);
+
+ if (rta_type == RTAX_CC_ALGO) {
+ char *p = memdup_suffix0(RTA_DATA(rta), RTA_PAYLOAD(rta));
+ if (!p)
+ return log_oom();
+
+ free_and_replace(metric->tcp_congestion_control_algo, p);
+
+ } else {
+ if (RTA_PAYLOAD(rta) != sizeof(uint32_t))
+ continue;
+
+ r = route_metric_set(metric, rta_type, *(uint32_t*) RTA_DATA(rta));
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ return 0;
+}
+
+static int config_parse_route_metric_advmss_impl(
+ 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) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ uint64_t u;
+ int r;
+
+ assert(rvalue);
+
+ r = parse_size(rvalue, 1024, &u);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (u == 0 || u > UINT32_MAX) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = (uint32_t) u;
+ return 1;
+}
+
+static int config_parse_route_metric_hop_limit_impl(
+ 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) {
+
+ uint32_t k, *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+ if (k == 0 || k > 255) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = k;
+ return 1;
+}
+
+int config_parse_tcp_window(
+ 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) {
+
+ uint32_t k, *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+ if (k == 0 || k >= 1024) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = k;
+ return 1;
+}
+
+static int config_parse_route_metric_tcp_rto_impl(
+ 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) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ usec_t usec;
+ int r;
+
+ assert(rvalue);
+
+ r = parse_sec(rvalue, &usec);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (!timestamp_is_set(usec) ||
+ DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = (uint32_t) DIV_ROUND_UP(usec, USEC_PER_MSEC);
+ return 1;
+}
+
+static int config_parse_route_metric_boolean_impl(
+ 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) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = r;
+ return 1;
+}
+
+#define DEFINE_CONFIG_PARSE_ROUTE_METRIC(name, parser) \
+ int config_parse_route_metric_##name( \
+ 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) { \
+ \
+ Network *network = ASSERT_PTR(userdata); \
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL; \
+ uint16_t attr_type = ltype; \
+ int r; \
+ \
+ assert(filename); \
+ assert(section); \
+ assert(lvalue); \
+ assert(rvalue); \
+ \
+ r = route_new_static(network, filename, section_line, &route); \
+ if (r == -ENOMEM) \
+ return log_oom(); \
+ if (r < 0) { \
+ log_syntax(unit, LOG_WARNING, filename, line, r, \
+ "Failed to allocate route, ignoring assignment: %m"); \
+ return 0; \
+ } \
+ \
+ if (isempty(rvalue)) { \
+ route_metric_unset(&route->metric, attr_type); \
+ TAKE_PTR(route); \
+ return 0; \
+ } \
+ \
+ uint32_t k; \
+ r = parser(unit, filename, line, section, section_line, \
+ lvalue, /* ltype = */ 0, rvalue, \
+ &k, userdata); \
+ if (r <= 0) \
+ return r; \
+ \
+ if (route_metric_set_full( \
+ &route->metric, \
+ attr_type, \
+ k, \
+ /* force = */ true) < 0) \
+ return log_oom(); \
+ \
+ TAKE_PTR(route); \
+ return 0; \
+ }
+
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(mtu, config_parse_mtu);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(advmss, config_parse_route_metric_advmss_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(hop_limit, config_parse_route_metric_hop_limit_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_window, config_parse_tcp_window);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_rto, config_parse_route_metric_tcp_rto_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(boolean, config_parse_route_metric_boolean_impl);
+
+int config_parse_route_metric_tcp_congestion(
+ 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) {
+
+ Network *network = ASSERT_PTR(userdata);
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
+ int r;
+
+ assert(filename);
+ assert(rvalue);
+
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ r = config_parse_string(unit, filename, line, section, section_line, lvalue, 0,
+ rvalue, &route->metric.tcp_congestion_control_algo, userdata);
+ if (r <= 0)
+ return r;
+
+ TAKE_PTR(route);
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "hash-funcs.h"
+
+typedef struct RouteMetric {
+ size_t n_metrics; /* maximum metric attr type with non-zero value */
+ uint32_t *metrics; /* RTAX_*, except for RTAX_CC_ALGO */
+
+ size_t n_metrics_set;
+ bool *metrics_set; /* used by conf parsers */
+
+ char *tcp_congestion_control_algo; /* RTAX_CC_ALGO */
+} RouteMetric;
+
+#define ROUTE_METRIC_NULL ((const RouteMetric) {})
+
+void route_metric_done(RouteMetric *metric);
+int route_metric_copy(const RouteMetric *src, RouteMetric *dest);
+
+void route_metric_hash_func(const RouteMetric *metric, struct siphash *state);
+int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b);
+
+int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force);
+static inline int route_metric_set(RouteMetric *metric, uint16_t attr, uint32_t value) {
+ return route_metric_set_full(metric, attr, value, false);
+}
+uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr);
+
+int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m);
+int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *message);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_mtu);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_advmss);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_hop_limit);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_window);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_rto);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_boolean);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_congestion);
+CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/nexthop.h>
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "netlink-util.h"
+#include "networkd-network.h"
+#include "networkd-nexthop.h"
+#include "networkd-route.h"
+#include "networkd-route-nexthop.h"
+#include "networkd-route-util.h"
+#include "parse-util.h"
+#include "string-util.h"
+
+static void route_nexthop_done(RouteNextHop *nh) {
+ assert(nh);
+
+ free(nh->ifname);
+}
+
+RouteNextHop* route_nexthop_free(RouteNextHop *nh) {
+ if (!nh)
+ return NULL;
+
+ route_nexthop_done(nh);
+
+ return mfree(nh);
+}
+
+void route_nexthops_done(Route *route) {
+ assert(route);
+
+ route_nexthop_done(&route->nexthop);
+ ordered_set_free(route->nexthops);
+}
+
+static void route_nexthop_hash_func_full(const RouteNextHop *nh, struct siphash *state, bool hash_all_parameters) {
+ assert(nh);
+ assert(state);
+
+ /* See nh_comp() in net/ipv4/fib_semantics.c of the kernel. */
+
+ siphash24_compress_typesafe(nh->family, state);
+ if (!IN_SET(nh->family, AF_INET, AF_INET6))
+ return;
+
+ in_addr_hash_func(&nh->gw, nh->family, state);
+ if (!hash_all_parameters)
+ return;
+ siphash24_compress_typesafe(nh->weight, state);
+ siphash24_compress_typesafe(nh->ifindex, state);
+ if (nh->ifindex == 0)
+ siphash24_compress_string(nh->ifname, state); /* For Network or Request object. */
+}
+
+static int route_nexthop_compare_func_full(const RouteNextHop *a, const RouteNextHop *b, bool hash_all_parameters) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = CMP(a->family, b->family);
+ if (r != 0)
+ return r;
+
+ if (!IN_SET(a->family, AF_INET, AF_INET6))
+ return 0;
+
+ r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
+ if (r != 0)
+ return r;
+
+ if (!hash_all_parameters)
+ return 0;
+
+ r = CMP(a->weight, b->weight);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->ifindex, b->ifindex);
+ if (r != 0)
+ return r;
+
+ if (a->ifindex == 0) {
+ r = strcmp_ptr(a->ifname, b->ifname);
+ if (r != 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void route_nexthop_hash_func(const RouteNextHop *nh, struct siphash *state) {
+ route_nexthop_hash_func_full(nh, state, /* hash_all_parameters = */ true);
+}
+
+static int route_nexthop_compare_func(const RouteNextHop *a, const RouteNextHop *b) {
+ return route_nexthop_compare_func_full(a, b, /* hash_all_parameters = */ true);
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+ route_nexthop_hash_ops,
+ RouteNextHop,
+ route_nexthop_hash_func,
+ route_nexthop_compare_func,
+ route_nexthop_free);
+
+static size_t route_n_nexthops(const Route *route) {
+ assert(route);
+
+ if (route->nexthop_id != 0 || route_type_is_reject(route))
+ return 0;
+
+ if (ordered_set_isempty(route->nexthops))
+ return 1;
+
+ return ordered_set_size(route->nexthops);
+}
+
+void route_nexthops_hash_func(const Route *route, struct siphash *state) {
+ assert(route);
+
+ size_t nhs = route_n_nexthops(route);
+ siphash24_compress_typesafe(nhs, state);
+
+ switch (nhs) {
+ case 0:
+ siphash24_compress_typesafe(route->nexthop_id, state);
+ return;
+
+ case 1:
+ route_nexthop_hash_func_full(&route->nexthop, state, /* hash_all_parameters = */ false);
+ return;
+
+ default: {
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops)
+ route_nexthop_hash_func(nh, state);
+ }}
+}
+
+int route_nexthops_compare_func(const Route *a, const Route *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ size_t a_nhs = route_n_nexthops(a);
+ size_t b_nhs = route_n_nexthops(b);
+ r = CMP(a_nhs, b_nhs);
+ if (r != 0)
+ return r;
+
+ switch (a_nhs) {
+ case 0:
+ return CMP(a->nexthop_id, b->nexthop_id);
+
+ case 1:
+ return route_nexthop_compare_func_full(&a->nexthop, &b->nexthop, /* hash_all_parameters = */ false);
+
+ default: {
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, a->nexthops) {
+ r = CMP(nh, (RouteNextHop*) ordered_set_get(a->nexthops, nh));
+ if (r != 0)
+ return r;
+ }
+ return 0;
+ }}
+}
+
+int route_nexthop_get_link(Manager *manager, Link *link, const RouteNextHop *nh, Link **ret) {
+ assert(manager);
+ assert(nh);
+
+ if (nh->ifindex > 0)
+ return link_get_by_index(manager, nh->ifindex, ret);
+ if (nh->ifname)
+ return link_get_by_name(manager, nh->ifname, ret);
+
+ if (link) {
+ if (ret)
+ *ret = link;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static bool route_nexthop_is_ready_to_configure(const RouteNextHop *nh, Link *link, bool onlink) {
+ assert(nh);
+ assert(link);
+
+ if (route_nexthop_get_link(link->manager, link, nh, &link))
+ return false;
+
+ if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ true))
+ return false;
+
+ /* If the interface is not managed by us, we request that the interface has carrier.
+ * That is, ConfigureWithoutCarrier=no is the default even for unamanaged interfaces. */
+ if (!link->network && !link_has_carrier(link))
+ return false;
+
+ return gateway_is_ready(link, onlink, nh->family, &nh->gw);
+}
+
+int route_nexthops_is_ready_to_configure(const Route *route, Link *link) {
+ int r;
+
+ assert(route);
+ assert(link);
+
+ Manager *manager = ASSERT_PTR(link->manager);
+
+ if (route->nexthop_id != 0) {
+ struct nexthop_grp *nhg;
+ NextHop *nh;
+
+ r = nexthop_is_ready(manager, route->nexthop_id, &nh);
+ if (r <= 0)
+ return r;
+
+ HASHMAP_FOREACH(nhg, nh->group) {
+ r = nexthop_is_ready(manager, nhg->id, NULL);
+ if (r <= 0)
+ return r;
+ }
+
+ return true;
+ }
+
+ if (route_type_is_reject(route))
+ return true;
+
+ if (ordered_set_isempty(route->nexthops))
+ return route_nexthop_is_ready_to_configure(&route->nexthop, link, FLAGS_SET(route->flags, RTNH_F_ONLINK));
+
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops)
+ if (!route_nexthop_is_ready_to_configure(nh, link, FLAGS_SET(route->flags, RTNH_F_ONLINK)))
+ return false;
+
+ return true;
+}
+
+int route_nexthops_to_string(const Route *route, char **ret) {
+ _cleanup_free_ char *buf = NULL;
+ int r;
+
+ assert(route);
+ assert(ret);
+
+ if (route->nexthop_id != 0) {
+ if (asprintf(&buf, "nexthop: %"PRIu32, route->nexthop_id) < 0)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ if (route_type_is_reject(route)) {
+ buf = strdup("gw: n/a");
+ if (!buf)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ if (ordered_set_isempty(route->nexthops)) {
+ if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw))
+ buf = strjoin("gw: ", IN_ADDR_TO_STRING(route->nexthop.family, &route->nexthop.gw));
+ else if (route->gateway_from_dhcp_or_ra) {
+ if (route->nexthop.family == AF_INET)
+ buf = strdup("gw: _dhcp4");
+ else if (route->nexthop.family == AF_INET6)
+ buf = strdup("gw: _ipv6ra");
+ else
+ buf = strdup("gw: _dhcp");
+ } else
+ buf = strdup("gw: n/a");
+ if (!buf)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops) {
+ const char *s = in_addr_is_set(nh->family, &nh->gw) ? IN_ADDR_TO_STRING(nh->family, &nh->gw) : NULL;
+
+ if (nh->ifindex > 0)
+ r = strextendf_with_separator(&buf, ",", "%s@%i:%"PRIu32, strempty(s), nh->ifindex, nh->weight + 1);
+ else if (nh->ifname)
+ r = strextendf_with_separator(&buf, ",", "%s@%s:%"PRIu32, strempty(s), nh->ifname, nh->weight + 1);
+ else
+ r = strextendf_with_separator(&buf, ",", "%s:%"PRIu32, strempty(s), nh->weight + 1);
+ if (r < 0)
+ return r;
+ }
+
+ char *p = strjoin("gw: ", strna(buf));
+ if (!p)
+ return -ENOMEM;
+
+ *ret = p;
+ return 0;
+}
+
+static int append_nexthop_one(Link *link, const Route *route, const RouteNextHop *nh, struct rtattr **rta, size_t offset) {
+ struct rtnexthop *rtnh;
+ struct rtattr *new_rta;
+ int r;
+
+ assert(route);
+ assert(IN_SET(route->family, AF_INET, AF_INET6));
+ assert(nh);
+ assert(rta);
+ assert(*rta);
+
+ if (nh->ifindex <= 0) {
+ assert(link);
+ assert(link->manager);
+
+ r = route_nexthop_get_link(link->manager, link, nh, &link);
+ if (r < 0)
+ return r;
+ }
+
+ new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop)));
+ if (!new_rta)
+ return -ENOMEM;
+ *rta = new_rta;
+
+ rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
+ *rtnh = (struct rtnexthop) {
+ .rtnh_len = sizeof(*rtnh),
+ .rtnh_ifindex = nh->ifindex > 0 ? nh->ifindex : link->ifindex,
+ .rtnh_hops = nh->weight,
+ };
+
+ (*rta)->rta_len += sizeof(struct rtnexthop);
+
+ if (nh->family == route->family) {
+ r = rtattr_append_attribute(rta, RTA_GATEWAY, &nh->gw, FAMILY_ADDRESS_SIZE(nh->family));
+ if (r < 0)
+ goto clear;
+
+ rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
+ rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(nh->family));
+
+ } else if (nh->family == AF_INET6) {
+ assert(route->family == AF_INET);
+
+ r = rtattr_append_attribute(rta, RTA_VIA,
+ &(RouteVia) {
+ .family = nh->family,
+ .address = nh->gw,
+ }, sizeof(RouteVia));
+ if (r < 0)
+ goto clear;
+
+ rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
+ rtnh->rtnh_len += RTA_SPACE(sizeof(RouteVia));
+
+ } else if (nh->family == AF_INET)
+ assert_not_reached();
+
+ return 0;
+
+clear:
+ (*rta)->rta_len -= sizeof(struct rtnexthop);
+ return r;
+}
+
+static int netlink_message_append_multipath_route(Link *link, const Route *route, sd_netlink_message *message) {
+ _cleanup_free_ struct rtattr *rta = NULL;
+ size_t offset;
+ int r;
+
+ assert(route);
+ assert(message);
+
+ if (ordered_set_isempty(route->nexthops))
+ return 0;
+
+ rta = new(struct rtattr, 1);
+ if (!rta)
+ return -ENOMEM;
+
+ *rta = (struct rtattr) {
+ .rta_type = RTA_MULTIPATH,
+ .rta_len = RTA_LENGTH(0),
+ };
+ offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta;
+
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops) {
+ struct rtnexthop *rtnh;
+
+ r = append_nexthop_one(link, route, nh, &rta, offset);
+ if (r < 0)
+ return r;
+
+ rtnh = (struct rtnexthop *)((uint8_t *) rta + offset);
+ offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta;
+ }
+
+ return sd_netlink_message_append_data(message, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta));
+}
+
+int route_nexthops_set_netlink_message(Link *link, const Route *route, sd_netlink_message *message) {
+ int r;
+
+ assert(route);
+ assert(IN_SET(route->family, AF_INET, AF_INET6));
+ assert(message);
+
+ if (route->nexthop_id != 0)
+ return sd_netlink_message_append_u32(message, RTA_NH_ID, route->nexthop_id);
+
+ if (route_type_is_reject(route))
+ return 0;
+
+ if (ordered_set_isempty(route->nexthops)) {
+
+ if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) {
+ if (route->nexthop.family == route->family)
+ r = netlink_message_append_in_addr_union(message, RTA_GATEWAY, route->nexthop.family, &route->nexthop.gw);
+ else {
+ assert(route->family == AF_INET);
+ r = sd_netlink_message_append_data(message, RTA_VIA,
+ &(const RouteVia) {
+ .family = route->nexthop.family,
+ .address = route->nexthop.gw,
+ }, sizeof(RouteVia));
+ }
+ if (r < 0)
+ return r;
+ }
+
+ return sd_netlink_message_append_u32(message, RTA_OIF, route->nexthop.ifindex > 0 ? route->nexthop.ifindex : ASSERT_PTR(link)->ifindex);
+ }
+
+ return netlink_message_append_multipath_route(link, route, message);
+}
+
+static int route_parse_nexthops(Route *route, const struct rtnexthop *rtnh, size_t size) {
+ _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL;
+ int r;
+
+ assert(route);
+ assert(IN_SET(route->family, AF_INET, AF_INET6));
+ assert(rtnh);
+
+ if (size < sizeof(struct rtnexthop))
+ return -EBADMSG;
+
+ for (; size >= sizeof(struct rtnexthop); ) {
+ _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
+
+ if (NLMSG_ALIGN(rtnh->rtnh_len) > size)
+ return -EBADMSG;
+
+ if (rtnh->rtnh_len < sizeof(struct rtnexthop))
+ return -EBADMSG;
+
+ nh = new(RouteNextHop, 1);
+ if (!nh)
+ return -ENOMEM;
+
+ *nh = (RouteNextHop) {
+ .ifindex = rtnh->rtnh_ifindex,
+ .weight = rtnh->rtnh_hops,
+ };
+
+ if (rtnh->rtnh_len > sizeof(struct rtnexthop)) {
+ size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop);
+ bool have_gw = false;
+
+ for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+
+ switch (attr->rta_type) {
+ case RTA_GATEWAY:
+ if (have_gw)
+ return -EBADMSG;
+
+ if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(route->family)))
+ return -EBADMSG;
+
+ nh->family = route->family;
+ memcpy(&nh->gw, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(nh->family));
+ have_gw = true;
+ break;
+
+ case RTA_VIA:
+ if (have_gw)
+ return -EBADMSG;
+
+ if (route->family != AF_INET)
+ return -EBADMSG;
+
+ if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia)))
+ return -EBADMSG;
+
+ RouteVia *via = RTA_DATA(attr);
+ if (via->family != AF_INET6)
+ return -EBADMSG;
+
+ nh->family = via->family;
+ nh->gw = via->address;
+ have_gw = true;
+ break;
+ }
+ }
+ }
+
+ r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh);
+ assert(r != 0);
+ if (r > 0)
+ TAKE_PTR(nh);
+ else if (r != -EEXIST)
+ return r;
+
+ size -= NLMSG_ALIGN(rtnh->rtnh_len);
+ rtnh = RTNH_NEXT(rtnh);
+ }
+
+ ordered_set_free(route->nexthops);
+ route->nexthops = TAKE_PTR(nexthops);
+ return 0;
+}
+
+int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message) {
+ int r;
+
+ assert(route);
+ assert(message);
+
+ r = sd_netlink_message_read_u32(message, RTA_NH_ID, &route->nexthop_id);
+ if (r < 0 && r != -ENODATA)
+ return log_warning_errno(r, "rtnl: received route message with invalid nexthop id, ignoring: %m");
+
+ if (route->nexthop_id != 0 || route_type_is_reject(route))
+ /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's
+ * fib6_nh_init() in net/ipv6/route.c. However, we'd like to make it consistent with IPv4
+ * routes. Hence, skip reading of RTA_OIF. */
+ return 0;
+
+ uint32_t ifindex;
+ r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex);
+ if (r >= 0)
+ route->nexthop.ifindex = (int) ifindex;
+ else if (r != -ENODATA)
+ return log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m");
+
+ if (route->nexthop.ifindex > 0) {
+ r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, route->family, &route->nexthop.gw);
+ if (r >= 0) {
+ route->nexthop.family = route->family;
+ return 0;
+ }
+ if (r != -ENODATA)
+ return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m");
+
+ if (route->family != AF_INET)
+ return 0;
+
+ RouteVia via;
+ r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via);
+ if (r >= 0) {
+ route->nexthop.family = via.family;
+ route->nexthop.gw = via.address;
+ return 0;
+ }
+ if (r != -ENODATA)
+ return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m");
+
+ return 0;
+ }
+
+ size_t rta_len;
+ _cleanup_free_ void *rta = NULL;
+ r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta);
+ if (r == -ENODATA)
+ return 0;
+ if (r < 0)
+ return log_warning_errno(r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m");
+
+ r = route_parse_nexthops(route, rta, rta_len);
+ if (r < 0)
+ return log_warning_errno(r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m");
+
+ return 0;
+}
+
+int route_section_verify_nexthops(Route *route) {
+ assert(route);
+ assert(route->section);
+
+ if (route->gateway_from_dhcp_or_ra) {
+ assert(route->network);
+
+ if (route->nexthop.family == AF_UNSPEC)
+ /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */
+ switch (route->family) {
+ case AF_UNSPEC:
+ log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
+ "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".",
+ route->section->filename, route->section->line);
+
+ route->nexthop.family = route->family = AF_INET;
+ break;
+ case AF_INET:
+ case AF_INET6:
+ log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
+ "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.",
+ route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra");
+
+ route->nexthop.family = route->family;
+ break;
+ default:
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: Invalid route family. Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+ }
+
+ if (route->nexthop.family == AF_INET && !FLAGS_SET(route->network->dhcp, ADDRESS_FAMILY_IPV4))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. "
+ "Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+
+ if (route->nexthop.family == AF_INET6 && !route->network->ipv6_accept_ra)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. "
+ "Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+ }
+
+ /* When only Gateway= is specified, assume the route family based on the Gateway address. */
+ if (route->family == AF_UNSPEC)
+ route->family = route->nexthop.family;
+
+ if (route->family == AF_UNSPEC) {
+ assert(route->section);
+
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: Route section without Gateway=, Destination=, Source=, "
+ "or PreferredSource= field configured. "
+ "Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+ }
+
+ if (route->gateway_onlink < 0 && in_addr_is_set(route->nexthop.family, &route->nexthop.gw) &&
+ route->network && ordered_hashmap_isempty(route->network->addresses_by_section)) {
+ /* If no address is configured, in most cases the gateway cannot be reachable.
+ * TODO: we may need to improve the condition above. */
+ log_warning("%s: Gateway= without static address configured. "
+ "Enabling GatewayOnLink= option.",
+ route->section->filename);
+ route->gateway_onlink = true;
+ }
+
+ if (route->gateway_onlink >= 0)
+ SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink);
+
+ if (route->family == AF_INET6) {
+ if (route->nexthop.family == AF_INET)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: IPv4 gateway is configured for IPv6 route. "
+ "Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops)
+ if (nh->family == AF_INET)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: IPv4 multipath route is specified for IPv6 route. "
+ "Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+ }
+
+ if (route->nexthop_id != 0 &&
+ (route->gateway_from_dhcp_or_ra ||
+ in_addr_is_set(route->nexthop.family, &route->nexthop.gw) ||
+ !ordered_set_isempty(route->nexthops)))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. "
+ "Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+
+ if (route_type_is_reject(route) &&
+ (route->gateway_from_dhcp_or_ra ||
+ in_addr_is_set(route->nexthop.family, &route->nexthop.gw) ||
+ !ordered_set_isempty(route->nexthops)))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: reject type route cannot be specified with Gateway= or MultiPathRoute=. "
+ "Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+
+ if ((route->gateway_from_dhcp_or_ra ||
+ in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) &&
+ !ordered_set_isempty(route->nexthops))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: Gateway= cannot be specified with MultiPathRoute=. "
+ "Ignoring [Route] section from line %u.",
+ route->section->filename, route->section->line);
+
+ return 0;
+}
+
+int config_parse_gateway(
+ 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) {
+
+ Network *network = userdata;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "Network")) {
+ /* we are not in an Route section, so use line number instead */
+ r = route_new_static(network, filename, line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+ } else {
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ if (isempty(rvalue)) {
+ route->gateway_from_dhcp_or_ra = false;
+ route->nexthop.family = AF_UNSPEC;
+ route->nexthop.gw = IN_ADDR_NULL;
+ TAKE_PTR(route);
+ return 0;
+ }
+
+ if (streq(rvalue, "_dhcp")) {
+ route->gateway_from_dhcp_or_ra = true;
+ route->nexthop.family = AF_UNSPEC;
+ route->nexthop.gw = IN_ADDR_NULL;
+ TAKE_PTR(route);
+ return 0;
+ }
+
+ if (streq(rvalue, "_dhcp4")) {
+ route->gateway_from_dhcp_or_ra = true;
+ route->nexthop.family = AF_INET;
+ route->nexthop.gw = IN_ADDR_NULL;
+ TAKE_PTR(route);
+ return 0;
+ }
+
+ if (streq(rvalue, "_ipv6ra")) {
+ route->gateway_from_dhcp_or_ra = true;
+ route->nexthop.family = AF_INET6;
+ route->nexthop.gw = IN_ADDR_NULL;
+ TAKE_PTR(route);
+ return 0;
+ }
+ }
+
+ r = in_addr_from_string_auto(rvalue, &route->nexthop.family, &route->nexthop.gw);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
+ return 0;
+ }
+
+ route->gateway_from_dhcp_or_ra = false;
+ TAKE_PTR(route);
+ return 0;
+}
+
+int config_parse_route_gateway_onlink(
+ 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) {
+
+ Network *network = userdata;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ r = config_parse_tristate(unit, filename, line, section, section_line, lvalue, ltype, rvalue,
+ &route->gateway_onlink, network);
+ if (r <= 0)
+ return r;
+
+ TAKE_PTR(route);
+ return 0;
+}
+
+int config_parse_route_nexthop(
+ 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) {
+
+ Network *network = userdata;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
+ uint32_t id;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ if (isempty(rvalue)) {
+ route->nexthop_id = 0;
+ TAKE_PTR(route);
+ return 0;
+ }
+
+ r = safe_atou32(rvalue, &id);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (id == 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ route->nexthop_id = id;
+ TAKE_PTR(route);
+ return 0;
+}
+
+int config_parse_multipath_route(
+ 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) {
+
+ _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
+ _cleanup_free_ char *word = NULL;
+ Network *network = userdata;
+ const char *p;
+ char *dev;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ if (isempty(rvalue)) {
+ route->nexthops = ordered_set_free(route->nexthops);
+ TAKE_PTR(route);
+ return 0;
+ }
+
+ nh = new0(RouteNextHop, 1);
+ if (!nh)
+ return log_oom();
+
+ p = rvalue;
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r <= 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid multipath route option, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ dev = strchr(word, '@');
+ if (dev) {
+ *dev++ = '\0';
+
+ r = parse_ifindex(dev);
+ if (r > 0)
+ nh->ifindex = r;
+ else {
+ if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue);
+ return 0;
+ }
+
+ nh->ifname = strdup(dev);
+ if (!nh->ifname)
+ return log_oom();
+ }
+ }
+
+ r = in_addr_from_string_auto(word, &nh->family, &nh->gw);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ if (!isempty(p)) {
+ r = safe_atou32(p, &nh->weight);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid multipath route weight, ignoring assignment: %s", p);
+ return 0;
+ }
+ /* ip command takes weight in the range 1…255, while kernel takes the value in the
+ * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip
+ * command uses, then networkd decreases by one and stores it to match the range which
+ * kernel uses. */
+ if (nh->weight == 0 || nh->weight > 256) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid multipath route weight, ignoring assignment: %s", p);
+ return 0;
+ }
+ nh->weight--;
+ }
+
+ r = ordered_set_ensure_put(&route->nexthops, &route_nexthop_hash_ops, nh);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to store multipath route, ignoring assignment: %m");
+ return 0;
+ }
+
+ TAKE_PTR(nh);
+ TAKE_PTR(route);
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "in-addr-util.h"
+#include "macro.h"
+#include "siphash24.h"
+
+typedef struct Link Link;
+typedef struct Manager Manager;
+typedef struct Route Route;
+
+typedef struct RouteNextHop {
+ int family; /* used in RTA_VIA (IPv4 only) */
+ union in_addr_union gw; /* RTA_GATEWAY or RTA_VIA (IPv4 only) */
+ uint32_t weight; /* rtnh_hops */
+ int ifindex; /* RTA_OIF(u32) or rtnh_ifindex */
+ char *ifname; /* only used by Route object owned by Network object */
+ /* unsupported attributes: RTA_FLOW (IPv4 only), RTA_ENCAP_TYPE, RTA_ENCAP. */
+} RouteNextHop;
+
+RouteNextHop* route_nexthop_free(RouteNextHop *nh);
+DEFINE_TRIVIAL_CLEANUP_FUNC(RouteNextHop*, route_nexthop_free);
+
+void route_nexthops_done(Route *route);
+
+void route_nexthops_hash_func(const Route *route, struct siphash *state);
+int route_nexthops_compare_func(const Route *a, const Route *b);
+
+int route_nexthop_get_link(Manager *manager, Link *link, const RouteNextHop *nh, Link **ret);
+int route_nexthops_is_ready_to_configure(const Route *route, Link *link);
+
+int route_nexthops_to_string(const Route *route, char **ret);
+
+int route_nexthops_set_netlink_message(Link *link, const Route *route, sd_netlink_message *message);
+int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message);
+
+int route_section_verify_nexthops(Route *route);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_gateway);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_gateway_onlink);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop);
+CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route);
return cached;
}
+bool route_type_is_reject(const Route *route) {
+ assert(route);
+
+ return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW);
+}
+
static bool route_lifetime_is_valid(const Route *route) {
assert(route);
continue;
if (route->scope != RT_SCOPE_UNIVERSE)
continue;
- if (!in_addr_is_set(route->gw_family, &route->gw))
+ if (!in_addr_is_set(route->nexthop.family, &route->nexthop.gw))
continue;
/* Found a default gateway. */
/* If we have already found another gw, then let's compare their weight and priority. */
if (*gw) {
- if (route->gw_weight > (*gw)->gw_weight)
+ if (route->nexthop.weight > (*gw)->nexthop.weight)
continue;
if (route->priority >= (*gw)->priority)
continue;
unsigned routes_max(void);
+bool route_type_is_reject(const Route *route);
+
bool link_find_default_gateway(Link *link, int family, Route **gw);
static inline bool link_has_default_gateway(Link *link, int family) {
return link_find_default_gateway(link, family, NULL);
#include "vrf.h"
#include "wireguard.h"
-int route_new(Route **ret) {
- _cleanup_(route_freep) Route *route = NULL;
-
- route = new(Route, 1);
- if (!route)
- return -ENOMEM;
-
- *route = (Route) {
- .family = AF_UNSPEC,
- .scope = RT_SCOPE_UNIVERSE,
- .protocol = RTPROT_UNSPEC,
- .type = RTN_UNICAST,
- .table = RT_TABLE_MAIN,
- .lifetime_usec = USEC_INFINITY,
- .quickack = -1,
- .fast_open_no_cookie = -1,
- .gateway_onlink = -1,
- };
-
- *ret = TAKE_PTR(route);
-
- return 0;
-}
-
-static int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) {
- _cleanup_(config_section_freep) ConfigSection *n = NULL;
- _cleanup_(route_freep) Route *route = NULL;
- int r;
-
- assert(network);
- assert(ret);
- assert(filename);
- assert(section_line > 0);
-
- r = config_section_new(filename, section_line, &n);
- if (r < 0)
- return r;
-
- route = hashmap_get(network->routes_by_section, n);
- if (route) {
- *ret = TAKE_PTR(route);
- return 0;
- }
-
- if (hashmap_size(network->routes_by_section) >= routes_max())
- return -E2BIG;
-
- r = route_new(&route);
- if (r < 0)
- return r;
-
- route->protocol = RTPROT_STATIC;
- route->network = network;
- route->section = TAKE_PTR(n);
- route->source = NETWORK_CONFIG_SOURCE_STATIC;
-
- r = hashmap_ensure_put(&network->routes_by_section, &config_section_hash_ops, route->section, route);
- if (r < 0)
- return r;
-
- *ret = TAKE_PTR(route);
- return 0;
-}
-
-Route *route_free(Route *route) {
+Route* route_free(Route *route) {
if (!route)
return NULL;
hashmap_remove(route->network->routes_by_section, route->section);
}
- config_section_free(route->section);
-
if (route->link)
set_remove(route->link->routes, route);
if (route->manager)
set_remove(route->manager->routes, route);
- ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free);
+ if (route->wireguard)
+ set_remove(route->wireguard->routes, route);
+ config_section_free(route->section);
+ route_nexthops_done(route);
+ route_metric_done(&route->metric);
sd_event_source_disable_unref(route->expire);
- free(route->tcp_congestion_control_algo);
-
return mfree(route);
}
switch (route->family) {
case AF_INET:
- case AF_INET6:
- siphash24_compress_typesafe(route->dst_prefixlen, state);
+ /* First, the table, destination prefix, priority, and tos (dscp), are used to find routes.
+ * See fib_table_insert(), fib_find_node(), and fib_find_alias() in net/ipv4/fib_trie.c of the kernel. */
+ siphash24_compress_typesafe(route->table, state);
in_addr_hash_func(&route->dst, route->family, state);
+ siphash24_compress_typesafe(route->dst_prefixlen, state);
+ siphash24_compress_typesafe(route->priority, state);
+ siphash24_compress_typesafe(route->tos, state);
- siphash24_compress_typesafe(route->src_prefixlen, state);
- in_addr_hash_func(&route->src, route->family, state);
+ /* Then, protocol, scope, type, flags, prefsrc, metrics (RTAX_* attributes), and nexthops (gateways)
+ * are used to find routes. See fib_find_info() in net/ipv4/fib_semantics.c of the kernel. */
+ siphash24_compress_typesafe(route->protocol, state);
+ siphash24_compress_typesafe(route->scope, state);
+ siphash24_compress_typesafe(route->type, state);
+ unsigned flags = route->flags & ~RTNH_COMPARE_MASK;
+ siphash24_compress_typesafe(flags, state);
+ in_addr_hash_func(&route->prefsrc, route->family, state);
- siphash24_compress_typesafe(route->gw_family, state);
- if (IN_SET(route->gw_family, AF_INET, AF_INET6)) {
- in_addr_hash_func(&route->gw, route->gw_family, state);
- siphash24_compress_typesafe(route->gw_weight, state);
- }
+ /* nexthops (id, number of nexthops, nexthop) */
+ route_nexthops_hash_func(route, state);
- in_addr_hash_func(&route->prefsrc, route->family, state);
+ /* metrics */
+ route_metric_hash_func(&route->metric, state);
+ break;
- siphash24_compress_typesafe(route->tos, state);
- siphash24_compress_typesafe(route->priority, state);
+ case AF_INET6:
+ /* First, table and destination prefix are used for classifying routes.
+ * See fib6_add() and fib6_add_1() in net/ipv6/ip6_fib.c of the kernel. */
siphash24_compress_typesafe(route->table, state);
- siphash24_compress_typesafe(route->protocol, state);
- siphash24_compress_typesafe(route->scope, state);
- siphash24_compress_typesafe(route->type, state);
+ in_addr_hash_func(&route->dst, route->family, state);
+ siphash24_compress_typesafe(route->dst_prefixlen, state);
- siphash24_compress_typesafe(route->initcwnd, state);
- siphash24_compress_typesafe(route->initrwnd, state);
+ /* Then, source prefix is used. See fib6_add(). */
+ in_addr_hash_func(&route->src, route->family, state);
+ siphash24_compress_typesafe(route->src_prefixlen, state);
+
+ /* See fib6_add_rt2node(). */
+ siphash24_compress_typesafe(route->priority, state);
- siphash24_compress_typesafe(route->advmss, state);
- siphash24_compress_typesafe(route->nexthop_id, state);
+ /* See rt6_duplicate_nexthop() in include/net/ip6_route.h of the kernel.
+ * Here, we hash nexthop in a similar way as the one for IPv4. */
+ route_nexthops_hash_func(route, state);
+ /* Unlike IPv4 routes, metrics are not taken into account. */
break;
+
default:
/* treat any other address family as AF_UNSPEC */
break;
switch (a->family) {
case AF_INET:
- case AF_INET6:
- r = CMP(a->dst_prefixlen, b->dst_prefixlen);
+ r = CMP(a->table, b->table);
if (r != 0)
return r;
if (r != 0)
return r;
- r = CMP(a->src_prefixlen, b->src_prefixlen);
+ r = CMP(a->dst_prefixlen, b->dst_prefixlen);
if (r != 0)
return r;
- r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family));
+ r = CMP(a->priority, b->priority);
if (r != 0)
return r;
- r = CMP(a->gw_family, b->gw_family);
+ r = CMP(a->tos, b->tos);
if (r != 0)
return r;
- if (IN_SET(a->gw_family, AF_INET, AF_INET6)) {
- r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
- if (r != 0)
- return r;
-
- r = CMP(a->gw_weight, b->gw_weight);
- if (r != 0)
- return r;
- }
+ r = CMP(a->protocol, b->protocol);
+ if (r != 0)
+ return r;
- r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family));
+ r = CMP(a->scope, b->scope);
if (r != 0)
return r;
- r = CMP(a->tos, b->tos);
+ r = CMP(a->type, b->type);
if (r != 0)
return r;
- r = CMP(a->priority, b->priority);
+ r = CMP(a->flags & ~RTNH_COMPARE_MASK, b->flags & ~RTNH_COMPARE_MASK);
if (r != 0)
return r;
- r = CMP(a->table, b->table);
+ r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family));
if (r != 0)
return r;
- r = CMP(a->protocol, b->protocol);
+ r = route_nexthops_compare_func(a, b);
if (r != 0)
return r;
- r = CMP(a->scope, b->scope);
+ return route_metric_compare_func(&a->metric, &b->metric);
+
+ case AF_INET6:
+ r = CMP(a->table, b->table);
if (r != 0)
return r;
- r = CMP(a->type, b->type);
+ r = memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family));
if (r != 0)
return r;
- r = CMP(a->initcwnd, b->initcwnd);
+ r = CMP(a->dst_prefixlen, b->dst_prefixlen);
if (r != 0)
return r;
- r = CMP(a->initrwnd, b->initrwnd);
+ r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family));
if (r != 0)
return r;
- r = CMP(a->advmss, b->advmss);
+ r = CMP(a->src_prefixlen, b->src_prefixlen);
if (r != 0)
return r;
- r = CMP(a->nexthop_id, b->nexthop_id);
+ r = CMP(a->priority, b->priority);
if (r != 0)
return r;
- return 0;
+ return route_nexthops_compare_func(a, b);
+
default:
/* treat any other address family as AF_UNSPEC */
return 0;
route_compare_func,
route_free);
-static bool route_type_is_reject(const Route *route) {
- assert(route);
+int route_new(Route **ret) {
+ _cleanup_(route_freep) Route *route = NULL;
+
+ route = new(Route, 1);
+ if (!route)
+ return -ENOMEM;
+
+ *route = (Route) {
+ .family = AF_UNSPEC,
+ .scope = RT_SCOPE_UNIVERSE,
+ .protocol = RTPROT_UNSPEC,
+ .type = RTN_UNICAST,
+ .table = RT_TABLE_MAIN,
+ .lifetime_usec = USEC_INFINITY,
+ .gateway_onlink = -1,
+ };
+
+ *ret = TAKE_PTR(route);
- return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW);
+ return 0;
}
-static bool route_needs_convert(const Route *route) {
- assert(route);
+int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) {
+ _cleanup_(config_section_freep) ConfigSection *n = NULL;
+ _cleanup_(route_freep) Route *route = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(filename);
+ assert(section_line > 0);
+
+ r = config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ route = hashmap_get(network->routes_by_section, n);
+ if (route) {
+ *ret = TAKE_PTR(route);
+ return 0;
+ }
+
+ if (hashmap_size(network->routes_by_section) >= routes_max())
+ return -E2BIG;
+
+ r = route_new(&route);
+ if (r < 0)
+ return r;
+
+ route->protocol = RTPROT_STATIC;
+ route->network = network;
+ route->section = TAKE_PTR(n);
+ route->source = NETWORK_CONFIG_SOURCE_STATIC;
+
+ r = hashmap_ensure_put(&network->routes_by_section, &config_section_hash_ops, route->section, route);
+ if (r < 0)
+ return r;
- return route->nexthop_id > 0 || !ordered_set_isempty(route->multipath_routes);
+ *ret = TAKE_PTR(route);
+ return 0;
}
static int route_add(Manager *manager, Link *link, Route *route) {
return 0;
}
+static int route_get_link(Manager *manager, const Route *route, Link **ret) {
+ int r;
+
+ assert(manager);
+ assert(route);
+
+ if (route->nexthop_id != 0) {
+ NextHop *nh;
+
+ r = nexthop_get_by_id(manager, route->nexthop_id, &nh);
+ if (r < 0)
+ return r;
+
+ return link_get_by_index(manager, nh->ifindex, ret);
+ }
+
+ return route_nexthop_get_link(manager, NULL, &route->nexthop, ret);
+}
+
int route_dup(const Route *src, Route **ret) {
_cleanup_(route_freep) Route *dest = NULL;
int r;
return -ENOMEM;
/* Unset all pointers */
+ dest->manager = NULL;
dest->network = NULL;
+ dest->wireguard = NULL;
dest->section = NULL;
dest->link = NULL;
- dest->manager = NULL;
- dest->multipath_routes = NULL;
+ dest->nexthops = NULL;
+ dest->metric = ROUTE_METRIC_NULL;
dest->expire = NULL;
- dest->tcp_congestion_control_algo = NULL;
- r = free_and_strdup(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo);
+ r = route_metric_copy(&src->metric, &dest->metric);
if (r < 0)
return r;
assert(nh);
assert(hashmap_isempty(nh->group));
- route->gw_family = nh->family;
- route->gw = nh->gw;
+ route->nexthop.family = nh->family;
+ route->nexthop.gw = nh->gw;
if (nh_weight != UINT8_MAX)
- route->gw_weight = nh_weight;
+ route->nexthop.weight = nh_weight;
if (nh->blackhole)
route->type = RTN_BLACKHOLE;
}
-static void route_apply_multipath_route(Route *route, const MultipathRoute *m) {
+static void route_apply_route_nexthop(Route *route, const RouteNextHop *nh) {
assert(route);
- assert(m);
-
- route->gw_family = m->gateway.family;
- route->gw = m->gateway.address;
- route->gw_weight = m->weight;
-}
-
-static int multipath_route_get_link(Manager *manager, const MultipathRoute *m, Link **ret) {
- int r;
-
- assert(manager);
- assert(m);
-
- if (m->ifname) {
- r = link_get_by_name(manager, m->ifname, ret);
- return r < 0 ? r : 1;
-
- } else if (m->ifindex > 0) { /* Always ignore ifindex if ifname is set. */
- r = link_get_by_index(manager, m->ifindex, ret);
- return r < 0 ? r : 1;
- }
+ assert(nh);
- if (ret)
- *ret = NULL;
- return 0;
+ route->nexthop.family = nh->family;
+ route->nexthop.gw = nh->gw;
+ route->nexthop.weight = nh->weight;
}
typedef struct ConvertedRoutes {
return 0;
}
-static int route_convert(Manager *manager, const Route *route, ConvertedRoutes **ret) {
+static bool route_needs_convert(const Route *route) {
+ assert(route);
+
+ return route->nexthop_id > 0 || !ordered_set_isempty(route->nexthops);
+}
+
+static int route_convert(Manager *manager, Link *link, const Route *route, ConvertedRoutes **ret) {
_cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL;
int r;
assert(route);
assert(ret);
+ /* link may be NULL */
+
if (!route_needs_convert(route)) {
*ret = NULL;
return 0;
}
- assert(!ordered_set_isempty(route->multipath_routes));
+ assert(!ordered_set_isempty(route->nexthops));
- r = converted_routes_new(ordered_set_size(route->multipath_routes), &c);
+ r = converted_routes_new(ordered_set_size(route->nexthops), &c);
if (r < 0)
return r;
size_t i = 0;
- MultipathRoute *m;
- ORDERED_SET_FOREACH(m, route->multipath_routes) {
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops) {
r = route_dup(route, &c->routes[i]);
if (r < 0)
return r;
- route_apply_multipath_route(c->routes[i], m);
+ route_apply_route_nexthop(c->routes[i], nh);
- r = multipath_route_get_link(manager, m, &c->links[i]);
+ r = route_nexthop_get_link(manager, link, nh, &c->links[i]);
if (r < 0)
return r;
}
}
-static void log_route_debug(const Route *route, const char *str, const Link *link, const Manager *manager) {
- _cleanup_free_ char *state = NULL, *gw_alloc = NULL, *prefsrc = NULL,
+static void log_route_debug(const Route *route, const char *str, Manager *manager) {
+ _cleanup_free_ char *state = NULL, *nexthop = NULL, *prefsrc = NULL,
*table = NULL, *scope = NULL, *proto = NULL, *flags = NULL;
- const char *gw = NULL, *dst, *src;
+ const char *dst, *src;
+ Link *link = NULL;
assert(route);
assert(str);
assert(manager);
- /* link may be NULL. */
-
if (!DEBUG_LOGGING)
return;
+ (void) route_get_link(manager, route, &link);
+
(void) network_config_state_to_string_alloc(route->state, &state);
dst = in_addr_is_set(route->family, &route->dst) || route->dst_prefixlen > 0 ?
src = in_addr_is_set(route->family, &route->src) || route->src_prefixlen > 0 ?
IN_ADDR_PREFIX_TO_STRING(route->family, &route->src, route->src_prefixlen) : NULL;
- if (in_addr_is_set(route->gw_family, &route->gw)) {
- (void) in_addr_to_string(route->gw_family, &route->gw, &gw_alloc);
- gw = gw_alloc;
- } else if (route->gateway_from_dhcp_or_ra) {
- if (route->gw_family == AF_INET)
- gw = "_dhcp4";
- else if (route->gw_family == AF_INET6)
- gw = "_ipv6ra";
- } else {
- MultipathRoute *m;
-
- ORDERED_SET_FOREACH(m, route->multipath_routes) {
- _cleanup_free_ char *buf = NULL;
- union in_addr_union a = m->gateway.address;
-
- (void) in_addr_to_string(m->gateway.family, &a, &buf);
- (void) strextend_with_separator(&gw_alloc, ",", strna(buf));
- if (m->ifname)
- (void) strextend(&gw_alloc, "@", m->ifname);
- else if (m->ifindex > 0)
- (void) strextendf(&gw_alloc, "@%i", m->ifindex);
- /* See comments in config_parse_multipath_route(). */
- (void) strextendf(&gw_alloc, ":%"PRIu32, m->weight + 1);
- }
- gw = gw_alloc;
- }
+ (void) route_nexthops_to_string(route, &nexthop);
+
if (in_addr_is_set(route->family, &route->prefsrc))
(void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc);
(void) route_scope_to_string_alloc(route->scope, &scope);
(void) route_flags_to_string_alloc(route->flags, &flags);
log_link_debug(link,
- "%s %s route (%s): dst: %s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, "
- "proto: %s, type: %s, nexthop: %"PRIu32", priority: %"PRIu32", flags: %s",
+ "%s %s route (%s): dst: %s, src: %s, %s, prefsrc: %s, "
+ "table: %s, priority: %"PRIu32", "
+ "proto: %s, scope: %s, type: %s, flags: %s",
str, strna(network_config_source_to_string(route->source)), strna(state),
- strna(dst), strna(src), strna(gw), strna(prefsrc),
- strna(scope), strna(table), strna(proto),
- strna(route_type_to_string(route->type)),
- route->nexthop_id, route->priority, strna(flags));
+ strna(dst), strna(src), strna(nexthop), strna(prefsrc),
+ strna(table), route->priority,
+ strna(proto), strna(scope), strna(route_type_to_string(route->type)), strna(flags));
}
-static int route_set_netlink_message(const Route *route, sd_netlink_message *req, Link *link) {
+static int route_set_netlink_message(const Route *route, sd_netlink_message *m, Link *link) {
int r;
assert(route);
- assert(req);
+ assert(m);
/* link may be NULL */
- if (in_addr_is_set(route->gw_family, &route->gw) && route->nexthop_id == 0) {
- if (route->gw_family == route->family) {
- r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->gw_family, &route->gw);
- if (r < 0)
- return r;
- } else {
- RouteVia rtvia = {
- .family = route->gw_family,
- .address = route->gw,
- };
-
- r = sd_netlink_message_append_data(req, RTA_VIA, &rtvia, sizeof(rtvia));
- if (r < 0)
- return r;
- }
- }
-
+ /* rtmsg header (and relevant attributes) */
if (route->dst_prefixlen > 0) {
- r = netlink_message_append_in_addr_union(req, RTA_DST, route->family, &route->dst);
+ r = netlink_message_append_in_addr_union(m, RTA_DST, route->family, &route->dst);
if (r < 0)
return r;
- r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen);
+ r = sd_rtnl_message_route_set_dst_prefixlen(m, route->dst_prefixlen);
if (r < 0)
return r;
}
if (route->src_prefixlen > 0) {
- r = netlink_message_append_in_addr_union(req, RTA_SRC, route->family, &route->src);
+ r = netlink_message_append_in_addr_union(m, RTA_SRC, route->family, &route->src);
if (r < 0)
return r;
- r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen);
+ r = sd_rtnl_message_route_set_src_prefixlen(m, route->src_prefixlen);
if (r < 0)
return r;
}
- if (in_addr_is_set(route->family, &route->prefsrc)) {
- r = netlink_message_append_in_addr_union(req, RTA_PREFSRC, route->family, &route->prefsrc);
- if (r < 0)
- return r;
- }
+ r = sd_rtnl_message_route_set_tos(m, route->tos);
+ if (r < 0)
+ return r;
- r = sd_rtnl_message_route_set_scope(req, route->scope);
+ r = sd_rtnl_message_route_set_scope(m, route->scope);
if (r < 0)
return r;
- r = sd_rtnl_message_route_set_flags(req, route->flags & RTNH_F_ONLINK);
+ r = sd_rtnl_message_route_set_type(m, route->type);
if (r < 0)
return r;
- if (route->table < 256) {
- r = sd_rtnl_message_route_set_table(req, route->table);
- if (r < 0)
- return r;
- } else {
- r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC);
- if (r < 0)
- return r;
+ r = sd_rtnl_message_route_set_flags(m, route->flags & ~RTNH_COMPARE_MASK);
+ if (r < 0)
+ return r;
- /* Table attribute to allow more than 256. */
- r = sd_netlink_message_append_u32(req, RTA_TABLE, route->table);
+ /* attributes */
+ r = sd_netlink_message_append_u32(m, RTA_PRIORITY, route->priority);
+ if (r < 0)
+ return r;
+
+ if (in_addr_is_set(route->family, &route->prefsrc)) {
+ r = netlink_message_append_in_addr_union(m, RTA_PREFSRC, route->family, &route->prefsrc);
if (r < 0)
return r;
}
- if (!route_type_is_reject(route) &&
- route->nexthop_id == 0 &&
- ordered_set_isempty(route->multipath_routes)) {
- assert(link); /* Those routes must be attached to a specific link */
-
- r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
+ if (route->table < 256) {
+ r = sd_rtnl_message_route_set_table(m, route->table);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_rtnl_message_route_set_table(m, RT_TABLE_UNSPEC);
if (r < 0)
return r;
- }
- if (route->nexthop_id > 0) {
- r = sd_netlink_message_append_u32(req, RTA_NH_ID, route->nexthop_id);
+ /* Table attribute to allow more than 256. */
+ r = sd_netlink_message_append_u32(m, RTA_TABLE, route->table);
if (r < 0)
return r;
}
- r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref);
+ r = sd_netlink_message_append_u8(m, RTA_PREF, route->pref);
+ if (r < 0)
+ return r;
+
+ /* nexthops */
+ r = route_nexthops_set_netlink_message(link, route, m);
if (r < 0)
return r;
- r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority);
+ /* metrics */
+ r = route_metric_set_netlink_message(&route->metric, m);
if (r < 0)
return r;
}
int route_remove(Route *route) {
- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
unsigned char type;
Manager *manager;
Link *link;
link = route->link;
manager = route->manager ?: link->manager;
- log_route_debug(route, "Removing", link, manager);
+ log_route_debug(route, "Removing", manager);
+
+ r = sd_rtnl_message_new_route(manager->rtnl, &m, RTM_DELROUTE, route->family, route->protocol);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not create netlink message: %m");
- r = sd_rtnl_message_new_route(manager->rtnl, &req,
- RTM_DELROUTE, route->family,
- route->protocol);
+ r = route_set_netlink_message(route, m, link);
if (r < 0)
- return log_link_error_errno(link, r, "Could not create netlink message: %m");
+ return log_link_warning_errno(link, r, "Could not fill netlink message: %m");
if (route->family == AF_INET && route->nexthop_id > 0 && route->type == RTN_BLACKHOLE)
/* When IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel
else
type = route->type;
- r = sd_rtnl_message_route_set_type(req, type);
+ r = sd_rtnl_message_route_set_type(m, type);
if (r < 0)
return log_link_error_errno(link, r, "Could not set route type: %m");
- r = route_set_netlink_message(route, req, link);
- if (r < 0)
- return log_error_errno(r, "Could not fill netlink message: %m");
-
- r = netlink_call_async(manager->rtnl, NULL, req, route_remove_handler,
+ r = netlink_call_async(manager->rtnl, NULL, m, route_remove_handler,
link ? link_netlink_destroy_callback : NULL, link);
if (r < 0)
- return log_link_error_errno(link, r, "Could not send netlink message: %m");
+ return log_link_warning_errno(link, r, "Could not send netlink message: %m");
link_ref(link);
_cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
Route *existing;
- r = route_convert(manager, route, &converted);
+ r = route_convert(manager, link, route, &converted);
if (r < 0)
continue;
if (r == 0) {
_cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
Route *existing;
- r = route_convert(link->manager, route, &converted);
+ r = route_convert(link->manager, link, route, &converted);
if (r < 0)
continue;
if (r == 0) {
}
static int route_setup_timer(Route *route, const struct rta_cacheinfo *cacheinfo) {
- Manager *manager;
int r;
assert(route);
- assert(route->manager || (route->link && route->link->manager));
-
- manager = route->manager ?: route->link->manager;
-
- if (route->lifetime_usec == USEC_INFINITY)
- return 0;
if (cacheinfo && cacheinfo->rta_expires != 0)
- /* Assume that non-zero rta_expires means kernel will handle the route expiration. */
+ route->expiration_managed_by_kernel = true;
+
+ if (route->lifetime_usec == USEC_INFINITY || /* We do not request expiration for the route. */
+ route->expiration_managed_by_kernel) { /* We have received nonzero expiration previously. The expiration is managed by the kernel. */
+ route->expire = sd_event_source_disable_unref(route->expire);
return 0;
+ }
+ Manager *manager = ASSERT_PTR(route->manager ?: ASSERT_PTR(route->link)->manager);
r = event_reset_time(manager->event, &route->expire, CLOCK_BOOTTIME,
route->lifetime_usec, 0, route_expire_handler, route, 0, "route-expiration", true);
if (r < 0)
- return r;
+ return log_link_warning_errno(route->link, r, "Failed to configure expiration timer for route, ignoring: %m");
+ log_route_debug(route, "Configured expiration timer for", manager);
return 1;
}
-static int append_nexthop_one(const Link *link, const Route *route, const MultipathRoute *m, struct rtattr **rta, size_t offset) {
- struct rtnexthop *rtnh;
- struct rtattr *new_rta;
+int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg) {
int r;
- assert(route);
assert(m);
- assert(rta);
- assert(*rta);
-
- new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop)));
- if (!new_rta)
- return -ENOMEM;
- *rta = new_rta;
-
- rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
- *rtnh = (struct rtnexthop) {
- .rtnh_len = sizeof(*rtnh),
- .rtnh_ifindex = m->ifindex > 0 ? m->ifindex : link->ifindex,
- .rtnh_hops = m->weight,
- };
-
- (*rta)->rta_len += sizeof(struct rtnexthop);
-
- if (route->family == m->gateway.family) {
- r = rtattr_append_attribute(rta, RTA_GATEWAY, &m->gateway.address, FAMILY_ADDRESS_SIZE(m->gateway.family));
- if (r < 0)
- goto clear;
- rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
- rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family));
- } else {
- r = rtattr_append_attribute(rta, RTA_VIA, &m->gateway, FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family));
- if (r < 0)
- goto clear;
- rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
- rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family));
- }
-
- return 0;
-
-clear:
- (*rta)->rta_len -= sizeof(struct rtnexthop);
- return r;
-}
-
-static int append_nexthops(const Link *link, const Route *route, sd_netlink_message *req) {
- _cleanup_free_ struct rtattr *rta = NULL;
- struct rtnexthop *rtnh;
- MultipathRoute *m;
- size_t offset;
- int r;
-
assert(link);
+ assert(link->manager);
assert(route);
- assert(req);
-
- if (ordered_set_isempty(route->multipath_routes))
- return 0;
-
- rta = new(struct rtattr, 1);
- if (!rta)
- return -ENOMEM;
-
- *rta = (struct rtattr) {
- .rta_type = RTA_MULTIPATH,
- .rta_len = RTA_LENGTH(0),
- };
- offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta;
-
- ORDERED_SET_FOREACH(m, route->multipath_routes) {
- r = append_nexthop_one(link, route, m, &rta, offset);
- if (r < 0)
- return r;
-
- rtnh = (struct rtnexthop *)((uint8_t *) rta + offset);
- offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta;
- }
-
- r = sd_netlink_message_append_data(req, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta));
- if (r < 0)
- return r;
-
- return 0;
-}
-
-int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg) {
- int r;
-
- assert(m);
- assert(link);
assert(error_msg);
r = sd_netlink_message_get_errno(m);
- if (r < 0 && r != -EEXIST) {
- log_link_message_warning_errno(link, m, r, "Could not set route");
+ if (r == -EEXIST) {
+ Route *existing;
+
+ if (route_get(link->manager, link, route, &existing) >= 0) {
+ /* When re-configuring an existing route, kernel does not send RTM_NEWROUTE
+ * notification, so we need to update the timer here. */
+ existing->lifetime_usec = route->lifetime_usec;
+ (void) route_setup_timer(existing, NULL);
+ }
+
+ } else if (r < 0) {
+ log_link_message_warning_errno(link, m, r, error_msg);
link_enter_failed(link);
return 0;
}
int r;
assert(route);
- assert(IN_SET(route->family, AF_INET, AF_INET6));
assert(link);
assert(link->manager);
- assert(link->manager->rtnl);
- assert(link->ifindex > 0);
assert(req);
- log_route_debug(route, "Configuring", link, link->manager);
+ log_route_debug(route, "Configuring", link->manager);
r = sd_rtnl_message_new_route(link->manager->rtnl, &m, RTM_NEWROUTE, route->family, route->protocol);
if (r < 0)
return r;
- r = sd_rtnl_message_route_set_type(m, route->type);
- if (r < 0)
- return r;
-
r = route_set_netlink_message(route, m, link);
if (r < 0)
return r;
return r;
}
- r = sd_netlink_message_open_container(m, RTA_METRICS);
- if (r < 0)
- return r;
-
- if (route->mtu > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_MTU, route->mtu);
- if (r < 0)
- return r;
- }
-
- if (route->initcwnd > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_INITCWND, route->initcwnd);
- if (r < 0)
- return r;
- }
-
- if (route->initrwnd > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_INITRWND, route->initrwnd);
- if (r < 0)
- return r;
- }
-
- if (route->quickack >= 0) {
- r = sd_netlink_message_append_u32(m, RTAX_QUICKACK, route->quickack);
- if (r < 0)
- return r;
- }
-
- if (route->fast_open_no_cookie >= 0) {
- r = sd_netlink_message_append_u32(m, RTAX_FASTOPEN_NO_COOKIE, route->fast_open_no_cookie);
- if (r < 0)
- return r;
- }
-
- if (route->advmss > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_ADVMSS, route->advmss);
- if (r < 0)
- return r;
- }
-
- if (!isempty(route->tcp_congestion_control_algo)) {
- r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, route->tcp_congestion_control_algo);
- if (r < 0)
- return r;
- }
-
- if (route->hop_limit > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_HOPLIMIT, route->hop_limit);
- if (r < 0)
- return r;
- }
-
- if (route->tcp_rto_usec > 0) {
- r = sd_netlink_message_append_u32(m, RTAX_RTO_MIN, DIV_ROUND_UP(route->tcp_rto_usec, USEC_PER_MSEC));
- if (r < 0)
- return r;
- }
-
- r = sd_netlink_message_close_container(m);
- if (r < 0)
- return r;
-
- if (!ordered_set_isempty(route->multipath_routes)) {
- assert(route->nexthop_id == 0);
- assert(!in_addr_is_set(route->gw_family, &route->gw));
-
- r = append_nexthops(link, route, m);
- if (r < 0)
- return r;
- }
-
return request_call_netlink_async(link->manager->rtnl, m, req);
}
assert(route);
assert(link);
- if (!link_is_ready_to_configure(link, false))
+ if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false))
return false;
if (set_size(link->routes) >= routes_max())
return false;
- if (route->nexthop_id > 0) {
- struct nexthop_grp *nhg;
- NextHop *nh;
-
- if (nexthop_get_by_id(link->manager, route->nexthop_id, &nh) < 0)
- return false;
-
- if (!nexthop_exists(nh))
- return false;
-
- HASHMAP_FOREACH(nhg, nh->group) {
- NextHop *g;
-
- if (nexthop_get_by_id(link->manager, nhg->id, &g) < 0)
- return false;
-
- if (!nexthop_exists(g))
- return false;
- }
- }
-
if (in_addr_is_set(route->family, &route->prefsrc) > 0) {
r = manager_has_address(link->manager, route->family, &route->prefsrc);
if (r <= 0)
return r;
}
- if (!gateway_is_ready(link, FLAGS_SET(route->flags, RTNH_F_ONLINK), route->gw_family, &route->gw))
- return false;
-
- MultipathRoute *m;
- ORDERED_SET_FOREACH(m, route->multipath_routes) {
- union in_addr_union a = m->gateway.address;
- Link *l = NULL;
-
- r = multipath_route_get_link(link->manager, m, &l);
- if (r < 0)
- return false;
- if (r > 0) {
- if (!link_is_ready_to_configure(l, /* allow_unmanaged = */ true) ||
- !link_has_carrier(l))
- return false;
-
- m->ifindex = l->ifindex;
- }
-
- if (!gateway_is_ready(l ?: link, FLAGS_SET(route->flags, RTNH_F_ONLINK), m->gateway.family, &a))
- return false;
- }
-
- return true;
+ return route_nexthops_is_ready_to_configure(route, link);
}
static int route_process_request(Request *req, Link *link, Route *route) {
return 0;
if (route_needs_convert(route)) {
- r = route_convert(link->manager, route, &converted);
+ r = route_convert(link->manager, link, route, &converted);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to convert route: %m");
existing->lifetime_usec = route->lifetime_usec;
if (consume_object)
route_free(route);
-
- if (existing->expire) {
- /* When re-configuring an existing route, kernel does not send RTM_NEWROUTE
- * message, so we need to update the timer here. */
- r = route_setup_timer(existing, NULL);
- if (r < 0)
- log_link_warning_errno(link, r, "Failed to update expiration timer for route, ignoring: %m");
- if (r > 0)
- log_route_debug(existing, "Updated expiration timer for", link, link->manager);
- }
}
- log_route_debug(existing, "Requesting", link, link->manager);
+ log_route_debug(existing, "Requesting", link->manager);
r = link_queue_request_safe(link, REQUEST_TYPE_ROUTE,
existing, NULL,
route_hash_func,
assert(link);
- r = route_configure_handler_internal(rtnl, m, link, "Could not set route");
+ r = route_configure_handler_internal(rtnl, m, link, route, "Could not set route");
if (r <= 0)
return r;
return link_request_route(link, route, false, &link->static_route_messages,
static_route_handler, NULL);
- log_route_debug(route, "Requesting", link, link->manager);
+ log_route_debug(route, "Requesting", link->manager);
return link_queue_request_safe(link, REQUEST_TYPE_ROUTE,
route, NULL, route_hash_func, route_compare_func,
route_process_request,
}
void route_cancel_request(Route *route, Link *link) {
- Request req;
+ Request *req;
assert(route);
link = route->link ?: link;
assert(link);
+ assert(link->manager);
if (!route_is_requesting(route))
return;
- req = (Request) {
- .link = link,
- .type = REQUEST_TYPE_ROUTE,
- .userdata = route,
- .hash_func = (hash_func_t) route_hash_func,
- .compare_func = (compare_func_t) route_compare_func,
- };
-
- request_detach(link->manager, &req);
+ req = ordered_set_get(link->manager->request_queue,
+ &(Request) {
+ .link = link,
+ .type = REQUEST_TYPE_ROUTE,
+ .userdata = route,
+ .hash_func = (hash_func_t) route_hash_func,
+ .compare_func = (compare_func_t) route_compare_func,
+ });
+
+ if (req)
+ request_detach(req);
route_cancel_requesting(route);
}
_cleanup_(route_freep) Route *tmp = in;
Route *route = NULL;
- bool update_dhcp4;
+ bool is_new = false, update_dhcp4;
int r;
assert(manager);
switch (type) {
case RTM_NEWROUTE:
- if (route) {
- route->flags = tmp->flags;
- route_enter_configured(route);
- log_route_debug(route, "Received remembered", link, manager);
+ if (!route) {
+ if (!manager->manage_foreign_routes) {
+ route_enter_configured(tmp);
+ log_route_debug(tmp, "Ignoring received", manager);
+ return 0;
+ }
- r = route_setup_timer(route, cacheinfo);
- if (r < 0)
- log_link_warning_errno(link, r, "Failed to configure expiration timer for route, ignoring: %m");
- if (r > 0)
- log_route_debug(route, "Configured expiration timer for", link, manager);
-
- } else if (!manager->manage_foreign_routes) {
- route_enter_configured(tmp);
- log_route_debug(tmp, "Ignoring received", link, manager);
-
- } else {
- /* A route appeared that we did not request */
- route_enter_configured(tmp);
- log_route_debug(tmp, "Received new", link, manager);
+ /* If we do not know the route, then save it. */
r = route_add(manager, link, tmp);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m");
return 0;
}
- TAKE_PTR(tmp);
- }
+
+ route = TAKE_PTR(tmp);
+ is_new = true;
+
+ } else
+ /* Update remembered route with the received notification. */
+ route->flags = tmp->flags;
+
+ route_enter_configured(route);
+ log_route_debug(route, is_new ? "Received new" : "Received remembered", manager);
+
+ (void) route_setup_timer(route, cacheinfo);
break;
if (route) {
route_enter_removed(route);
if (route->state == 0) {
- log_route_debug(route, "Forgetting", link, manager);
+ log_route_debug(route, "Forgetting", manager);
route_free(route);
} else
- log_route_debug(route, "Removed", link, manager);
+ log_route_debug(route, "Removed", manager);
} else
log_route_debug(tmp,
manager->manage_foreign_routes ? "Kernel removed unknown" : "Ignoring received",
- link, manager);
+ manager);
break;
}
int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
- _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
_cleanup_(route_freep) Route *tmp = NULL;
- _cleanup_free_ void *rta_multipath = NULL;
- struct rta_cacheinfo cacheinfo;
- bool has_cacheinfo;
- Link *link = NULL;
- uint32_t ifindex;
- uint16_t type;
- size_t rta_len;
int r;
assert(rtnl);
return 0;
}
+ uint16_t type;
r = sd_netlink_message_get_type(message, &type);
if (r < 0) {
log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
return 0;
}
- r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex);
- if (r < 0 && r != -ENODATA) {
- log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m");
- return 0;
- } else if (r >= 0) {
- if (ifindex <= 0) {
- log_warning("rtnl: received route message with invalid ifindex %u, ignoring.", ifindex);
- return 0;
- }
-
- r = link_get_by_index(m, ifindex, &link);
- if (r < 0) {
- /* when enumerating we might be out of sync, but we will
- * get the route again, so just ignore it */
- if (!m->enumerating)
- log_warning("rtnl: received route message for link (%u) we do not know about, ignoring", ifindex);
- return 0;
- }
- }
-
r = route_new(&tmp);
if (r < 0)
return log_oom();
+ /* rtmsg header */
r = sd_rtnl_message_route_get_family(message, &tmp->family);
if (r < 0) {
- log_link_warning(link, "rtnl: received route message without family, ignoring");
+ log_warning_errno(r, "rtnl: received route message without family, ignoring: %m");
return 0;
} else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) {
- log_link_debug(link, "rtnl: received route message with invalid family '%i', ignoring", tmp->family);
+ log_debug("rtnl: received route message with invalid family '%i', ignoring.", tmp->family);
return 0;
}
- r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol);
+ r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen);
if (r < 0) {
- log_warning_errno(r, "rtnl: received route message without route protocol, ignoring: %m");
+ log_warning_errno(r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m");
return 0;
}
- r = sd_rtnl_message_route_get_flags(message, &tmp->flags);
+ r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen);
if (r < 0) {
- log_warning_errno(r, "rtnl: received route message without route flags, ignoring: %m");
+ log_warning_errno(r, "rtnl: received route message with invalid source prefixlen, ignoring: %m");
return 0;
}
- r = netlink_message_read_in_addr_union(message, RTA_DST, tmp->family, &tmp->dst);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message without valid destination, ignoring: %m");
+ r = sd_rtnl_message_route_get_tos(message, &tmp->tos);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: received route message with invalid tos, ignoring: %m");
return 0;
}
- r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, tmp->family, &tmp->gw);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m");
+ r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: received route message without route protocol, ignoring: %m");
return 0;
- } else if (r >= 0)
- tmp->gw_family = tmp->family;
- else if (tmp->family == AF_INET) {
- RouteVia via;
-
- r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m");
- return 0;
- } else if (r >= 0) {
- tmp->gw_family = via.family;
- tmp->gw = via.address;
- }
}
- r = netlink_message_read_in_addr_union(message, RTA_SRC, tmp->family, &tmp->src);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message without valid source, ignoring: %m");
+ r = sd_rtnl_message_route_get_scope(message, &tmp->scope);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: received route message with invalid scope, ignoring: %m");
return 0;
}
- r = netlink_message_read_in_addr_union(message, RTA_PREFSRC, tmp->family, &tmp->prefsrc);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message without valid preferred source, ignoring: %m");
+ r = sd_rtnl_message_route_get_type(message, &tmp->type);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: received route message with invalid type, ignoring: %m");
return 0;
}
- r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen);
+ r = sd_rtnl_message_route_get_flags(message, &tmp->flags);
if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m");
+ log_warning_errno(r, "rtnl: received route message without route flags, ignoring: %m");
return 0;
}
- r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen);
- if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid source prefixlen, ignoring: %m");
+ /* attributes */
+ r = netlink_message_read_in_addr_union(message, RTA_DST, tmp->family, &tmp->dst);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: received route message without valid destination, ignoring: %m");
return 0;
}
- r = sd_rtnl_message_route_get_scope(message, &tmp->scope);
- if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid scope, ignoring: %m");
+ r = netlink_message_read_in_addr_union(message, RTA_SRC, tmp->family, &tmp->src);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: received route message without valid source, ignoring: %m");
return 0;
}
- r = sd_rtnl_message_route_get_tos(message, &tmp->tos);
- if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid tos, ignoring: %m");
+ r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: received route message with invalid priority, ignoring: %m");
return 0;
}
- r = sd_rtnl_message_route_get_type(message, &tmp->type);
- if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid type, ignoring: %m");
+ r = netlink_message_read_in_addr_union(message, RTA_PREFSRC, tmp->family, &tmp->prefsrc);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: received route message without valid preferred source, ignoring: %m");
return 0;
}
tmp->table = table;
}
if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid table, ignoring: %m");
+ log_warning_errno(r, "rtnl: received route message with invalid table, ignoring: %m");
return 0;
}
- r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority);
+ r = sd_netlink_message_read_u8(message, RTA_PREF, &tmp->pref);
if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid priority, ignoring: %m");
+ log_warning_errno(r, "rtnl: received route message with invalid preference, ignoring: %m");
return 0;
}
- r = sd_netlink_message_read_u32(message, RTA_NH_ID, &tmp->nexthop_id);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid nexthop id, ignoring: %m");
+ /* nexthops */
+ if (route_nexthops_read_netlink_message(tmp, message) < 0)
+ return 0;
+
+ /* metrics */
+ if (route_metric_read_netlink_message(&tmp->metric, message) < 0)
return 0;
- }
- r = sd_netlink_message_enter_container(message, RTA_METRICS);
+ bool has_cacheinfo;
+ struct rta_cacheinfo cacheinfo;
+ r = sd_netlink_message_read(message, RTA_CACHEINFO, sizeof(cacheinfo), &cacheinfo);
if (r < 0 && r != -ENODATA) {
- log_link_error_errno(link, r, "rtnl: Could not enter RTA_METRICS container, ignoring: %m");
+ log_warning_errno(r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m");
return 0;
}
- if (r >= 0) {
- r = sd_netlink_message_read_u32(message, RTAX_INITCWND, &tmp->initcwnd);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid initcwnd, ignoring: %m");
- return 0;
- }
-
- r = sd_netlink_message_read_u32(message, RTAX_INITRWND, &tmp->initrwnd);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid initrwnd, ignoring: %m");
- return 0;
- }
-
- r = sd_netlink_message_read_u32(message, RTAX_ADVMSS, &tmp->advmss);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route message with invalid advmss, ignoring: %m");
- return 0;
- }
+ has_cacheinfo = r >= 0;
- r = sd_netlink_message_exit_container(message);
+ Link *link = NULL;
+ if (tmp->nexthop.ifindex > 0) {
+ r = link_get_by_index(m, tmp->nexthop.ifindex, &link);
if (r < 0) {
- log_link_error_errno(link, r, "rtnl: Could not exit from RTA_METRICS container, ignoring: %m");
+ /* when enumerating we might be out of sync, but we will
+ * get the route again, so just ignore it */
+ if (!m->enumerating)
+ log_warning("rtnl: received route message for link (%i) we do not know about, ignoring", tmp->nexthop.ifindex);
return 0;
}
}
- r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta_multipath);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m");
- return 0;
- } else if (r >= 0) {
- r = rtattr_read_nexthop(rta_multipath, rta_len, tmp->family, &tmp->multipath_routes);
- if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m");
- return 0;
- }
- }
-
- r = sd_netlink_message_read(message, RTA_CACHEINFO, sizeof(cacheinfo), &cacheinfo);
- if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m");
- return 0;
- }
- has_cacheinfo = r >= 0;
-
- /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's
- * fib6_nh_init() in net/ipv6/route.c. However, we'd like to manage them by Manager. Hence, set
- * link to NULL here. */
- if (route_type_is_reject(tmp))
- link = NULL;
-
if (!route_needs_convert(tmp))
return process_route_one(m, link, type, TAKE_PTR(tmp), has_cacheinfo ? &cacheinfo : NULL);
- r = route_convert(m, tmp, &converted);
+ _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
+ r = route_convert(m, link, tmp, &converted);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: failed to convert received route, ignoring: %m");
return 0;
}
int network_add_ipv4ll_route(Network *network) {
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
unsigned section_line;
int r;
return r;
/* IPv4LLRoute= is in [Network] section. */
- r = route_new_static(network, network->filename, section_line, &n);
+ r = route_new_static(network, network->filename, section_line, &route);
if (r < 0)
return r;
- r = in_addr_from_string(AF_INET, "169.254.0.0", &n->dst);
+ r = in_addr_from_string(AF_INET, "169.254.0.0", &route->dst);
if (r < 0)
return r;
- n->family = AF_INET;
- n->dst_prefixlen = 16;
- n->scope = RT_SCOPE_LINK;
- n->scope_set = true;
- n->table_set = true;
- n->priority = IPV4LL_ROUTE_METRIC;
- n->protocol = RTPROT_STATIC;
+ route->family = AF_INET;
+ route->dst_prefixlen = 16;
+ route->scope = RT_SCOPE_LINK;
+ route->scope_set = true;
+ route->table_set = true;
+ route->priority = IPV4LL_ROUTE_METRIC;
+ route->protocol = RTPROT_STATIC;
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
int network_add_default_route_on_device(Network *network) {
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
unsigned section_line;
int r;
return r;
/* DefaultRouteOnDevice= is in [Network] section. */
- r = route_new_static(network, network->filename, section_line, &n);
+ r = route_new_static(network, network->filename, section_line, &route);
if (r < 0)
return r;
- n->family = AF_INET;
- n->scope = RT_SCOPE_LINK;
- n->scope_set = true;
- n->protocol = RTPROT_STATIC;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_gateway(
- 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) {
-
- Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- if (streq(section, "Network")) {
- /* we are not in an Route section, so use line number instead */
- r = route_new_static(network, filename, line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
- } else {
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->gateway_from_dhcp_or_ra = false;
- n->gw_family = AF_UNSPEC;
- n->gw = IN_ADDR_NULL;
- TAKE_PTR(n);
- return 0;
- }
-
- if (streq(rvalue, "_dhcp")) {
- n->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(n);
- return 0;
- }
-
- if (streq(rvalue, "_dhcp4")) {
- n->gw_family = AF_INET;
- n->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(n);
- return 0;
- }
-
- if (streq(rvalue, "_ipv6ra")) {
- n->gw_family = AF_INET6;
- n->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(n);
- return 0;
- }
- }
-
- r = in_addr_from_string_auto(rvalue, &n->gw_family, &n->gw);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
+ route->family = AF_INET;
+ route->scope = RT_SCOPE_LINK;
+ route->scope_set = true;
+ route->protocol = RTPROT_STATIC;
- n->gateway_from_dhcp_or_ra = false;
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
assert(filename);
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- if (n->family == AF_UNSPEC)
- r = in_addr_from_string_auto(rvalue, &n->family, &n->prefsrc);
+ if (route->family == AF_UNSPEC)
+ r = in_addr_from_string_auto(rvalue, &route->family, &route->prefsrc);
else
- r = in_addr_from_string(n->family, rvalue, &n->prefsrc);
+ r = in_addr_from_string(route->family, rvalue, &route->prefsrc);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
"Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
return 0;
}
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
union in_addr_union *buffer;
unsigned char *prefixlen;
int r;
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
}
if (streq(lvalue, "Destination")) {
- buffer = &n->dst;
- prefixlen = &n->dst_prefixlen;
+ buffer = &route->dst;
+ prefixlen = &route->dst_prefixlen;
} else if (streq(lvalue, "Source")) {
- buffer = &n->src;
- prefixlen = &n->src_prefixlen;
+ buffer = &route->src;
+ prefixlen = &route->src_prefixlen;
} else
assert_not_reached();
- if (n->family == AF_UNSPEC)
- r = in_addr_prefix_from_string_auto(rvalue, &n->family, buffer, prefixlen);
+ if (route->family == AF_UNSPEC)
+ r = in_addr_prefix_from_string_auto(rvalue, &route->family, buffer, prefixlen);
else
- r = in_addr_prefix_from_string(rvalue, n->family, buffer, prefixlen);
+ r = in_addr_prefix_from_string(rvalue, route->family, buffer, prefixlen);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
"Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
return 0;
}
- (void) in_addr_mask(n->family, buffer, *prefixlen);
+ (void) in_addr_mask(route->family, buffer, *prefixlen);
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
assert(filename);
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- r = safe_atou32(rvalue, &n->priority);
+ r = safe_atou32(rvalue, &route->priority);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Could not parse route priority \"%s\", ignoring assignment: %m", rvalue);
return 0;
}
- n->priority_set = true;
- TAKE_PTR(n);
+ route->priority_set = true;
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
assert(filename);
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- n->scope = r;
- n->scope_set = true;
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_nexthop(
- 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) {
-
- Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- uint32_t id;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->nexthop_id = 0;
- TAKE_PTR(n);
- return 0;
- }
-
- r = safe_atou32(rvalue, &id);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue);
- return 0;
- }
- if (id == 0) {
- log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue);
- return 0;
- }
-
- n->nexthop_id = id;
- TAKE_PTR(n);
+ route->scope = r;
+ route->scope_set = true;
+ TAKE_PTR(route);
return 0;
}
void *data,
void *userdata) {
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
Network *network = userdata;
int r;
assert(rvalue);
assert(data);
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- r = manager_get_route_table_from_string(network->manager, rvalue, &n->table);
+ r = manager_get_route_table_from_string(network->manager, rvalue, &route->table);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Could not parse route table \"%s\", ignoring assignment: %m", rvalue);
return 0;
}
- n->table_set = true;
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_boolean(
- 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) {
-
- Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- r = parse_boolean(rvalue);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Could not parse %s=\"%s\", ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
-
- if (STR_IN_SET(lvalue, "GatewayOnLink", "GatewayOnlink"))
- n->gateway_onlink = r;
- else if (streq(lvalue, "QuickAck"))
- n->quickack = r;
- else if (streq(lvalue, "FastOpenNoCookie"))
- n->fast_open_no_cookie = r;
- else
- assert_not_reached();
-
- TAKE_PTR(n);
+ route->table_set = true;
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
}
if (streq(rvalue, "low"))
- n->pref = ICMPV6_ROUTER_PREF_LOW;
+ route->pref = ICMPV6_ROUTER_PREF_LOW;
else if (streq(rvalue, "medium"))
- n->pref = ICMPV6_ROUTER_PREF_MEDIUM;
+ route->pref = ICMPV6_ROUTER_PREF_MEDIUM;
else if (streq(rvalue, "high"))
- n->pref = ICMPV6_ROUTER_PREF_HIGH;
+ route->pref = ICMPV6_ROUTER_PREF_HIGH;
else {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown route preference: %s", rvalue);
return 0;
}
- n->pref_set = true;
- TAKE_PTR(n);
+ route->pref_set = true;
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int r;
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- n->protocol = r;
+ route->protocol = r;
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
void *userdata) {
Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ _cleanup_(route_free_or_set_invalidp) Route *route = NULL;
int t, r;
- r = route_new_static(network, filename, section_line, &n);
+ r = route_new_static(network, filename, section_line, &route);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- n->type = (unsigned char) t;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_hop_limit(
- 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) {
-
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- Network *network = userdata;
- uint32_t k;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->hop_limit = 0;
- TAKE_PTR(n);
- return 0;
- }
-
- r = safe_atou32(rvalue, &k);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Could not parse per route hop limit, ignoring assignment: %s", rvalue);
- return 0;
- }
- if (k > 255) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Specified per route hop limit \"%s\" is too large, ignoring assignment: %m", rvalue);
- return 0;
- }
- if (k == 0) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid per route hop limit \"%s\", ignoring assignment: %m", rvalue);
- return 0;
- }
-
- n->hop_limit = k;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_tcp_congestion(
- 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) {
-
- Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- r = config_parse_string(unit, filename, line, section, section_line, lvalue, ltype,
- rvalue, &n->tcp_congestion_control_algo, userdata);
- if (r < 0)
- return r;
+ route->type = (unsigned char) t;
- TAKE_PTR(n);
+ TAKE_PTR(route);
return 0;
}
-int config_parse_tcp_advmss(
- 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) {
-
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- Network *network = userdata;
- uint64_t u;
+int route_section_verify(Route *route) {
int r;
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->advmss = 0;
- TAKE_PTR(n);
- return 0;
- }
-
- r = parse_size(rvalue, 1024, &u);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Could not parse TCPAdvertisedMaximumSegmentSize= \"%s\", ignoring assignment: %m", rvalue);
- return 0;
- }
-
- if (u == 0 || u > UINT32_MAX) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid TCPAdvertisedMaximumSegmentSize= \"%s\", ignoring assignment: %m", rvalue);
- return 0;
- }
-
- n->advmss = u;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_tcp_window(
- 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) {
-
- uint32_t *window = ASSERT_PTR(data);
- uint32_t k;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = safe_atou32(rvalue, &k);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Could not parse TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
- if (k >= 1024) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Specified TCP %s \"%s\" is too large, ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
- if (k == 0) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue);
- return 0;
- }
-
- *window = k;
- return 0;
-}
-
-int config_parse_route_tcp_window(
- 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) {
-
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- Network *network = userdata;
- uint32_t *d;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (streq(lvalue, "InitialCongestionWindow"))
- d = &n->initcwnd;
- else if (streq(lvalue, "InitialAdvertisedReceiveWindow"))
- d = &n->initrwnd;
- else
- assert_not_reached();
-
- r = config_parse_tcp_window(unit, filename, line, section, section_line, lvalue, ltype, rvalue, d, userdata);
- if (r < 0)
- return r;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_mtu(
- 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) {
-
- Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- r = config_parse_mtu(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->mtu, userdata);
- if (r <= 0)
- return r;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_route_tcp_rto(
- 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) {
-
- Network *network = userdata;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- usec_t usec;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- r = parse_sec(rvalue, &usec);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to parse route TCP retransmission timeout (RTO), ignoring assignment: %s", rvalue);
- return 0;
- }
-
- if (!timestamp_is_set(usec) ||
- DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Route TCP retransmission timeout (RTO) must be in the range 0…%"PRIu32"ms, ignoring assignment: %s", UINT32_MAX, rvalue);
- return 0;
- }
-
- n->tcp_rto_usec = usec;
-
- TAKE_PTR(n);
- return 0;
-}
-
-int config_parse_multipath_route(
- 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) {
-
- _cleanup_(multipath_route_freep) MultipathRoute *m = NULL;
- _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- _cleanup_free_ char *word = NULL;
- Network *network = userdata;
- union in_addr_union a;
- int family, r;
- const char *p;
- char *dev;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = route_new_static(network, filename, section_line, &n);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to allocate route, ignoring assignment: %m");
- return 0;
- }
-
- if (isempty(rvalue)) {
- n->multipath_routes = ordered_set_free_with_destructor(n->multipath_routes, multipath_route_free);
- return 0;
- }
-
- m = new0(MultipathRoute, 1);
- if (!m)
- return log_oom();
-
- p = rvalue;
- r = extract_first_word(&p, &word, NULL, 0);
- if (r == -ENOMEM)
- return log_oom();
- if (r <= 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid multipath route option, ignoring assignment: %s", rvalue);
- return 0;
- }
-
- dev = strchr(word, '@');
- if (dev) {
- *dev++ = '\0';
-
- r = parse_ifindex(dev);
- if (r > 0)
- m->ifindex = r;
- else {
- if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue);
- return 0;
- }
-
- m->ifname = strdup(dev);
- if (!m->ifname)
- return log_oom();
- }
- }
-
- r = in_addr_from_string_auto(word, &family, &a);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue);
- return 0;
- }
- m->gateway.address = a;
- m->gateway.family = family;
-
- if (!isempty(p)) {
- r = safe_atou32(p, &m->weight);
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid multipath route weight, ignoring assignment: %s", p);
- return 0;
- }
- /* ip command takes weight in the range 1…255, while kernel takes the value in the
- * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip
- * command uses, then networkd decreases by one and stores it to match the range which
- * kernel uses. */
- if (m->weight == 0 || m->weight > 256) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid multipath route weight, ignoring assignment: %s", p);
- return 0;
- }
- m->weight--;
- }
-
- r = ordered_set_ensure_put(&n->multipath_routes, NULL, m);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to store multipath route, ignoring assignment: %m");
- return 0;
- }
-
- TAKE_PTR(m);
- TAKE_PTR(n);
- return 0;
-}
+ assert(route);
+ assert(route->section);
-static int route_section_verify(Route *route, Network *network) {
if (section_is_invalid(route->section))
return -EINVAL;
/* Currently, we do not support static route with finite lifetime. */
assert(route->lifetime_usec == USEC_INFINITY);
- if (route->gateway_from_dhcp_or_ra) {
- if (route->gw_family == AF_UNSPEC) {
- /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */
- switch (route->family) {
- case AF_UNSPEC:
- log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
- "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".",
- route->section->filename, route->section->line);
- route->family = AF_INET;
- break;
- case AF_INET:
- case AF_INET6:
- log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
- "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.",
- route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra");
- break;
- default:
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: Invalid route family. Ignoring [Route] section from line %u.",
- route->section->filename, route->section->line);
- }
- route->gw_family = route->family;
- }
-
- if (route->gw_family == AF_INET && !FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4))
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. "
- "Ignoring [Route] section from line %u.",
- route->section->filename, route->section->line);
-
- if (route->gw_family == AF_INET6 && !network->ipv6_accept_ra)
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. "
- "Ignoring [Route] section from line %u.",
- route->section->filename, route->section->line);
- }
-
- /* When only Gateway= is specified, assume the route family based on the Gateway address. */
- if (route->family == AF_UNSPEC)
- route->family = route->gw_family;
-
- if (route->family == AF_UNSPEC) {
- assert(route->section);
-
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: Route section without Gateway=, Destination=, Source=, "
- "or PreferredSource= field configured. "
- "Ignoring [Route] section from line %u.",
- route->section->filename, route->section->line);
- }
-
- if (route->family == AF_INET6 && route->gw_family == AF_INET)
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: IPv4 gateway is configured for IPv6 route. "
- "Ignoring [Route] section from line %u.",
- route->section->filename, route->section->line);
+ r = route_section_verify_nexthops(route);
+ if (r < 0)
+ return r;
- if (!route->table_set && network->vrf) {
- route->table = VRF(network->vrf)->table;
+ /* table */
+ if (!route->table_set && route->network && route->network->vrf) {
+ route->table = VRF(route->network->vrf)->table;
route->table_set = true;
}
if (!route->table_set && IN_SET(route->type, RTN_LOCAL, RTN_BROADCAST, RTN_ANYCAST, RTN_NAT))
route->table = RT_TABLE_LOCAL;
- if (!route->scope_set && route->family != AF_INET6) {
+ /* scope */
+ if (!route->scope_set && route->family == AF_INET) {
if (IN_SET(route->type, RTN_LOCAL, RTN_NAT))
route->scope = RT_SCOPE_HOST;
else if (IN_SET(route->type, RTN_BROADCAST, RTN_ANYCAST, RTN_MULTICAST))
route->scope = RT_SCOPE_LINK;
else if (IN_SET(route->type, RTN_UNICAST, RTN_UNSPEC) &&
!route->gateway_from_dhcp_or_ra &&
- !in_addr_is_set(route->gw_family, &route->gw) &&
- ordered_set_isempty(route->multipath_routes) &&
+ !in_addr_is_set(route->nexthop.family, &route->nexthop.gw) &&
+ ordered_set_isempty(route->nexthops) &&
route->nexthop_id == 0)
route->scope = RT_SCOPE_LINK;
}
- if (route->scope != RT_SCOPE_UNIVERSE && route->family == AF_INET6) {
- log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename);
- route->scope = RT_SCOPE_UNIVERSE;
- }
-
- if (route->family == AF_INET6 && route->priority == 0)
- route->priority = IP6_RT_PRIO_USER;
+ /* IPv6 route */
+ if (route->family == AF_INET6) {
+ if (route->scope != RT_SCOPE_UNIVERSE) {
+ log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename);
+ route->scope = RT_SCOPE_UNIVERSE;
+ }
- if (route->gateway_onlink < 0 && in_addr_is_set(route->gw_family, &route->gw) &&
- ordered_hashmap_isempty(network->addresses_by_section)) {
- /* If no address is configured, in most cases the gateway cannot be reachable.
- * TODO: we may need to improve the condition above. */
- log_warning("%s: Gateway= without static address configured. "
- "Enabling GatewayOnLink= option.",
- network->filename);
- route->gateway_onlink = true;
+ if (route->priority == 0)
+ route->priority = IP6_RT_PRIO_USER;
}
- if (route->gateway_onlink >= 0)
- SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink);
-
- if (route->family == AF_INET6) {
- MultipathRoute *m;
-
- ORDERED_SET_FOREACH(m, route->multipath_routes)
- if (m->gateway.family == AF_INET)
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: IPv4 multipath route is specified for IPv6 route. "
- "Ignoring [Route] section from line %u.",
- route->section->filename, route->section->line);
- }
-
- if ((route->gateway_from_dhcp_or_ra ||
- in_addr_is_set(route->gw_family, &route->gw)) &&
- !ordered_set_isempty(route->multipath_routes))
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: Gateway= cannot be specified with MultiPathRoute=. "
- "Ignoring [Route] section from line %u.",
- route->section->filename, route->section->line);
-
- if (route->nexthop_id > 0 &&
- (route->gateway_from_dhcp_or_ra ||
- in_addr_is_set(route->gw_family, &route->gw) ||
- !ordered_set_isempty(route->multipath_routes)))
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. "
- "Ignoring [Route] section from line %u.",
- route->section->filename, route->section->line);
-
return 0;
}
assert(network);
HASHMAP_FOREACH(route, network->routes_by_section)
- if (route_section_verify(route, network) < 0)
+ if (route_section_verify(route) < 0)
route_free(route);
}
#include "conf-parser.h"
#include "in-addr-util.h"
#include "networkd-link.h"
+#include "networkd-route-metric.h"
+#include "networkd-route-nexthop.h"
#include "networkd-util.h"
typedef struct Manager Manager;
typedef struct Network Network;
typedef struct Request Request;
typedef struct Route Route;
+typedef struct Wireguard Wireguard;
+
typedef int (*route_netlink_handler_t)(
sd_netlink *rtnl,
sd_netlink_message *m,
Link *link;
Manager *manager;
Network *network;
+ Wireguard *wireguard;
ConfigSection *section;
NetworkConfigSource source;
NetworkConfigState state;
union in_addr_union provider; /* DHCP server or router address */
+ /* rtmsg header */
int family;
- int gw_family;
- uint32_t gw_weight;
- int quickack;
- int fast_open_no_cookie;
-
unsigned char dst_prefixlen;
- unsigned char src_prefixlen;
- unsigned char scope;
+ unsigned char src_prefixlen; /* IPv6 only */
+ unsigned char tos; /* IPv4 only */
unsigned char protocol; /* RTPROT_* */
- unsigned char type; /* RTN_* */
- unsigned char tos;
- uint32_t priority; /* note that ip(8) calls this 'metric' */
- uint32_t table;
- uint32_t mtu;
- uint32_t initcwnd;
- uint32_t initrwnd;
- uint32_t advmss;
- uint32_t hop_limit;
- char *tcp_congestion_control_algo;
- unsigned char pref;
- unsigned flags;
- int gateway_onlink; /* Only used in conf parser and route_section_verify(). */
- uint32_t nexthop_id;
- usec_t tcp_rto_usec;
+ unsigned char scope; /* IPv4 only */
+ unsigned char type; /* RTN_*, e.g. RTN_LOCAL, RTN_UNREACHABLE */
+ unsigned flags; /* e.g. RTNH_F_ONLINK */
+
+ /* attributes */
+ union in_addr_union dst; /* RTA_DST */
+ union in_addr_union src; /* RTA_SRC (IPv6 only) */
+ uint32_t priority; /* RTA_PRIORITY, note that ip(8) calls this 'metric' */
+ union in_addr_union prefsrc; /* RTA_PREFSRC */
+ uint32_t table; /* RTA_TABLE, also used in rtmsg header */
+ uint8_t pref; /* RTA_PREF (IPv6 only) */
+
+ /* nexthops */
+ RouteNextHop nexthop; /* RTA_OIF, and RTA_GATEWAY or RTA_VIA (IPv4 only) */
+ OrderedSet *nexthops; /* RTA_MULTIPATH */
+ uint32_t nexthop_id; /* RTA_NH_ID */
+
+ /* metrics (RTA_METRICS) */
+ RouteMetric metric;
+
+ /* This is an absolute point in time, and NOT a timespan/duration.
+ * Must be specified with clock_boottime_or_monotonic(). */
+ usec_t lifetime_usec; /* RTA_EXPIRES (IPv6 only) */
+ /* Used when kernel does not support RTA_EXPIRES attribute. */
+ sd_event_source *expire;
+ bool expiration_managed_by_kernel:1; /* RTA_CACHEINFO has nonzero rta_expires */
+ /* Only used by conf persers and route_section_verify(). */
bool scope_set:1;
bool table_set:1;
bool priority_set:1;
bool protocol_set:1;
bool pref_set:1;
bool gateway_from_dhcp_or_ra:1;
-
- union in_addr_union gw;
- union in_addr_union dst;
- union in_addr_union src;
- union in_addr_union prefsrc;
- OrderedSet *multipath_routes;
-
- /* This is an absolute point in time, and NOT a timespan/duration.
- * Must be specified with clock_boottime_or_monotonic(). */
- usec_t lifetime_usec;
- /* Used when kernel does not support RTA_EXPIRES attribute. */
- sd_event_source *expire;
+ int gateway_onlink;
};
extern const struct hash_ops route_hash_ops;
-int route_new(Route **ret);
-Route *route_free(Route *route);
+Route* route_free(Route *route);
DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_free);
+
+int route_new(Route **ret);
+int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret);
int route_dup(const Route *src, Route **ret);
-int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg);
+int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg);
int route_remove(Route *route);
int route_remove_and_drop(Route *route);
int network_add_ipv4ll_route(Network *network);
int network_add_default_route_on_device(Network *network);
void network_drop_invalid_routes(Network *network);
+int route_section_verify(Route *route);
DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(Route, route);
void link_mark_routes(Link *link, NetworkConfigSource source);
-CONFIG_PARSER_PROTOTYPE(config_parse_gateway);
CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src);
CONFIG_PARSER_PROTOTYPE(config_parse_destination);
CONFIG_PARSER_PROTOTYPE(config_parse_route_priority);
CONFIG_PARSER_PROTOTYPE(config_parse_route_scope);
CONFIG_PARSER_PROTOTYPE(config_parse_route_table);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_boolean);
CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_route_preference);
CONFIG_PARSER_PROTOTYPE(config_parse_route_protocol);
CONFIG_PARSER_PROTOTYPE(config_parse_route_type);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_tcp_window);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_hop_limit);
-CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_tcp_rto);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu);
-CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route);
-CONFIG_PARSER_PROTOTYPE(config_parse_tcp_congestion);
-CONFIG_PARSER_PROTOTYPE(config_parse_tcp_advmss);
-CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop);
if (r < 0)
return log_oom();
- r = parse_ip_port_range(rvalue, &low, &high);
+ r = parse_ip_port_range(rvalue, &low, &high, /* allow_zero = */ false);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse routing policy rule port range '%s'", rvalue);
return 0;
fprintf(f, "REQUIRED_FOR_ONLINE=%s\n",
yes_no(link->network->required_for_online));
- LinkOperationalStateRange st = link->network->required_operstate_for_online;
- fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s%s%s\n",
- strempty(link_operstate_to_string(st.min)),
- st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "",
- st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : "");
+ LinkOperationalStateRange st;
+ link_required_operstate_for_online(link, &st);
+
+ fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s:%s\n",
+ link_operstate_to_string(st.min), link_operstate_to_string(st.max));
fprintf(f, "REQUIRED_FAMILY_FOR_ONLINE=%s\n",
link_required_address_family_to_string(link->network->required_family_for_online));
return 0;
}
- r = sd_netlink_message_read_data_suffix0(message, NL80211_ATTR_SSID, &len, (void**) &ssid);
+ r = sd_netlink_message_read_data(message, NL80211_ATTR_SSID, &len, (void**) &ssid);
if (r < 0 && r != -ENODATA) {
log_link_debug_errno(link, r, "nl80211: received %s(%u) message without valid SSID, ignoring: %m",
strna(nl80211_cmd_to_string(cmd)), cmd);
return false;
}
-static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange s) {
+static int manager_link_is_online(Manager *m, Link *l, const LinkOperationalStateRange *state_range) {
AddressFamily required_family;
bool needs_ipv4;
bool needs_ipv6;
"link is being processed by networkd: setup state is %s.",
l->state);
- if (s.min < 0)
- s.min = m->required_operstate.min >= 0 ? m->required_operstate.min
- : l->required_operstate.min;
+ const LinkOperationalStateRange *range;
+ FOREACH_POINTER(range, state_range, &m->required_operstate, &l->required_operstate)
+ if (operational_state_range_is_valid(range))
+ break;
+ assert(range != POINTER_MAX);
- if (s.max < 0)
- s.max = m->required_operstate.max >= 0 ? m->required_operstate.max
- : l->required_operstate.max;
-
- if (l->operational_state < s.min || l->operational_state > s.max)
+ if (!operational_state_is_in_range(l->operational_state, range))
return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
"Operational state '%s' is not in range ['%s':'%s']",
link_operstate_to_string(l->operational_state),
- link_operstate_to_string(s.min), link_operstate_to_string(s.max));
+ link_operstate_to_string(range->min), link_operstate_to_string(range->max));
required_family = m->required_family > 0 ? m->required_family : l->required_family;
needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4;
needs_ipv6 = required_family & ADDRESS_FAMILY_IPV6;
- if (s.min < LINK_OPERSTATE_ROUTABLE) {
+ if (range->min < LINK_OPERSTATE_ROUTABLE) {
if (needs_ipv4 && l->ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED)
return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
"No routable or link-local IPv4 address is configured.");
continue;
}
- r = manager_link_is_online(m, l, *range);
+ r = manager_link_is_online(m, l, range);
if (r <= 0 && !m->any)
return false;
if (r > 0 && m->any)
continue;
}
- r = manager_link_is_online(m, l,
- (LinkOperationalStateRange) { _LINK_OPERSTATE_INVALID,
- _LINK_OPERSTATE_INVALID });
+ r = manager_link_is_online(m, l, /* state_range = */ NULL);
if (r < 0 && !m->any) /* Unlike the above loop, unmanaged interfaces are ignored here. */
return false;
if (r > 0) {
static usec_t arg_timeout = 120 * USEC_PER_SEC;
static Hashmap *arg_interfaces = NULL;
static char **arg_ignore = NULL;
-static LinkOperationalStateRange arg_required_operstate = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID };
+static LinkOperationalStateRange arg_required_operstate = LINK_OPERSTATE_RANGE_INVALID;
static AddressFamily arg_required_family = ADDRESS_FAMILY_NO;
static bool arg_any = false;
if (p) {
r = parse_operational_state_range(p + 1, range);
if (r < 0)
- log_error_errno(r, "Invalid operational state range '%s'", p + 1);
+ return log_error_errno(r, "Invalid operational state range: %s", p + 1);
ifname = strndup(optarg, p - optarg);
} else {
- range->min = _LINK_OPERSTATE_INVALID;
- range->max = _LINK_OPERSTATE_INVALID;
+ *range = LINK_OPERSTATE_RANGE_INVALID;
ifname = strdup(str);
}
if (!ifname)
if (!ifname_valid(ifname))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Invalid interface name '%s'", ifname);
+ "Invalid interface name: %s", ifname);
- r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, TAKE_PTR(range));
+ r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, range);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to store interface name: %m");
if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Interface name %s is already specified", ifname);
+ return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+ "Interface name %s is already specified.", ifname);
TAKE_PTR(ifname);
+ TAKE_PTR(range);
return 0;
}
break;
- case 'o': {
- LinkOperationalStateRange range;
-
- r = parse_operational_state_range(optarg, &range);
+ case 'o':
+ r = parse_operational_state_range(optarg, &arg_required_operstate);
if (r < 0)
return log_error_errno(r, "Invalid operational state range '%s'", optarg);
-
- arg_required_operstate = range;
-
break;
- }
case '4':
arg_required_family |= ADDRESS_FAMILY_IPV4;
if (!c || c->n_data == 0)
return 0;
- r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+ r = make_run_host(root);
if (r < 0)
- return log_error_errno(r, "Failed to create /run/host: %m");
+ return r;
r = userns_mkdir(root, "/run/host/home", 0755, 0, 0);
if (r < 0)
return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
"sysctl key invalid, refusing: %s", k);
- r = strv_extend_strv(&s->sysctl, STRV_MAKE(k, m), false);
+ r = strv_extend_many(&s->sysctl, k, m);
if (r < 0)
return log_oom();
}
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#if HAVE_BLKID
-#endif
#include <errno.h>
#include <getopt.h>
-#include <linux/fs.h>
#include <linux/loop.h>
#if HAVE_SELINUX
#include <selinux/selinux.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/ioctl.h>
+#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
+#include <linux/fs.h> /* Must be included after <sys/mount.h> */
+
#include "sd-bus.h"
#include "sd-daemon.h"
#include "sd-id128.h"
_cleanup_free_ void *k = NULL;
size_t l;
- r = unhexmem(optarg, strlen(optarg), &k, &l);
+ r = unhexmem(optarg, &k, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash: %s", optarg);
if (l < sizeof(sd_id128_t))
void *p;
if ((value = startswith(optarg, "base64:"))) {
- r = unbase64mem(value, strlen(value), &p, &l);
+ r = unbase64mem(value, &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
return 0;
}
+int make_run_host(const char *root) {
+ int r;
+
+ assert(root);
+
+ r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create /run/host/: %m");
+
+ return 0;
+}
+
static int setup_credentials(const char *root) {
const char *q;
int r;
if (arg_credentials.n_credentials == 0)
return 0;
- r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+ r = make_run_host(root);
if (r < 0)
- return log_error_errno(r, "Failed to create /run/host: %m");
+ return r;
r = userns_mkdir(root, "/run/host/credentials", 0700, 0, 0);
if (r < 0)
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
(void) mkdir_p(p, 0600);
- r = userns_mkdir(root, "/run/host", 0755, 0, 0);
+ r = make_run_host(root);
if (r < 0)
- return log_error_errno(r, "Failed to create /run/host: %m");
+ return r;
r = userns_mkdir(root, NSPAWN_MOUNT_TUNNEL, 0600, 0, 0);
if (r < 0)
(void) mkdir_parents(NSPAWN_NOTIFY_SOCKET_PATH, 0755);
(void) sockaddr_un_unlink(&sa.un);
- r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
- if (r < 0)
- return log_error_errno(errno, "bind(" NSPAWN_NOTIFY_SOCKET_PATH ") failed: %m");
+ WITH_UMASK(0577) { /* only set "w" bit, which is all that's necessary for connecting from the container */
+ r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ if (r < 0)
+ return log_error_errno(errno, "bind(" NSPAWN_NOTIFY_SOCKET_PATH ") failed: %m");
+ }
r = userns_lchown(NSPAWN_NOTIFY_SOCKET_PATH, 0, 0);
if (r < 0)
return TAKE_FD(fd);
}
+static int setup_unix_export_dir_outside(char **ret) {
+ int r;
+
+ assert(ret);
+
+ _cleanup_free_ char *p = NULL;
+ p = path_join("/run/systemd/nspawn/unix-export", arg_machine);
+ if (!p)
+ return log_oom();
+
+ r = path_is_mount_point(p, /* root= */ NULL, 0);
+ if (r > 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Mount point '%s' exists already, refusing.", p);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "Failed to detect if '%s' is a mount point: %m", p);
+
+ r = mkdir_p(p, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create '%s': %m", p);
+
+ _cleanup_(rmdir_and_freep) char *q = TAKE_PTR(p);
+
+ /* Mount the "unix export" directory really tiny, just 64 inodes. We mark the superblock writable
+ * (since the container shall bind sockets into it). */
+ r = mount_nofollow_verbose(
+ LOG_ERR,
+ "tmpfs",
+ q,
+ "tmpfs",
+ MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
+ "size=4M,nr_inodes=64,mode=0755");
+ if (r < 0)
+ return r;
+
+ _cleanup_(umount_and_rmdir_and_freep) char *w = TAKE_PTR(q);
+
+ /* After creating the superblock we change the bind mount to be read-only. This means that the fs
+ * itself is writable, but not through the mount accessible from the host. */
+ r = mount_nofollow_verbose(
+ LOG_ERR,
+ /* source= */ NULL,
+ w,
+ /* fstype= */ NULL,
+ MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
+ /* options= */ NULL);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(w);
+ return 0;
+}
+
+static int setup_unix_export_host_inside(const char *directory, const char *unix_export_path) {
+ int r;
+
+ assert(directory);
+ assert(unix_export_path);
+
+ r = make_run_host(directory);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ char *p = path_join(directory, "run/host/unix-export");
+ if (!p)
+ return log_oom();
+
+ if (mkdir(p, 0755) < 0)
+ return log_error_errno(errno, "Failed to create '%s': %m", p);
+
+ r = mount_nofollow_verbose(
+ LOG_ERR,
+ unix_export_path,
+ p,
+ /* fstype= */ NULL,
+ MS_BIND,
+ /* options= */ NULL);
+ if (r < 0)
+ return r;
+
+ r = mount_nofollow_verbose(
+ LOG_ERR,
+ /* source= */ NULL,
+ p,
+ /* fstype= */ NULL,
+ MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(),
+ /* options= */ NULL);
+ if (r < 0)
+ return r;
+
+ r = userns_lchown(p, 0, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to chown '%s': %m", p);
+
+ return 0;
+}
+
static int outer_child(
Barrier *barrier,
const char *directory,
int fd_outer_socket,
int fd_inner_socket,
FDSet *fds,
- int netns_fd) {
+ int netns_fd,
+ const char *unix_export_path) {
_cleanup_(bind_user_context_freep) BindUserContext *bind_user_context = NULL;
_cleanup_strv_free_ char **os_release_pairs = NULL;
p = prefix_roota(directory, "/run/host");
(void) make_inaccessible_nodes(p, arg_uid_shift, arg_uid_shift);
+ r = setup_unix_export_host_inside(directory, unix_export_path);
+ if (r < 0)
+ return r;
+
r = setup_pts(directory);
if (r < 0)
return r;
/* The same stuff as the $container env var, but nicely readable for the entire payload */
p = prefix_roota(directory, "/run/host/container-manager");
- (void) write_string_file(p, arg_container_service_name, WRITE_STRING_FILE_CREATE);
+ (void) write_string_file(p, arg_container_service_name, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MODE_0444);
/* The same stuff as the $container_uuid env var */
p = prefix_roota(directory, "/run/host/container-uuid");
- (void) write_string_filef(p, WRITE_STRING_FILE_CREATE, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(arg_uuid));
+ (void) write_string_filef(p, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MODE_0444, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(arg_uuid));
if (!arg_use_cgns) {
r = mount_cgroups(
_cleanup_close_ int notify_socket = -EBADF, mntns_fd = -EBADF, fd_kmsg_fifo = -EBADF;
_cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL;
_cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
+ _cleanup_(umount_and_rmdir_and_freep) char *unix_export_host_dir = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
assert_se(sigemptyset(&mask_chld) == 0);
assert_se(sigaddset(&mask_chld, SIGCHLD) == 0);
+ /* Set up the unix export host directory on the host first */
+ r = setup_unix_export_dir_outside(&unix_export_host_dir);
+ if (r < 0)
+ return r;
+
if (arg_userns_mode == USER_NAMESPACE_PICK) {
/* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely
* check with getpwuid() if the specific user already exists. Note that /etc might be
fd_outer_socket_pair[1],
fd_inner_socket_pair[1],
fds,
- child_netns_fd);
+ child_netns_fd,
+ unix_export_host_dir);
if (r < 0)
_exit(EXIT_FAILURE);
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
(void) rm_rf(p, REMOVE_ROOT);
+
+ p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine);
+ (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
+ (void) rmdir(p);
}
expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET, &expose_args.address4);
int userns_lchown(const char *p, uid_t uid, gid_t gid);
int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid);
+int make_run_host(const char *root);
#include "strv.h"
#include "varlink.h"
-static JsonDispatchFlags json_dispatch_flags = 0;
+static JsonDispatchFlags json_dispatch_flags = JSON_ALLOW_EXTENSIONS;
static void setup_logging(void) {
log_parse_environment_variables();
free(message->property);
}
-static int managed_oom_mode(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
- ManagedOOMMode *mode = userdata, m;
- const char *s;
-
- assert(mode);
- assert_se(s = json_variant_string(v));
-
- m = managed_oom_mode_from_string(s);
- if (m < 0)
- return json_log(v, flags, m, "%s is not a valid ManagedOOMMode", s);
-
- *mode = m;
- return 0;
-}
+static JSON_DISPATCH_ENUM_DEFINE(dispatch_managed_oom_mode, ManagedOOMMode, managed_oom_mode_from_string);
static int process_managed_oom_message(Manager *m, uid_t uid, JsonVariant *parameters) {
JsonVariant *c, *cgroups;
int r;
static const JsonDispatch dispatch_table[] = {
- { "mode", JSON_VARIANT_STRING, managed_oom_mode, offsetof(ManagedOOMMessage, mode), JSON_MANDATORY },
- { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, path), JSON_MANDATORY },
- { "property", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, property), JSON_MANDATORY },
- { "limit", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(ManagedOOMMessage, limit), 0 },
+ { "mode", JSON_VARIANT_STRING, dispatch_managed_oom_mode, offsetof(ManagedOOMMessage, mode), JSON_MANDATORY },
+ { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, path), JSON_MANDATORY },
+ { "property", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, property), JSON_MANDATORY },
+ { "limit", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(ManagedOOMMessage, limit), 0 },
{},
};
static EmptyMode arg_empty = EMPTY_UNSET;
static bool arg_dry_run = true;
-static const char *arg_node = NULL;
+static char *arg_node = NULL;
static char *arg_root = NULL;
static char *arg_image = NULL;
static char **arg_definitions = NULL;
static char *arg_copy_source = NULL;
static char *arg_make_ddi = NULL;
+STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
}
-static void context_bar_char_process_partition(
+static int context_bar_char_process_partition(
Context *context,
Partition *bar[],
size_t n,
Partition *p,
- size_t *ret_start) {
+ size_t **start_array,
+ size_t *n_start_array) {
uint64_t from, to, total;
size_t x, y;
assert(bar);
assert(n > 0);
assert(p);
+ assert(start_array);
+ assert(n_start_array);
if (p->dropped)
- return;
+ return 0;
assert(p->offset != UINT64_MAX);
assert(p->new_size != UINT64_MAX);
for (size_t i = x; i < y; i++)
bar[i] = p;
- *ret_start = x;
+ if (!GREEDY_REALLOC_APPEND(*start_array, *n_start_array, &x, 1))
+ return log_oom();
+
+ return 1;
}
static int partition_hint(const Partition *p, const char *node, char **ret) {
static int context_dump_partition_bar(Context *context) {
_cleanup_free_ Partition **bar = NULL;
_cleanup_free_ size_t *start_array = NULL;
+ size_t n_start_array = 0;
Partition *last = NULL;
bool z = false;
size_t c, j = 0;
+ int r;
assert_se((c = columns()) >= 2);
c -= 2; /* We do not use the leftmost and rightmost character cell */
if (!bar)
return log_oom();
- start_array = new(size_t, context->n_partitions);
- if (!start_array)
- return log_oom();
-
- LIST_FOREACH(partitions, p, context->partitions)
- context_bar_char_process_partition(context, bar, c, p, start_array + j++);
+ LIST_FOREACH(partitions, p, context->partitions) {
+ r = context_bar_char_process_partition(context, bar, c, p, &start_array, &n_start_array);
+ if (r < 0)
+ return r;
+ }
putc(' ', stdout);
fputs(ansi_normal(), stdout);
putc('\n', stdout);
- for (size_t i = 0; i < context->n_partitions; i++) {
+ for (size_t i = 0; i < n_start_array; i++) {
_cleanup_free_ char **line = NULL;
line = new0(char*, c);
j = 0;
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_free_ char *d = NULL;
+
+ if (p->dropped)
+ continue;
+
j++;
- if (i < context->n_partitions - j) {
+ if (i < n_start_array - j) {
if (line[start_array[j-1]]) {
const char *e;
return log_oom();
}
- } else if (i == context->n_partitions - j) {
+ } else if (i == n_start_array - j) {
_cleanup_free_ char *hint = NULL;
(void) partition_hint(p, context->node, &hint);
if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) {
#if HAVE_TPM2
+ _cleanup_(iovec_done) struct iovec pubkey = {}, blob = {}, srk = {};
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- _cleanup_(erase_and_freep) void *secret = NULL;
- _cleanup_free_ void *pubkey = NULL;
- _cleanup_free_ void *blob = NULL, *srk_buf = NULL;
- size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0;
ssize_t base64_encoded_size;
int keyslot;
TPM2Flags flags = 0;
if (arg_tpm2_public_key_pcr_mask != 0) {
- r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey, &pubkey_size);
+ r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (arg_tpm2_public_key || r != -ENOENT)
return log_error_errno(r, "Failed to read TPM PCR public key: %m");
}
TPM2B_PUBLIC public;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public);
+ if (iovec_is_set(&pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
}
r = tpm2_calculate_sealing_policy(
arg_tpm2_hash_pcr_values,
arg_tpm2_n_hash_pcr_values,
- pubkey ? &public : NULL,
+ iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false,
arg_tpm2_pcrlock ? &pcrlock_policy : NULL,
&policy);
arg_tpm2_seal_key_handle,
&device_key_public,
/* attributes= */ NULL,
- /* secret= */ NULL, /* secret_size= */ 0,
+ /* secret= */ NULL,
&policy,
/* pin= */ NULL,
- &secret, &secret_size,
- &blob, &blob_size,
- &srk_buf, &srk_buf_size);
+ &secret,
+ &blob,
+ &srk);
else
r = tpm2_seal(tpm2_context,
arg_tpm2_seal_key_handle,
&policy,
/* pin= */ NULL,
- &secret, &secret_size,
- &blob, &blob_size,
+ &secret,
+ &blob,
/* ret_primary_alg= */ NULL,
- &srk_buf, &srk_buf_size);
+ &srk);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
- base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
+ base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded);
if (base64_encoded_size < 0)
return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
keyslot,
hash_pcr_mask,
hash_pcr_bank,
- pubkey, pubkey_size,
+ &pubkey,
arg_tpm2_public_key_pcr_mask,
/* primary_alg= */ 0,
- blob, blob_size,
- policy.buffer, policy.size,
- NULL, 0, /* no salt because tpm2_seal has no pin */
- srk_buf, srk_buf_size,
+ &blob,
+ &IOVEC_MAKE(policy.buffer, policy.size),
+ /* salt= */ NULL, /* no salt because tpm2_seal has no pin */
+ &srk,
flags,
&v);
if (r < 0)
return log_oom();
}
- arg_node = argc > optind ? argv[optind] : NULL;
+ if (argc > optind) {
+ arg_node = strdup(argv[optind]);
+ if (!arg_node)
+ return log_oom();
+ }
if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node && !arg_image)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
#define PCRLOCK_FIRMWARE_CONFIG_LATE_PATH "/var/lib/pcrlock.d/550-firmware-config-late.pcrlock.d/generated.pcrlock"
#define PCRLOCK_GPT_PATH "/var/lib/pcrlock.d/600-gpt.pcrlock.d/generated.pcrlock"
#define PCRLOCK_SECUREBOOT_AUTHORITY_PATH "/var/lib/pcrlock.d/620-secureboot-authority.pcrlock.d/generated.pcrlock"
-#define PCRLOCK_KERNEL_CMDLINE_PATH "/var/lib/pcrlock.d/710-kernel-cmdline.pcrlock/generated.pcrlock"
-#define PCRLOCK_KERNEL_INITRD_PATH "/var/lib/pcrlock.d/720-kernel-initrd.pcrlock/generated.pcrlock"
+#define PCRLOCK_KERNEL_CMDLINE_PATH "/var/lib/pcrlock.d/710-kernel-cmdline.pcrlock.d/generated.pcrlock"
+#define PCRLOCK_KERNEL_INITRD_PATH "/var/lib/pcrlock.d/720-kernel-initrd.pcrlock.d/generated.pcrlock"
#define PCRLOCK_MACHINE_ID_PATH "/var/lib/pcrlock.d/820-machine-id.pcrlock"
#define PCRLOCK_ROOT_FILE_SYSTEM_PATH "/var/lib/pcrlock.d/830-root-file-system.pcrlock"
#define PCRLOCK_FILE_SYSTEM_PATH_PREFIX "/var/lib/pcrlock.d/840-file-system-"
return r;
}
- log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%" PRIu32 ".", path, nv_index);
+ log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index);
log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1));
return !m->operations;
}
-static int manager_run(Manager *m) {
- assert(m);
-
- return bus_event_loop_with_idle(
- m->event,
- m->bus,
- "org.freedesktop.portable1",
- DEFAULT_EXIT_USEC,
- check_idle, m);
-}
-
static int run(int argc, char *argv[]) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
if (r < 0)
return log_error_errno(r, "Failed to fully start up daemon: %m");
- log_debug("systemd-portabled running as pid " PID_FMT, getpid_cached());
r = sd_notify(false, NOTIFY_READY);
if (r < 0)
log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
- r = manager_run(m);
+ r = bus_event_loop_with_idle(
+ m->event,
+ m->bus,
+ "org.freedesktop.portable1",
+ DEFAULT_EXIT_USEC,
+ check_idle, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run main loop: %m");
- (void) sd_notify(false, NOTIFY_STOPPING);
- log_debug("systemd-portabled stopped as pid " PID_FMT, getpid_cached());
- return r;
+ return 0;
}
DEFINE_MAIN_FUNCTION(run);
],
'include_directories' : resolve_includes,
},
+ test_template + {
+ 'sources' : [
+ files('test-resolved-dummy-server.c'),
+ basic_dns_sources,
+ systemd_resolved_sources,
+ ],
+ 'dependencies' : [
+ lib_openssl_or_gcrypt,
+ libm,
+ systemd_resolved_dependencies,
+ ],
+ 'include_directories' : resolve_includes,
+ 'type' : 'manual',
+ },
resolve_fuzz_template + {
'sources' : files('fuzz-dns-packet.c'),
},
static void monitor_query_dump(JsonVariant *v) {
_cleanup_(json_variant_unrefp) JsonVariant *question = NULL, *answer = NULL, *collected_questions = NULL;
- int rcode = -1, error = 0, r;
- const char *state = NULL;
+ int rcode = -1, error = 0, ede_code = -1;
+ const char *state = NULL, *result = NULL, *ede_msg = NULL;
assert(v);
JsonDispatch dispatch_table[] = {
- { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY },
- { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 },
- { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 },
- { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY },
- { "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 },
- { "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 },
+ { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY },
+ { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 },
+ { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 },
+ { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY },
+ { "result", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&result), 0 },
+ { "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 },
+ { "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 },
+ { "extendedDNSErrorCode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&ede_code), 0 },
+ { "extendedDNSErrorMessage", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&ede_msg), 0 },
{}
};
- r = json_dispatch(v, dispatch_table, 0, NULL);
- if (r < 0)
- return (void) log_warning("Received malformed monitor message, ignoring.");
+ if (json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, NULL) < 0)
+ return;
/* First show the current question */
print_question('Q', ansi_highlight_cyan(), question);
/* And then show the questions that led to this one in case this was a CNAME chain */
print_question('C', ansi_highlight_grey(), collected_questions);
- printf("%s%s S%s: %s\n",
+ printf("%s%s S%s: %s",
streq_ptr(state, "success") ? ansi_highlight_green() : ansi_highlight_red(),
special_glyph(SPECIAL_GLYPH_ARROW_LEFT),
ansi_normal(),
streq_ptr(state, "rcode-failure") ? dns_rcode_to_string(rcode) :
state));
+ if (!isempty(result))
+ printf(": %s", result);
+
+ if (ede_code >= 0)
+ printf(" (%s%s%s)",
+ FORMAT_DNS_EDE_RCODE(ede_code),
+ !isempty(ede_msg) ? ": " : "",
+ strempty(ede_msg));
+
+ puts("");
+
print_answer(answer);
}
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
int r, c = 0;
- r = json_dispatch(item, dispatch_table, JSON_LOG, &item_info);
+ r = json_dispatch(item, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &item_info);
if (r < 0)
return r;
{},
};
- r = json_dispatch(scope, dispatch_table, JSON_LOG, &scope_info);
+ r = json_dispatch(scope, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &scope_info);
if (r < 0)
return r;
{},
};
- r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_PERMISSIVE, &server_state);
+ r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &server_state);
if (r < 0)
return r;
return reply_method_errorf(q, BUS_ERROR_ABORTED, "Query aborted");
case DNS_TRANSACTION_DNSSEC_FAILED:
- return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s",
- dnssec_result_to_string(q->answer_dnssec_result));
+ return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s%s%s%s%s%s",
+ dnssec_result_to_string(q->answer_dnssec_result),
+ q->answer_ede_rcode >= 0 ? " (" : "",
+ q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "",
+ (q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "",
+ q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "",
+ q->answer_ede_rcode >= 0 ? ")" : "");
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return reply_method_errorf(q, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known");
rc = FORMAT_DNS_RCODE(q->answer_rcode);
n = strjoina(_BUS_ERROR_DNS, rc);
- sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc);
+ sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error: %s%s%s%s%s%s",
+ dns_query_string(q), rc,
+ q->answer_ede_rcode >= 0 ? " (" : "",
+ q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "",
+ (q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "",
+ q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "",
+ q->answer_ede_rcode >= 0 ? ")" : "");
}
return sd_bus_reply_method_error(req, &error);
}
- case DNS_TRANSACTION_UPSTREAM_DNSSEC_FAILURE:
- return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed upstream: %s%s%s",
- dns_ede_rcode_to_string(q->answer_ede_rcode),
- isempty(q->answer_ede_msg) ? "" : ": ", q->answer_ede_msg);
-
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_VALIDATING:
case DNS_TXT_ITEM_DATA:
if (value) {
- r = unbase64mem(value, strlen(value), &decoded, &length);
+ r = unbase64mem(value, &decoded, &length);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
+ [DNSSEC_UPSTREAM_FAILURE] = "upstream-failure",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
DNSSEC_NO_SIGNATURE,
DNSSEC_MISSING_KEY,
- /* These two are added by the DnsTransaction logic */
+ /* These five are added by the DnsTransaction logic */
DNSSEC_UNSIGNED,
DNSSEC_FAILED_AUXILIARY,
DNSSEC_NSEC_MISMATCH,
DNSSEC_INCOMPATIBLE_SERVER,
+ DNSSEC_UPSTREAM_FAILURE,
_DNSSEC_RESULT_MAX,
_DNSSEC_RESULT_INVALID = -EINVAL,
switch (p->protocol) {
- case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_DNS:
- if (DNS_PACKET_TC(p)) /* mDNS query may have truncation flag. */
+ if (DNS_PACKET_TC(p))
+ return -EBADMSG;
+
+ if (DNS_PACKET_QDCOUNT(p) != 1)
+ return -EBADMSG;
+
+ if (DNS_PACKET_ANCOUNT(p) > 0)
+ return -EBADMSG;
+
+ /* Note, in most cases, DNS query packet does not have authority section. But some query
+ * types, e.g. IXFR, have Authority sections. Hence, unlike the check for LLMNR, we do not
+ * check DNS_PACKET_NSCOUNT(p) here. */
+ break;
+
+ case DNS_PROTOCOL_LLMNR:
+ if (DNS_PACKET_TC(p))
return -EBADMSG;
/* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */
break;
case DNS_PROTOCOL_MDNS:
+ /* Note, mDNS query may have truncation flag. So, unlike the check for DNS and LLMNR,
+ * we do not check DNS_PACKET_TC(p) here. */
+
/* RFC 6762, Section 18 specifies that messages with non-zero RCODE
* must be silently ignored, and that we must ignore the values of
* AA, RD, RA, AD, and CD bits. */
return dns_packet_compare_func(a, b) == 0;
}
-int dns_packet_ede_rcode(DnsPacket *p, char **ret_ede_msg) {
- assert(p);
-
- _cleanup_free_ char *msg = NULL, *msg_escaped = NULL;
- int ede_rcode = _DNS_EDNS_OPT_MAX_DEFINED;
- int r;
+int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg) {
const uint8_t *d;
size_t l;
+ int r;
+
+ assert(p);
if (!p->opt)
- return _DNS_EDE_RCODE_INVALID;
+ return -ENOENT;
d = p->opt->opt.data;
l = p->opt->opt.data_size;
"Truncated option in EDNS0 variable part.");
if (code == DNS_EDNS_OPT_EXT_ERROR) {
+ _cleanup_free_ char *msg = NULL;
+
if (length < 2U)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
- "EDNS0 truncated EDE info code.");
- ede_rcode = unaligned_read_be16(d + 4);
- r = make_cstring((char *)d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg);
+ "EDNS0 truncated EDE info code.");
+
+ r = make_cstring((char *) d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg);
if (r < 0)
- return log_debug_errno(r, "Invalid EDE text in opt");
- else if (!utf8_is_valid(msg))
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid EDE text in opt");
- else if (ede_rcode < _DNS_EDNS_OPT_MAX_DEFINED) {
- msg_escaped = cescape(msg);
- if (!msg_escaped)
- return -ENOMEM;
+ return log_debug_errno(r, "Invalid EDE text in opt.");
+
+ if (ret_ede_msg) {
+ if (!utf8_is_valid(msg)) {
+ _cleanup_free_ char *msg_escaped = NULL;
+
+ msg_escaped = cescape(msg);
+ if (!msg_escaped)
+ return log_oom_debug();
+
+ *ret_ede_msg = TAKE_PTR(msg_escaped);
+ } else
+ *ret_ede_msg = TAKE_PTR(msg);
}
- break;
+
+ if (ret_ede_rcode)
+ *ret_ede_rcode = unaligned_read_be16(d + 4);
+
+ return 0;
}
d += 4U + length;
l -= 4U + length;
}
- if (ret_ede_msg)
- *ret_ede_msg = TAKE_PTR(msg_escaped);
-
- return ede_rcode;
+ return -ENOENT;
}
bool dns_ede_rcode_is_dnssec(int ede_rcode) {
[DNS_RCODE_NXRRSET] = "NXRRSET",
[DNS_RCODE_NOTAUTH] = "NOTAUTH",
[DNS_RCODE_NOTZONE] = "NOTZONE",
+ [DNS_RCODE_DSOTYPENI] = "DSOTYPENI",
[DNS_RCODE_BADVERS] = "BADVERS",
[DNS_RCODE_BADKEY] = "BADKEY",
[DNS_RCODE_BADTIME] = "BADTIME",
bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b);
-int dns_packet_ede_rcode(DnsPacket *p, char **ret_ede_msg);
+int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg);
bool dns_ede_rcode_is_dnssec(int ede_rcode);
int dns_packet_has_nsid_request(DnsPacket *p);
/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */
enum {
- DNS_RCODE_SUCCESS = 0,
- DNS_RCODE_FORMERR = 1,
- DNS_RCODE_SERVFAIL = 2,
- DNS_RCODE_NXDOMAIN = 3,
- DNS_RCODE_NOTIMP = 4,
- DNS_RCODE_REFUSED = 5,
- DNS_RCODE_YXDOMAIN = 6,
- DNS_RCODE_YXRRSET = 7,
- DNS_RCODE_NXRRSET = 8,
- DNS_RCODE_NOTAUTH = 9,
- DNS_RCODE_NOTZONE = 10,
- DNS_RCODE_BADVERS = 16,
- DNS_RCODE_BADSIG = 16, /* duplicate value! */
- DNS_RCODE_BADKEY = 17,
- DNS_RCODE_BADTIME = 18,
- DNS_RCODE_BADMODE = 19,
- DNS_RCODE_BADNAME = 20,
- DNS_RCODE_BADALG = 21,
- DNS_RCODE_BADTRUNC = 22,
- DNS_RCODE_BADCOOKIE = 23,
+ DNS_RCODE_SUCCESS = 0,
+ DNS_RCODE_FORMERR = 1,
+ DNS_RCODE_SERVFAIL = 2,
+ DNS_RCODE_NXDOMAIN = 3,
+ DNS_RCODE_NOTIMP = 4,
+ DNS_RCODE_REFUSED = 5,
+ DNS_RCODE_YXDOMAIN = 6,
+ DNS_RCODE_YXRRSET = 7,
+ DNS_RCODE_NXRRSET = 8,
+ DNS_RCODE_NOTAUTH = 9,
+ DNS_RCODE_NOTZONE = 10,
+ DNS_RCODE_DSOTYPENI = 11,
+ /* 12-15 are unassigned. */
+ DNS_RCODE_BADVERS = 16,
+ DNS_RCODE_BADSIG = 16, /* duplicate value! */
+ DNS_RCODE_BADKEY = 17,
+ DNS_RCODE_BADTIME = 18,
+ DNS_RCODE_BADMODE = 19,
+ DNS_RCODE_BADNAME = 20,
+ DNS_RCODE_BADALG = 21,
+ DNS_RCODE_BADTRUNC = 22,
+ DNS_RCODE_BADCOOKIE = 23,
+ /* 24-3840 are unassigned. */
+ /* 3841-4095 are for private use. */
+ /* 4096-65534 are unassigned. */
_DNS_RCODE_MAX_DEFINED,
- _DNS_RCODE_MAX = 4095 /* 4 bit rcode in the header plus 8 bit rcode in OPT, makes 12 bit */
+ _DNS_RCODE_MAX = 65535, /* reserved */
+ _DNS_RCODE_INVALID = -EINVAL,
};
/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11 */
enum {
- DNS_EDNS_OPT_RESERVED = 0, /* RFC 6891 */
- DNS_EDNS_OPT_LLQ = 1, /* RFC 8764 */
- DNS_EDNS_OPT_UL = 2,
- DNS_EDNS_OPT_NSID = 3, /* RFC 5001 */
- /* DNS_EDNS_OPT_RESERVED = 4 */
- DNS_EDNS_OPT_DAU = 5, /* RFC 6975 */
- DNS_EDNS_OPT_DHU = 6, /* RFC 6975 */
- DNS_EDNS_OPT_N3U = 7, /* RFC 6975 */
+ DNS_EDNS_OPT_RESERVED = 0, /* RFC 6891 */
+ DNS_EDNS_OPT_LLQ = 1, /* RFC 8764 */
+ DNS_EDNS_OPT_UL = 2,
+ DNS_EDNS_OPT_NSID = 3, /* RFC 5001 */
+ /* DNS_EDNS_OPT_RESERVED = 4 */
+ DNS_EDNS_OPT_DAU = 5, /* RFC 6975 */
+ DNS_EDNS_OPT_DHU = 6, /* RFC 6975 */
+ DNS_EDNS_OPT_N3U = 7, /* RFC 6975 */
DNS_EDNS_OPT_CLIENT_SUBNET = 8, /* RFC 7871 */
- DNS_EDNS_OPT_EXPIRE = 9, /* RFC 7314 */
- DNS_EDNS_OPT_COOKIE = 10, /* RFC 7873 */
+ DNS_EDNS_OPT_EXPIRE = 9, /* RFC 7314 */
+ DNS_EDNS_OPT_COOKIE = 10, /* RFC 7873 */
DNS_EDNS_OPT_TCP_KEEPALIVE = 11, /* RFC 7828 */
- DNS_EDNS_OPT_PADDING = 12, /* RFC 7830 */
- DNS_EDNS_OPT_CHAIN = 13, /* RFC 7901 */
- DNS_EDNS_OPT_KEY_TAG = 14, /* RFC 8145 */
- DNS_EDNS_OPT_EXT_ERROR = 15, /* RFC 8914 */
- DNS_EDNS_OPT_CLIENT_TAG = 16,
- DNS_EDNS_OPT_SERVER_TAG = 17,
+ DNS_EDNS_OPT_PADDING = 12, /* RFC 7830 */
+ DNS_EDNS_OPT_CHAIN = 13, /* RFC 7901 */
+ DNS_EDNS_OPT_KEY_TAG = 14, /* RFC 8145 */
+ DNS_EDNS_OPT_EXT_ERROR = 15, /* RFC 8914 */
+ DNS_EDNS_OPT_CLIENT_TAG = 16,
+ DNS_EDNS_OPT_SERVER_TAG = 17,
_DNS_EDNS_OPT_MAX_DEFINED,
- _DNS_EDNS_OPT_INVALID = -EINVAL
+ _DNS_EDNS_OPT_INVALID = -EINVAL,
};
/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#extended-dns-error-codes */
enum {
- DNS_EDE_RCODE_OTHER = 0, /* RFC 8914, Section 4.1 */
- DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG = 1, /* RFC 8914, Section 4.2 */
- DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST = 2, /* RFC 8914, Section 4.3 */
- DNS_EDE_RCODE_STALE_ANSWER = 3, /* RFC 8914, Section 4.4 */
- DNS_EDE_RCODE_FORGED_ANSWER = 4, /* RFC 8914, Section 4.5 */
- DNS_EDE_RCODE_DNSSEC_INDETERMINATE = 5, /* RFC 8914, Section 4.6 */
- DNS_EDE_RCODE_DNSSEC_BOGUS = 6, /* RFC 8914, Section 4.7 */
- DNS_EDE_RCODE_SIG_EXPIRED = 7, /* RFC 8914, Section 4.8 */
- DNS_EDE_RCODE_SIG_NOT_YET_VALID = 8, /* RFC 8914, Section 4.9 */
- DNS_EDE_RCODE_DNSKEY_MISSING = 9, /* RFC 8914, Section 4.10 */
- DNS_EDE_RCODE_RRSIG_MISSING = 10, /* RFC 8914, Section 4.11 */
- DNS_EDE_RCODE_NO_ZONE_KEY_BIT = 11, /* RFC 8914, Section 4.12 */
- DNS_EDE_RCODE_NSEC_MISSING = 12, /* RFC 8914, Section 4.13 */
- DNS_EDE_RCODE_CACHED_ERROR = 13, /* RFC 8914, Section 4.14 */
- DNS_EDE_RCODE_NOT_READY = 14, /* RFC 8914, Section 4.15 */
- DNS_EDE_RCODE_BLOCKED = 15, /* RFC 8914, Section 4.16 */
- DNS_EDE_RCODE_CENSORED = 16, /* RFC 8914, Section 4.17 */
- DNS_EDE_RCODE_FILTERED = 17, /* RFC 8914, Section 4.18 */
- DNS_EDE_RCODE_PROHIBITIED = 18, /* RFC 8914, Section 4.19 */
- DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER = 19, /* RFC 8914, Section 4.20 */
- DNS_EDE_RCODE_NOT_AUTHORITATIVE = 20, /* RFC 8914, Section 4.21 */
- DNS_EDE_RCODE_NOT_SUPPORTED = 21, /* RFC 8914, Section 4.22 */
- DNS_EDE_RCODE_UNREACH_AUTHORITY = 22, /* RFC 8914, Section 4.23 */
- DNS_EDE_RCODE_NET_ERROR = 23, /* RFC 8914, Section 4.24 */
- DNS_EDE_RCODE_INVALID_DATA = 24, /* RFC 8914, Section 4.25 */
- DNS_EDE_RCODE_SIG_NEVER = 25,
- DNS_EDE_RCODE_TOO_EARLY = 26, /* RFC 9250 */
- DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER = 27, /* RFC 9276 */
- DNS_EDE_RCODE_TRANSPORT_POLICY = 28,
- DNS_EDE_RCODE_SYNTHESIZED = 29,
+ DNS_EDE_RCODE_OTHER = 0, /* RFC 8914, Section 4.1 */
+ DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG = 1, /* RFC 8914, Section 4.2 */
+ DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST = 2, /* RFC 8914, Section 4.3 */
+ DNS_EDE_RCODE_STALE_ANSWER = 3, /* RFC 8914, Section 4.4 */
+ DNS_EDE_RCODE_FORGED_ANSWER = 4, /* RFC 8914, Section 4.5 */
+ DNS_EDE_RCODE_DNSSEC_INDETERMINATE = 5, /* RFC 8914, Section 4.6 */
+ DNS_EDE_RCODE_DNSSEC_BOGUS = 6, /* RFC 8914, Section 4.7 */
+ DNS_EDE_RCODE_SIG_EXPIRED = 7, /* RFC 8914, Section 4.8 */
+ DNS_EDE_RCODE_SIG_NOT_YET_VALID = 8, /* RFC 8914, Section 4.9 */
+ DNS_EDE_RCODE_DNSKEY_MISSING = 9, /* RFC 8914, Section 4.10 */
+ DNS_EDE_RCODE_RRSIG_MISSING = 10, /* RFC 8914, Section 4.11 */
+ DNS_EDE_RCODE_NO_ZONE_KEY_BIT = 11, /* RFC 8914, Section 4.12 */
+ DNS_EDE_RCODE_NSEC_MISSING = 12, /* RFC 8914, Section 4.13 */
+ DNS_EDE_RCODE_CACHED_ERROR = 13, /* RFC 8914, Section 4.14 */
+ DNS_EDE_RCODE_NOT_READY = 14, /* RFC 8914, Section 4.15 */
+ DNS_EDE_RCODE_BLOCKED = 15, /* RFC 8914, Section 4.16 */
+ DNS_EDE_RCODE_CENSORED = 16, /* RFC 8914, Section 4.17 */
+ DNS_EDE_RCODE_FILTERED = 17, /* RFC 8914, Section 4.18 */
+ DNS_EDE_RCODE_PROHIBITIED = 18, /* RFC 8914, Section 4.19 */
+ DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER = 19, /* RFC 8914, Section 4.20 */
+ DNS_EDE_RCODE_NOT_AUTHORITATIVE = 20, /* RFC 8914, Section 4.21 */
+ DNS_EDE_RCODE_NOT_SUPPORTED = 21, /* RFC 8914, Section 4.22 */
+ DNS_EDE_RCODE_UNREACH_AUTHORITY = 22, /* RFC 8914, Section 4.23 */
+ DNS_EDE_RCODE_NET_ERROR = 23, /* RFC 8914, Section 4.24 */
+ DNS_EDE_RCODE_INVALID_DATA = 24, /* RFC 8914, Section 4.25 */
+ DNS_EDE_RCODE_SIG_NEVER = 25,
+ DNS_EDE_RCODE_TOO_EARLY = 26, /* RFC 9250 */
+ DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER = 27, /* RFC 9276 */
+ DNS_EDE_RCODE_TRANSPORT_POLICY = 28,
+ DNS_EDE_RCODE_SYNTHESIZED = 29,
_DNS_EDE_RCODE_MAX_DEFINED,
- _DNS_EDE_RCODE_INVALID = -EINVAL
+ _DNS_EDE_RCODE_INVALID = -EINVAL,
};
const char* dns_rcode_to_string(int i) _const_;
q->answer = dns_answer_unref(q->answer);
q->answer_rcode = 0;
+ q->answer_ede_rcode = _DNS_EDE_RCODE_INVALID;
+ q->answer_ede_msg = mfree(q->answer_ede_msg);
q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
q->answer_errno = 0;
q->answer_query_flags = 0;
dns_answer_unref(q->reply_authoritative);
dns_answer_unref(q->reply_additional);
- free(q->answer_ede_msg);
-
if (q->request_stream) {
/* Detach the stream from our query, in case something else keeps a reference to it. */
(void) set_remove(q->request_stream->queries, q);
.question_bypass = dns_packet_ref(question_bypass),
.ifindex = ifindex,
.flags = flags,
+ .answer_ede_rcode = _DNS_EDE_RCODE_INVALID,
.answer_dnssec_result = _DNSSEC_RESULT_INVALID,
.answer_protocol = _DNS_PROTOCOL_INVALID,
.answer_family = AF_UNSPEC,
q->state = state;
- (void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->question_bypass, q->collected_questions, q->answer);
+ (void) manager_monitor_send(q->manager, q);
dns_query_stop(q);
if (q->complete)
!FLAGS_SET(t->answer_query_flags, SD_RESOLVED_AUTHENTICATED))
continue;
- char *answer_ede_msg = NULL;
- if (t->answer_ede_msg) {
- answer_ede_msg = strdup(t->answer_ede_msg);
- if (!answer_ede_msg) {
- log_oom();
- goto fail;
- }
- }
-
DNS_ANSWER_REPLACE(q->answer, dns_answer_ref(t->answer));
q->answer_rcode = t->answer_rcode;
- q->answer_dnssec_result = t->answer_dnssec_result;
q->answer_ede_rcode = t->answer_ede_rcode;
- q->answer_ede_msg = answer_ede_msg;
+ r = free_and_strdup_warn(&q->answer_ede_msg, t->answer_ede_msg);
+ if (r < 0)
+ goto fail;
+ q->answer_dnssec_result = t->answer_dnssec_result;
q->answer_query_flags = t->answer_query_flags | dns_transaction_source_to_query_flags(t->answer_source);
q->answer_errno = t->answer_errno;
DNS_PACKET_REPLACE(q->answer_full_packet, dns_packet_ref(t->received));
/* Discovered data */
DnsAnswer *answer;
int answer_rcode;
- DnssecResult answer_dnssec_result;
int answer_ede_rcode;
char *answer_ede_msg;
+ DnssecResult answer_dnssec_result;
uint64_t answer_query_flags;
DnsProtocol answer_protocol;
int answer_family;
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
t->answer_rcode = 0;
+ t->answer_ede_rcode = _DNS_EDE_RCODE_INVALID;
+ t->answer_ede_msg = mfree(t->answer_ede_msg);
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
t->answer_query_flags = 0;
dns_resource_key_unref(t->key);
dns_packet_unref(t->bypass);
- free(t->answer_ede_msg);
-
return mfree(t);
}
"DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level));
}
- if (state == DNS_TRANSACTION_UPSTREAM_DNSSEC_FAILURE) {
- dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str);
-
- log_struct(LOG_NOTICE,
- "MESSAGE_ID=" SD_MESSAGE_DNSSEC_FAILURE_STR,
- LOG_MESSAGE("Upstream resolver reported failure for question %s: %s%s%s",
- key_str, dns_ede_rcode_to_string(t->answer_ede_rcode),
- isempty(t->answer_ede_msg) ? "" : ": ", t->answer_ede_msg),
- "DNS_TRANSACTION=%" PRIu16, t->id,
- "DNS_QUESTION=%s", key_str,
- "DNS_EDE_RCODE=%s", dns_ede_rcode_to_string(t->answer_ede_rcode),
- "DNS_SERVER=%s", strna(dns_server_string_full(t->server)),
- "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level));
- }
-
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
}
}
- if (error != 0)
- LIST_FOREACH(transactions_by_stream, t, s->transactions)
+ if (error != 0) {
+ /* First, detach the stream from the server. Otherwise, transactions attached to this stream
+ * may be restarted by on_transaction_stream_error() below with this stream. */
+ dns_stream_detach(s);
+
+ /* Do not use LIST_FOREACH() here, as
+ * on_transaction_stream_error()
+ * -> dns_transaction_complete_errno()
+ * -> dns_transaction_free()
+ * may free multiple transactions in the list. */
+ DnsTransaction *t;
+ while ((t = s->transactions))
on_transaction_stream_error(t, error);
+ }
return 0;
}
/* We handle DNSSEC failures different from other errors, as we care about the DNSSEC
* validation result */
- log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result));
- t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */
+ log_debug("Auxiliary DNSSEC RR query failed validation: %s%s%s%s%s%s",
+ dnssec_result_to_string(dt->answer_dnssec_result),
+ dt->answer_ede_rcode >= 0 ? " (" : "",
+ dt->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(dt->answer_ede_rcode) : "",
+ (dt->answer_ede_rcode >= 0 && !isempty(dt->answer_ede_msg)) ? ": " : "",
+ dt->answer_ede_rcode >= 0 ? strempty(dt->answer_ede_msg) : "",
+ dt->answer_ede_rcode >= 0 ? ")" : "");
+
+ /* Copy error code over */
+ t->answer_dnssec_result = dt->answer_dnssec_result;
+ t->answer_ede_rcode = dt->answer_ede_rcode;
+ r = free_and_strdup(&t->answer_ede_msg, dt->answer_ede_msg);
+ if (r < 0)
+ log_oom_debug();
+
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return 0;
switch (t->scope->protocol) {
case DNS_PROTOCOL_DNS: {
- int ede_rcode;
- _cleanup_free_ char *ede_msg = NULL;
-
assert(t->server);
- ede_rcode = dns_packet_ede_rcode(p, &ede_msg);
- if (ede_rcode < 0 && ede_rcode != -EINVAL)
- log_debug_errno(ede_rcode, "Unable to extract EDE error code from packet, ignoring: %m");
- else {
- t->answer_ede_rcode = ede_rcode;
- t->answer_ede_msg = TAKE_PTR(ede_msg);
- }
+ (void) dns_packet_ede_rcode(p, &t->answer_ede_rcode, &t->answer_ede_msg);
if (!t->bypass &&
IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
/* If the server has replied with detailed error data, using a degraded feature set
* will likely not help anyone. Examine the detailed error to determine the best
* course of action. */
- if (ede_rcode >= 0 && DNS_PACKET_RCODE(p) == DNS_RCODE_SERVFAIL) {
+ if (t->answer_ede_rcode >= 0 && DNS_PACKET_RCODE(p) == DNS_RCODE_SERVFAIL) {
/* These codes are related to DNSSEC configuration errors. If accurate,
* this is the domain operator's problem, and retrying won't help. */
- if (dns_ede_rcode_is_dnssec(ede_rcode)) {
+ if (dns_ede_rcode_is_dnssec(t->answer_ede_rcode)) {
log_debug("Server returned error: %s (%s%s%s). Lookup failed.",
- FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
- FORMAT_DNS_EDE_RCODE(ede_rcode),
- isempty(t->answer_ede_msg) ? "" : ": ",
- t->answer_ede_msg);
- dns_transaction_complete(t, DNS_TRANSACTION_UPSTREAM_DNSSEC_FAILURE);
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
+
+ t->answer_dnssec_result = DNSSEC_UPSTREAM_FAILURE;
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return;
}
/* These codes probably indicate a transient error. Let's try again. */
- if (IN_SET(ede_rcode, DNS_EDE_RCODE_NOT_READY, DNS_EDE_RCODE_NET_ERROR)) {
+ if (IN_SET(t->answer_ede_rcode, DNS_EDE_RCODE_NOT_READY, DNS_EDE_RCODE_NET_ERROR)) {
log_debug("Server returned error: %s (%s%s%s), retrying transaction.",
- FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
- FORMAT_DNS_EDE_RCODE(ede_rcode),
- isempty(t->answer_ede_msg) ? "" : ": ",
- t->answer_ede_msg);
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
dns_transaction_retry(t, false);
return;
}
/* OK, the query failed, but we still shouldn't degrade the feature set for
* this server. */
log_debug("Server returned error: %s (%s%s%s)",
- FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
- FORMAT_DNS_EDE_RCODE(ede_rcode),
- isempty(t->answer_ede_msg) ? "" : ": ", t->answer_ede_msg);
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
break;
- } /* No EDE rcode, or EDE rcode we don't understand */
+ }
/* Request failed, immediately try again with reduced features */
if (DNS_PACKET_RCODE(p) == DNS_RCODE_REFUSED) {
/* This server refused our request? If so, try again, use a different server */
- if (ede_rcode > 0)
+ if (t->answer_ede_rcode >= 0)
log_debug("Server returned REFUSED (%s), switching servers, and retrying.",
- FORMAT_DNS_EDE_RCODE(ede_rcode));
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode));
else
log_debug("Server returned REFUSED, switching servers, and retrying.");
t->answer_source = DNS_TRANSACTION_CACHE;
if (t->answer_rcode == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
- else
+ else {
+ if (t->received)
+ (void) dns_packet_ede_rcode(t->received, &t->answer_ede_rcode, &t->answer_ede_msg);
+
dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE);
+ }
return 0;
}
}
DNS_TRANSACTION_PENDING,
DNS_TRANSACTION_VALIDATING,
DNS_TRANSACTION_RCODE_FAILURE,
- DNS_TRANSACTION_UPSTREAM_DNSSEC_FAILURE,
DNS_TRANSACTION_SUCCESS,
DNS_TRANSACTION_NO_SERVERS,
DNS_TRANSACTION_TIMEOUT,
return -EINVAL;
}
- r = unhexmem(p, strlen(p), &dd, &l);
+ r = unhexmem(p, &dd, &l);
if (r < 0) {
log_warning("Failed to parse DS digest %s on line %s:%u", p, path, line);
return -EINVAL;
return -EINVAL;
}
- r = unbase64mem(p, strlen(p), &k, &l);
+ r = unbase64mem(p, &k, &l);
if (r < 0)
return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", p, path, line);
return 1;
}
-static int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
+int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
usec_t end;
int r;
return 0;
}
-int manager_monitor_send(
- Manager *m,
- int state,
- int rcode,
- int error,
- DnsQuestion *question_idna,
- DnsQuestion *question_utf8,
- DnsPacket *question_bypass,
- DnsQuestion *collected_questions,
- DnsAnswer *answer) {
-
+int manager_monitor_send(Manager *m, DnsQuery *q) {
_cleanup_(json_variant_unrefp) JsonVariant *jquestion = NULL, *jcollected_questions = NULL, *janswer = NULL;
_cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL;
Varlink *connection;
return 0;
/* Merge all questions into one */
- r = dns_question_merge(question_idna, question_utf8, &merged);
+ r = dns_question_merge(q->question_idna, q->question_utf8, &merged);
if (r < 0)
return log_error_errno(r, "Failed to merge UTF8/IDNA questions: %m");
- if (question_bypass) {
+ if (q->question_bypass) {
_cleanup_(dns_question_unrefp) DnsQuestion *merged2 = NULL;
- r = dns_question_merge(merged, question_bypass->question, &merged2);
+ r = dns_question_merge(merged, q->question_bypass->question, &merged2);
if (r < 0)
return log_error_errno(r, "Failed to merge UTF8/IDNA questions and DNS packet question: %m");
return log_error_errno(r, "Failed to convert question to JSON: %m");
/* Generate a JSON array of the questions preceding the current one in the CNAME chain */
- r = dns_question_to_json(collected_questions, &jcollected_questions);
+ r = dns_question_to_json(q->collected_questions, &jcollected_questions);
if (r < 0)
return log_error_errno(r, "Failed to convert question to JSON: %m");
- DNS_ANSWER_FOREACH_ITEM(rri, answer) {
+ DNS_ANSWER_FOREACH_ITEM(rri, q->answer) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
r = dns_resource_record_to_json(rri->rr, &v);
SET_FOREACH(connection, m->varlink_subscription) {
r = varlink_notifyb(connection,
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(state))),
- JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_RCODE_FAILURE, "rcode", JSON_BUILD_INTEGER(rcode)),
- JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_ERRNO, "errno", JSON_BUILD_INTEGER(error)),
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(q->state))),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_DNSSEC_FAILED,
+ "result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_RCODE_FAILURE,
+ "rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_ERRNO,
+ "errno", JSON_BUILD_INTEGER(q->answer_errno)),
+ JSON_BUILD_PAIR_CONDITION(IN_SET(q->state,
+ DNS_TRANSACTION_DNSSEC_FAILED,
+ DNS_TRANSACTION_RCODE_FAILURE) &&
+ q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(IN_SET(q->state,
+ DNS_TRANSACTION_DNSSEC_FAILED,
+ DNS_TRANSACTION_RCODE_FAILURE) &&
+ q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)),
JSON_BUILD_PAIR("question", JSON_BUILD_VARIANT(jquestion)),
- JSON_BUILD_PAIR_CONDITION(jcollected_questions, "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)),
- JSON_BUILD_PAIR_CONDITION(janswer, "answer", JSON_BUILD_VARIANT(janswer))));
+ JSON_BUILD_PAIR_CONDITION(jcollected_questions,
+ "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)),
+ JSON_BUILD_PAIR_CONDITION(janswer,
+ "answer", JSON_BUILD_VARIANT(janswer))));
if (r < 0)
log_debug_errno(r, "Failed to send monitor event, ignoring: %m");
}
uint32_t manager_find_mtu(Manager *m);
-int manager_monitor_send(Manager *m, int state, int rcode, int error, DnsQuestion *question_idna, DnsQuestion *question_utf8, DnsPacket *question_bypass, DnsQuestion *collected_questions, DnsAnswer *answer);
+int manager_monitor_send(Manager *m, DnsQuery *q);
+int sendmsg_loop(int fd, struct msghdr *mh, int flags);
int manager_write(Manager *m, int fd, DnsPacket *p);
int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p);
int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret);
case DNS_TRANSACTION_DNSSEC_FAILED:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed",
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result)))));
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL);
case DNS_TRANSACTION_RCODE_FAILURE:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError",
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode))));
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-daemon.h"
+
+#include "fd-util.h"
+#include "iovec-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "resolved-dns-packet.h"
+#include "resolved-manager.h"
+#include "socket-netlink.h"
+#include "socket-util.h"
+
+/* Taken from resolved-dns-stub.c */
+#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U)
+
+/* This is more or less verbatim manager_recv() from resolved-manager.c, sans the manager stuff */
+static int server_recv(int fd, DnsPacket **ret) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ CMSG_BUFFER_TYPE(CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo))
+ + CMSG_SPACE(int) /* ttl/hoplimit */
+ + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */) control;
+ union sockaddr_union sa;
+ struct iovec iov;
+ struct msghdr mh = {
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ ssize_t ms, l;
+ int r;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ ms = next_datagram_size_fd(fd);
+ if (ms < 0)
+ return ms;
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, ms, DNS_PACKET_SIZE_MAX);
+ if (r < 0)
+ return r;
+
+ iov = IOVEC_MAKE(DNS_PACKET_DATA(p), p->allocated);
+
+ l = recvmsg_safe(fd, &mh, 0);
+ if (ERRNO_IS_NEG_TRANSIENT(l))
+ return 0;
+ if (l <= 0)
+ return l;
+
+ assert(!(mh.msg_flags & MSG_TRUNC));
+
+ p->size = (size_t) l;
+
+ p->family = sa.sa.sa_family;
+ p->ipproto = IPPROTO_UDP;
+ if (p->family == AF_INET) {
+ p->sender.in = sa.in.sin_addr;
+ p->sender_port = be16toh(sa.in.sin_port);
+ } else if (p->family == AF_INET6) {
+ p->sender.in6 = sa.in6.sin6_addr;
+ p->sender_port = be16toh(sa.in6.sin6_port);
+ p->ifindex = sa.in6.sin6_scope_id;
+ } else
+ return -EAFNOSUPPORT;
+
+ p->timestamp = now(CLOCK_BOOTTIME);
+
+ CMSG_FOREACH(cmsg, &mh) {
+
+ if (cmsg->cmsg_level == IPPROTO_IPV6) {
+ assert(p->family == AF_INET6);
+
+ switch (cmsg->cmsg_type) {
+
+ case IPV6_PKTINFO: {
+ struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
+
+ if (p->ifindex <= 0)
+ p->ifindex = i->ipi6_ifindex;
+
+ p->destination.in6 = i->ipi6_addr;
+ break;
+ }
+
+ case IPV6_HOPLIMIT:
+ p->ttl = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+
+ case IPV6_RECVFRAGSIZE:
+ p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+ }
+ } else if (cmsg->cmsg_level == IPPROTO_IP) {
+ assert(p->family == AF_INET);
+
+ switch (cmsg->cmsg_type) {
+
+ case IP_PKTINFO: {
+ struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
+
+ if (p->ifindex <= 0)
+ p->ifindex = i->ipi_ifindex;
+
+ p->destination.in = i->ipi_addr;
+ break;
+ }
+
+ case IP_TTL:
+ p->ttl = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+
+ case IP_RECVFRAGSIZE:
+ p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+ }
+ }
+ }
+
+ /* The Linux kernel sets the interface index to the loopback
+ * device if the packet came from the local host since it
+ * avoids the routing table in such a case. Let's unset the
+ * interface index in such a case. */
+ if (p->ifindex == LOOPBACK_IFINDEX)
+ p->ifindex = 0;
+
+ log_debug("Received DNS UDP packet of size %zu, ifindex=%i, ttl=%u, fragsize=%zu, sender=%s, destination=%s",
+ p->size, p->ifindex, p->ttl, p->fragsize,
+ IN_ADDR_TO_STRING(p->family, &p->sender),
+ IN_ADDR_TO_STRING(p->family, &p->destination));
+
+ *ret = TAKE_PTR(p);
+ return 1;
+}
+
+/* Same as above, see manager_ipv4_send() in resolved-manager.c */
+static int server_ipv4_send(
+ int fd,
+ const struct in_addr *destination,
+ uint16_t port,
+ const struct in_addr *source,
+ DnsPacket *packet) {
+
+ union sockaddr_union sa;
+ struct iovec iov;
+ struct msghdr mh = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa.in),
+ };
+
+ assert(fd >= 0);
+ assert(destination);
+ assert(port > 0);
+ assert(packet);
+
+ iov = IOVEC_MAKE(DNS_PACKET_DATA(packet), packet->size);
+
+ sa = (union sockaddr_union) {
+ .in.sin_family = AF_INET,
+ .in.sin_addr = *destination,
+ .in.sin_port = htobe16(port),
+ };
+
+ return sendmsg_loop(fd, &mh, 0);
+}
+
+static int make_reply_packet(DnsPacket *packet, DnsPacket **ret) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ int r;
+
+ assert(packet);
+ assert(ret);
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_PAYLOAD_SIZE_MAX(packet));
+ if (r < 0)
+ return r;
+
+ r = dns_packet_append_question(p, packet->question);
+ if (r < 0)
+ return r;
+
+ DNS_PACKET_HEADER(p)->id = DNS_PACKET_ID(packet);
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(packet->question));
+
+ *ret = TAKE_PTR(p);
+ return 0;
+}
+
+static int reply_append_edns(DnsPacket *packet, DnsPacket *reply, const char *extra_text, size_t rcode, uint16_t ede_code) {
+ size_t saved_size;
+ int r;
+
+ assert(packet);
+ assert(reply);
+
+ /* Append EDNS0 stuff (inspired by dns_packet_append_opt() from resolved-dns-packet.c).
+ *
+ * Relevant headers from RFC 6891:
+ *
+ * +------------+--------------+------------------------------+
+ * | Field Name | Field Type | Description |
+ * +------------+--------------+------------------------------+
+ * | NAME | domain name | MUST be 0 (root domain) |
+ * | TYPE | u_int16_t | OPT (41) |
+ * | CLASS | u_int16_t | requestor's UDP payload size |
+ * | TTL | u_int32_t | extended RCODE and flags |
+ * | RDLEN | u_int16_t | length of all RDATA |
+ * | RDATA | octet stream | {attribute,value} pairs |
+ * +------------+--------------+------------------------------+
+ *
+ * +0 (MSB) +1 (LSB)
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 0: | OPTION-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 2: | OPTION-LENGTH |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 4: | |
+ * / OPTION-DATA /
+ * / /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ *
+ * And from RFC 8914:
+ *
+ * 1 1 1 1 1 1
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 0: | OPTION-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 2: | OPTION-LENGTH |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 4: | INFO-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 6: / EXTRA-TEXT ... /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ */
+
+ saved_size = reply->size;
+
+ /* empty name */
+ r = dns_packet_append_uint8(reply, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* type */
+ r = dns_packet_append_uint16(reply, DNS_TYPE_OPT, NULL);
+ if (r < 0)
+ return r;
+
+ /* class: maximum udp packet that can be received */
+ r = dns_packet_append_uint16(reply, ADVERTISE_DATAGRAM_SIZE_MAX, NULL);
+ if (r < 0)
+ return r;
+
+ /* extended RCODE and VERSION */
+ r = dns_packet_append_uint16(reply, ((uint16_t) rcode & 0x0FF0) << 4, NULL);
+ if (r < 0)
+ return r;
+
+ /* flags: DNSSEC OK (DO), see RFC3225 */
+ r = dns_packet_append_uint16(reply, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* RDATA */
+
+ size_t extra_text_len = isempty(extra_text) ? 0 : strlen(extra_text);
+ /* RDLENGTH (OPTION CODE + OPTION LENGTH + INFO-CODE + EXTRA-TEXT) */
+ r = dns_packet_append_uint16(reply, 2 + 2 + 2 + extra_text_len, NULL);
+ if (r < 0)
+ return 0;
+
+ /* OPTION-CODE: 15 for EDE */
+ r = dns_packet_append_uint16(reply, 15, NULL);
+ if (r < 0)
+ return r;
+
+ /* OPTION-LENGTH: INFO-CODE + EXTRA-TEXT */
+ r = dns_packet_append_uint16(reply, 2 + extra_text_len, NULL);
+ if (r < 0)
+ return r;
+
+ /* INFO-CODE: EDE code */
+ r = dns_packet_append_uint16(reply, ede_code, NULL);
+ if (r < 0)
+ return r;
+
+ /* EXTRA-TEXT */
+ if (extra_text_len > 0) {
+ /* From RFC 8914:
+ * EDE text may be null terminated but MUST NOT be assumed to be; the length MUST be derived
+ * from the OPTION-LENGTH field
+ *
+ * Let's exercise our code on the receiving side and not NUL-terminate the EXTRA-TEXT field
+ */
+ r = dns_packet_append_blob(reply, extra_text, extra_text_len, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ DNS_PACKET_HEADER(reply)->arcount = htobe16(DNS_PACKET_ARCOUNT(reply) + 1);
+ reply->opt_start = saved_size;
+ reply->opt_size = reply->size - saved_size;
+
+ /* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */
+ DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+ 1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode));
+ return 0;
+}
+
+static void server_fail(DnsPacket *packet, DnsPacket *reply, int rcode) {
+ assert(reply);
+
+ /* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */
+ DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+ 1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode));
+}
+
+static int server_handle_edns_bogus_dnssec(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+
+ return reply_append_edns(packet, reply, NULL, DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_DNSSEC_BOGUS);
+}
+
+static int server_handle_edns_extra_text(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+
+ return reply_append_edns(packet, reply, "Nothing to see here!", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_CENSORED);
+}
+
+static int server_handle_edns_invalid_code(DnsPacket *packet, DnsPacket *reply, const char *extra_text) {
+ assert(packet);
+ assert(reply);
+ assert_cc(_DNS_EDE_RCODE_MAX_DEFINED < UINT16_MAX);
+
+ return reply_append_edns(packet, reply, extra_text, DNS_RCODE_SERVFAIL, _DNS_EDE_RCODE_MAX_DEFINED + 1);
+}
+
+static int server_handle_edns_code_zero(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+ assert_cc(DNS_EDE_RCODE_OTHER == 0);
+
+ return reply_append_edns(packet, reply, "\xF0\x9F\x90\xB1", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_OTHER);
+}
+
+static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+ const char *name;
+ int r;
+
+ assert(fd >= 0);
+
+ r = server_recv(fd, &packet);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to receive packet, ignoring: %m");
+ return 0;
+ }
+
+ r = dns_packet_validate_query(packet);
+ if (r < 0) {
+ log_debug_errno(r, "Invalid DNS UDP packet, ignoring.");
+ return 0;
+ }
+
+ r = dns_packet_extract(packet);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract DNS packet, ignoring: %m");
+ return 0;
+ }
+
+ name = dns_question_first_name(packet->question);
+ log_info("Processing question for name '%s'", name);
+
+ (void) dns_question_dump(packet->question, stdout);
+
+ r = make_reply_packet(packet, &reply);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to make reply packet, ignoring: %m");
+ return 0;
+ }
+
+ if (streq_ptr(name, "edns-bogus-dnssec.forwarded.test"))
+ r = server_handle_edns_bogus_dnssec(packet, reply);
+ else if (streq_ptr(name, "edns-extra-text.forwarded.test"))
+ r = server_handle_edns_extra_text(packet, reply);
+ else if (streq_ptr(name, "edns-invalid-code.forwarded.test"))
+ r = server_handle_edns_invalid_code(packet, reply, NULL);
+ else if (streq_ptr(name, "edns-invalid-code-with-extra-text.forwarded.test"))
+ r = server_handle_edns_invalid_code(packet, reply, "Hello [#]$%~ World");
+ else if (streq_ptr(name, "edns-code-zero.forwarded.test"))
+ r = server_handle_edns_code_zero(packet, reply);
+ else
+ r = log_debug_errno(SYNTHETIC_ERRNO(EFAULT), "Unhandled name '%s', ignoring.", name);
+ if (r < 0)
+ server_fail(packet, reply, DNS_RCODE_NXDOMAIN);
+
+ r = server_ipv4_send(fd, &packet->sender.in, packet->sender_port, &packet->destination.in, reply);
+ if (r < 0)
+ log_debug_errno(r, "Failed to send reply, ignoring: %m");
+
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ log_setup();
+
+ if (argc != 2)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "This program takes one argument in format ip_address:port");
+
+ fd = make_socket_fd(LOG_DEBUG, argv[1], SOCK_DGRAM, SOCK_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to listen on address '%s': %m", argv[1]);
+
+ r = sd_event_default(&event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate event: %m");
+
+ r = sd_event_add_io(event, NULL, fd, EPOLLIN, on_dns_packet, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add IO event source: %m");
+
+ r = sd_event_set_signal_exit(event, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
+
+ (void) sd_notify(/* unset_environment=false */ false, "READY=1");
+
+ r = sd_event_loop(event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run event loop: %m");
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
#include "ptyfwd.h"
#include "signal-util.h"
#include "spawn-polkit-agent.h"
+#include "special.h"
#include "strv.h"
#include "terminal-util.h"
#include "unit-def.h"
strv_free_and_replace(arg_cmdline, l);
if (!arg_slice) {
- arg_slice = strdup("user.slice");
+ arg_slice = strdup(SPECIAL_USER_SLICE);
if (!arg_slice)
return log_oom();
}
log_info("CPU time consumed: %s",
FORMAT_TIMESPAN(DIV_ROUND_UP(c.cpu_usage_nsec, NSEC_PER_USEC), USEC_PER_MSEC));
- if (c.memory_peak != UINT64_MAX)
- log_info("Memory peak: %s", FORMAT_BYTES(c.memory_peak));
+ if (c.memory_peak != UINT64_MAX) {
+ const char *swap;
- if (c.memory_swap_peak != UINT64_MAX)
- log_info("Memory swap peak: %s", FORMAT_BYTES(c.memory_swap_peak));
+ if (c.memory_swap_peak != UINT64_MAX)
+ swap = strjoina(" (swap: ", FORMAT_BYTES(c.memory_swap_peak), ")");
+ else
+ swap = "";
- if (c.ip_ingress_bytes != UINT64_MAX)
- log_info("IP traffic received: %s", FORMAT_BYTES(c.ip_ingress_bytes));
+ log_info("Memory peak: %s%s", FORMAT_BYTES(c.memory_peak), swap);
+ }
+
+ const char *ip_ingress = NULL, *ip_egress = NULL;
+
+ if (!IN_SET(c.ip_ingress_bytes, 0, UINT64_MAX))
+ ip_ingress = strjoina(" received: ", FORMAT_BYTES(c.ip_ingress_bytes));
+ if (!IN_SET(c.ip_egress_bytes, 0, UINT64_MAX))
+ ip_egress = strjoina(" sent: ", FORMAT_BYTES(c.ip_egress_bytes));
+
+ if (ip_ingress || ip_egress)
+ log_info("IP traffic%s%s", strempty(ip_ingress), strempty(ip_egress));
- if (c.ip_egress_bytes != UINT64_MAX)
- log_info("IP traffic sent: %s", FORMAT_BYTES(c.ip_egress_bytes));
+ const char *io_read = NULL, *io_write = NULL;
- if (c.io_read_bytes != UINT64_MAX)
- log_info("IO bytes read: %s", FORMAT_BYTES(c.io_read_bytes));
+ if (!IN_SET(c.io_read_bytes, 0, UINT64_MAX))
+ io_read = strjoina(" read: ", FORMAT_BYTES(c.io_read_bytes));
+ if (!IN_SET(c.io_write_bytes, 0, UINT64_MAX))
+ io_write = strjoina(" written: ", FORMAT_BYTES(c.io_write_bytes));
- if (c.io_write_bytes != UINT64_MAX)
- log_info("IO bytes written: %s", FORMAT_BYTES(c.io_write_bytes));
+ if (io_read || io_write)
+ log_info("IO bytes%s%s", strempty(io_read), strempty(io_write));
}
/* Try to propagate the service's return value. But if the service defines
r = on_ac_power();
if (r < 0)
- log_debug_errno(r, "Failed to check if the system is running on AC, assuming it is not: %m");
+ log_warning_errno(r, "Failed to check if the system is running on AC, assuming it is not: %m");
if (r > 0)
return false;
r = battery_enumerator_new(&e);
if (r < 0)
- return log_debug_errno(r, "Failed to initialize battery enumerator: %m");
+ return log_error_errno(r, "Failed to initialize battery enumerator: %m");
FOREACH_DEVICE(e, dev) {
int level;
return utf8_is_valid(p) && string_is_safe(p) && filename_is_valid(p);
}
-static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType *type, char **token) {
+static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *type, char **token) {
_cleanup_free_ char *buf = NULL, *p = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(dir);
assert(type);
assert(*type == BOOT_ENTRY_TOKEN_AUTO);
assert(token);
- if (!etc_kernel)
- return 0;
-
- p = path_join(etc_kernel, "entry-token");
+ p = path_join(dir, "entry-token");
if (!p)
return log_oom();
return 1;
}
+static int entry_token_load(int rfd, const char *conf_root, BootEntryTokenType *type, char **token) {
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(type);
+ assert(*type == BOOT_ENTRY_TOKEN_AUTO);
+ assert(token);
+
+ if (conf_root)
+ return entry_token_load_one(rfd, conf_root, type, token);
+
+ FOREACH_STRING(path, "/etc/kernel", "/usr/lib/kernel") {
+ r = entry_token_load_one(rfd, path, type, token);
+ if (r != 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int entry_token_from_machine_id(sd_id128_t machine_id, BootEntryTokenType *type, char **token) {
char *p;
int boot_entry_token_ensure_at(
int rfd,
- const char *etc_kernel,
+ const char *conf_root,
sd_id128_t machine_id,
bool machine_id_is_random,
BootEntryTokenType *type,
switch (*type) {
case BOOT_ENTRY_TOKEN_AUTO:
- r = entry_token_load(rfd, etc_kernel, type, token);
+ r = entry_token_load(rfd, conf_root, type, token);
if (r != 0)
return r;
int boot_entry_token_ensure(
const char *root,
- const char *etc_kernel,
+ const char *conf_root,
sd_id128_t machine_id,
bool machine_id_is_random,
BootEntryTokenType *type,
if (rfd < 0)
return -errno;
- return boot_entry_token_ensure_at(rfd, etc_kernel, machine_id, machine_id_is_random, type, token);
+ return boot_entry_token_ensure_at(rfd, conf_root, machine_id, machine_id_is_random, type, token);
}
int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token) {
int boot_entry_token_ensure(
const char *root,
- const char *etc_kernel, /* will be prefixed with root, typically /etc/kernel. */
+ const char *conf_root, /* will be prefixed with root, typically /etc/kernel. */
sd_id128_t machine_id,
bool machine_id_is_random,
BootEntryTokenType *type, /* input and output */
char **token); /* output, but do not pass uninitialized value. */
int boot_entry_token_ensure_at(
int rfd,
- const char *etc_kernel,
+ const char *conf_root,
sd_id128_t machine_id,
bool machine_id_is_random,
BootEntryTokenType *type,
AsyncPolkitQueryAction *action;
sd_bus *bus;
- sd_bus_message *request;
+ sd_bus_message *request; /* the original bus method call that triggered the polkit auth, NULL in case of varlink */
sd_bus_slot *slot;
- Varlink *link;
+ Varlink *link; /* the original varlink method call that triggered the polkit auth, NULL in case of bus */
Hashmap *registry;
sd_event_source *defer_event_source;
sd_bus_slot_unref(q->slot);
- if (q->registry && q->request)
- hashmap_remove(q->registry, q->request);
+ if (q->registry) {
+ if (q->request)
+ hashmap_remove(q->registry, q->request);
+ if (q->link)
+ hashmap_remove(q->registry, q->link);
+ }
sd_bus_message_unref(q->request);
e = sd_bus_message_get_error(reply);
- if (bus_error_is_unknown_service(e))
- /* Treat no PK available as access denied */
+ if (bus_error_is_unknown_service(e) ||
+ sd_bus_error_has_names(
+ e,
+ "org.freedesktop.PolicyKit1.Error.Failed",
+ "org.freedesktop.PolicyKit1.Error.Cancelled",
+ "org.freedesktop.PolicyKit1.Error.NotAuthorized"))
+ /* Treat no PK available as access denied. Also treat some of the well-known PK errors as such. */
q->denied_action = TAKE_PTR(a);
else {
/* Save error from polkit reply, so it can be returned when the same authorization
if (q->request)
(void) sd_bus_reply_method_errno(q->request, r, NULL);
if (q->link)
- varlink_error_errno(q->link, r);
+ (void) varlink_error_errno(q->link, r);
async_polkit_query_unref(q);
}
return r;
if (r < 0) {
/* Reply with a nice error */
if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED))
- return varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL);
-
- if (ERRNO_IS_NEG_PRIVILEGE(r))
- return varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL);
+ (void) varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL);
+ else if (ERRNO_IS_NEG_PRIVILEGE(r))
+ (void) varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL);
return r;
}
_cleanup_free_ void *decoded = NULL;
size_t decoded_size;
- r = unbase64mem(p, SIZE_MAX, &decoded, &decoded_size);
+ r = unbase64mem(p, &decoded, &decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to base64 decode encrypted credential: %m");
_cleanup_free_ void *decoded = NULL;
size_t sz;
- r = unbase64mem(eq, SIZE_MAX, &decoded, &sz);
+ r = unbase64mem(eq, &decoded, &sz);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 data '%s': %m", eq);
return bus_append_string(m, "RootHashPath", eq);
/* We have a roothash to decode, eg: RootHash=012345789abcdef */
- r = unhexmem(eq, strlen(eq), &roothash_decoded, &roothash_decoded_size);
+ r = unhexmem(eq, &roothash_decoded, &roothash_decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to decode RootHash= '%s': %m", eq);
if (roothash_decoded_size < sizeof(sd_id128_t))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:': %m", eq);
/* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */
- r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size);
+ r = unbase64mem(value, &roothash_sig_decoded, &roothash_sig_decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to decode RootHashSignature= '%s': %m", eq);
#include "bus-internal.h"
#include "bus-label.h"
#include "bus-util.h"
+#include "daemon-util.h"
#include "data-fd-util.h"
#include "fd-util.h"
#include "memstream-util.h"
if (r == 0 && !exiting && idle) {
/* Inform the service manager that we are going down, so that it will queue all
- * further start requests, instead of assuming we are already running. */
- sd_notify(false, "STOPPING=1");
+ * further start requests, instead of assuming we are still running. */
+ (void) sd_notify(false, NOTIFY_STOPPING);
r = bus_async_unregister_and_exit(e, bus, name);
if (r < 0)
#include "string-util.h"
#include "tomoyo-util.h"
#include "tpm2-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#include "virt.h"
return 0;
}
-/* Parse one main config file located in /etc/systemd and its drop-ins, which is what all systemd daemons
+/* Parse one main config file located in /etc/$pkgdir and its drop-ins, which is what all systemd daemons
* do. */
-int config_parse_config_file(
+int config_parse_config_file_full(
const char *conf_file,
+ const char *pkgdir,
const char *sections,
ConfigItemLookup lookup,
const void *table,
int r;
assert(conf_file);
+ assert(pkgdir);
/* build the dropin dir list */
dropin_dirs = new0(char*, strv_length(conf_paths) + 1);
STRV_FOREACH(p, conf_paths) {
char *d;
- d = strjoin(*p, "systemd/", conf_file, ".d");
+ d = strjoin(*p, pkgdir, "/", conf_file, ".d");
if (!d) {
if (flags & CONFIG_PARSE_WARN)
return log_oom();
if (r < 0)
return r;
- const char *sysconf_file = strjoina(PKGSYSCONFDIR, "/", conf_file);
+ const char *sysconf_file = strjoina(SYSCONF_DIR, "/", pkgdir, "/", conf_file);
return config_parse_many_files(STRV_MAKE_CONST(sysconf_file), dropins,
sections, lookup, table, flags, userdata, NULL);
return true;
}
-static void config_section_hash_func(const ConfigSection *c, struct siphash *state) {
+void config_section_hash_func(const ConfigSection *c, struct siphash *state) {
siphash24_compress_string(c->filename, state);
siphash24_compress_typesafe(c->line, state);
}
-static int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) {
+int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) {
int r;
r = strcmp(x->filename, y->filename);
if (isempty(rvalue)) {
*t = -1;
- return 0;
+ return 1;
}
r = parse_tristate(rvalue, t);
return 0;
}
- return 0;
+ return 1;
}
int config_parse_string(
void *userdata) {
char **s = ASSERT_PTR(data);
+ int r;
assert(filename);
assert(lvalue);
if (isempty(rvalue)) {
*s = mfree(*s);
- return 0;
+ return 1;
}
if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue)) {
return 0;
}
- return free_and_strdup_warn(s, empty_to_null(rvalue));
+ r = free_and_strdup_warn(s, empty_to_null(rvalue));
+ if (r < 0)
+ return r;
+
+ return 1;
}
int config_parse_dns_name(
void *userdata,
struct stat *ret_stat); /* possibly NULL */
-int config_parse_config_file(
+int config_parse_config_file_full(
const char *conf_file,
+ const char *pkgdir,
const char *sections, /* nulstr */
ConfigItemLookup lookup,
const void *table,
ConfigParseFlags flags,
void *userdata);
+static inline int config_parse_config_file(
+ const char *conf_file,
+ const char *sections, /* nulstr */
+ ConfigItemLookup lookup,
+ const void *table,
+ ConfigParseFlags flags,
+ void *userdata) {
+ return config_parse_config_file_full(conf_file, "systemd", sections, lookup, table, flags, userdata);
+}
+
int config_parse_many(
const char* const* conf_files, /* possibly empty */
const char* const* conf_file_dirs,
DEFINE_TRIVIAL_CLEANUP_FUNC(ConfigSection*, config_section_free);
int config_section_new(const char *filename, unsigned line, ConfigSection **ret);
+
+void config_section_hash_func(const ConfigSection *c, struct siphash *state);
+int config_section_compare_func(const ConfigSection *x, const ConfigSection *y);
extern const struct hash_ops config_section_hash_ops;
+
int _hashmap_by_section_find_unused_line(
HashmapBase *entries_by_section,
const char *filename,
#include "sparse-endian.h"
#include "stat-util.h"
#include "tpm2-util.h"
-#include "virt.h"
#define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024))
}
int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size) {
+ _cleanup_(iovec_done_erase) struct iovec ret_iovec = {};
_cleanup_(erase_and_freep) void *data = NULL;
_cleanup_free_ char *fn = NULL;
size_t sz = 0;
const char *d;
int r;
- assert(ret);
-
/* Just like read_credential() but will also look for encrypted credentials. Note that services only
* receive decrypted credentials, hence use read_credential() for those. This helper here is for
* generators, i.e. code that runs outside of service context, and thus has no decrypted credentials
now(CLOCK_REALTIME),
/* tpm2_device = */ NULL,
/* tpm2_signature_path = */ NULL,
- data,
- sz,
- ret,
- ret_size);
+ &IOVEC_MAKE(data, sz),
+ /* flags= */ 0,
+ &ret_iovec);
if (r < 0)
return r;
+ if (ret)
+ *ret = TAKE_PTR(ret_iovec.iov_base);
+ if (ret_size)
+ *ret_size = ret_iovec.iov_len;
+
return 1; /* found */
not_found:
- *ret = NULL;
-
+ if (ret)
+ *ret = NULL;
if (ret_size)
*ret_size = 0;
...) {
_cleanup_free_ void *b = NULL;
+ bool all = true;
int r, ret = 0;
/* Reads a bunch of credentials into the specified buffers. If the specified buffers are already
r = read_credential(first_name, &b, NULL);
if (r == -ENXIO) /* No creds passed at all? Bail immediately. */
return 0;
- if (r < 0) {
- if (r != -ENOENT)
- ret = r;
- } else
+ if (r == -ENOENT)
+ all = false;
+ else if (r < 0)
+ RET_GATHER(ret, r);
+ else
free_and_replace(*first_value, b);
va_list ap;
if (!name)
break;
- value = va_arg(ap, char **);
- if (*value)
- continue;
+ value = ASSERT_PTR(va_arg(ap, char **));
r = read_credential(name, &bb, NULL);
- if (r < 0) {
- if (ret >= 0 && r != -ENOENT)
- ret = r;
- } else
+ if (r == -ENOENT)
+ all = false;
+ else if (r < 0)
+ RET_GATHER(ret, r);
+ else
free_and_replace(*value, bb);
}
va_end(ap);
- return ret;
+ return ret < 0 ? ret : all;
}
int read_credential_bool(const char *name) {
CredentialSecretFlags flags,
const char *dirname,
const char *fn,
- void **ret_data,
- size_t *ret_size) {
+ struct iovec *ret) {
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -EBADF;
goto fail;
}
- if (ret_data) {
+ if (ret) {
void *copy;
copy = memdup(buf.data, sizeof(buf.data));
goto fail;
}
- *ret_data = copy;
+ *ret = IOVEC_MAKE(copy, sizeof(buf.data));
}
- if (ret_size)
- *ret_size = sizeof(buf.data);
-
return 0;
fail:
return r;
}
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) {
_cleanup_free_ char *_dirname = NULL, *_filename = NULL;
_cleanup_close_ int dfd = -EBADF;
sd_id128_t machine_id;
"Failed to open %s/%s: %m", dirname, filename);
- r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret, ret_size);
+ r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret);
if (r == -EEXIST) {
log_debug_errno(r, "Credential secret %s/%s appeared while we were creating it, rereading.",
dirname, filename);
if (!copy)
return log_oom_debug();
- *ret = copy;
+ *ret = IOVEC_MAKE(copy, sz);
}
- if (ret_size)
- *ret_size = sz;
-
return 0;
}
#define CREDENTIAL_FIELD_SIZE_MAX (16U*1024U)
static int sha256_hash_host_and_tpm2_key(
- const void *host_key,
- size_t host_key_size,
- const void *tpm2_key,
- size_t tpm2_key_size,
+ const struct iovec *host_key,
+ const struct iovec *tpm2_key,
uint8_t ret[static SHA256_DIGEST_LENGTH]) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md = NULL;
unsigned l;
- assert(host_key_size == 0 || host_key);
- assert(tpm2_key_size == 0 || tpm2_key);
+ assert(iovec_is_valid(host_key));
+ assert(iovec_is_valid(tpm2_key));
assert(ret);
/* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */
if (EVP_DigestInit_ex(md, EVP_sha256(), NULL) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context.");
- if (host_key && EVP_DigestUpdate(md, host_key, host_key_size) != 1)
+ if (iovec_is_set(host_key) && EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key.");
- if (tpm2_key && EVP_DigestUpdate(md, tpm2_key, tpm2_key_size) != 1)
+ if (iovec_is_set(tpm2_key) && EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key.");
assert(EVP_MD_CTX_size(md) == SHA256_DIGEST_LENGTH);
uint32_t tpm2_hash_pcr_mask,
const char *tpm2_pubkey_path,
uint32_t tpm2_pubkey_pcr_mask,
- const void *input,
- size_t input_size,
- void **ret,
- size_t *ret_size) {
+ const struct iovec *input,
+ CredentialFlags flags,
+ struct iovec *ret) {
+ _cleanup_(iovec_done) struct iovec tpm2_blob = {}, tpm2_policy_hash = {}, iv = {}, pubkey = {};
+ _cleanup_(iovec_done_erase) struct iovec tpm2_key = {}, output = {}, host_key = {};
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
- _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL;
- size_t host_key_size = 0, tpm2_key_size = 0, tpm2_blob_size = 0, tpm2_policy_hash_size = 0, output_size, p, ml;
- _cleanup_free_ void *tpm2_blob = NULL, *tpm2_policy_hash = NULL, *iv = NULL, *output = NULL;
_cleanup_free_ struct metadata_credential_header *m = NULL;
uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0;
struct encrypted_credential_header *h;
int ksz, bsz, ivsz, tsz, added, r;
- _cleanup_free_ void *pubkey = NULL;
- size_t pubkey_size = 0;
uint8_t md[SHA256_DIGEST_LENGTH];
const EVP_CIPHER *cc;
sd_id128_t id;
+ size_t p, ml;
- assert(input || input_size == 0);
+ assert(iovec_is_valid(input));
assert(ret);
- assert(ret_size);
if (!sd_id128_in_set(with_key,
_CRED_AUTO,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
- CRED_AES256_GCM_BY_TPM2_ABSENT))
+ CRED_AES256_GCM_BY_NULL))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key));
if (name && !credential_name_valid(name))
CREDENTIAL_SECRET_GENERATE|
CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED|
(sd_id128_equal(with_key, _CRED_AUTO) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
- &host_key,
- &host_key_size);
+ &host_key);
if (r == -ENOMEDIUM && sd_id128_equal(with_key, _CRED_AUTO))
log_debug_errno(r, "Credential host secret location on temporary file system, not using.");
else if (r < 0)
/* Load public key for PCR policies, if one is specified, or explicitly requested */
- r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey, &pubkey_size);
+ r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD))
return log_error_errno(r, "Failed read TPM PCR public key: %m");
}
}
- if (!pubkey)
+ if (!iovec_is_set(&pubkey))
tpm2_pubkey_pcr_mask = 0;
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
return log_error_errno(r, "Could not read PCR values: %m");
TPM2B_PUBLIC public;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public);
+ if (iovec_is_set(&pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
}
r = tpm2_calculate_sealing_policy(
tpm2_hash_pcr_values,
tpm2_n_hash_pcr_values,
- pubkey ? &public : NULL,
+ iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false,
/* pcrlock_policy= */ NULL,
&tpm2_policy);
/* seal_key_handle= */ 0,
&tpm2_policy,
/* pin= */ NULL,
- &tpm2_key, &tpm2_key_size,
- &tpm2_blob, &tpm2_blob_size,
+ &tpm2_key,
+ &tpm2_blob,
&tpm2_primary_alg,
- /* ret_srk_buf= */ NULL,
- /* ret_srk_buf_size= */ NULL);
+ /* ret_srk= */ NULL);
if (r < 0) {
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m");
}
- tpm2_policy_hash_size = tpm2_policy.size;
- tpm2_policy_hash = malloc(tpm2_policy_hash_size);
- if (!tpm2_policy_hash)
+ if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash))
return log_oom();
- memcpy(tpm2_policy_hash, tpm2_policy.buffer, tpm2_policy_hash_size);
- assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX);
- assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX);
+ assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
+ assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
}
#endif
if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
/* Let's settle the key type in auto mode now. */
- if (host_key && tpm2_key)
- id = pubkey ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
- else if (tpm2_key)
- id = pubkey ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
- else if (host_key)
+ if (iovec_is_set(&host_key) && iovec_is_set(&tpm2_key))
+ id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
+ else if (iovec_is_set(&tpm2_key))
+ id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
+ else if (iovec_is_set(&host_key))
id = CRED_AES256_GCM_BY_HOST;
else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
- id = CRED_AES256_GCM_BY_TPM2_ABSENT;
+ id = CRED_AES256_GCM_BY_NULL;
else
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"TPM2 not available and host key located on temporary file system, no encryption key available.");
} else
id = with_key;
- if (sd_id128_equal(id, CRED_AES256_GCM_BY_TPM2_ABSENT))
+ if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL) && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Using a null key for encryption and signing. Confidentiality or authenticity will not be provided.");
/* Let's now take the host key and the TPM2 key and hash it together, to use as encryption key for the data */
- r = sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
+ r = sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
if (r < 0)
return r;
if (ivsz > 0) {
assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX);
- iv = malloc(ivsz);
- if (!iv)
+ iv.iov_base = malloc(ivsz);
+ if (!iv.iov_base)
return log_oom();
- r = crypto_random_bytes(iv, ivsz);
+ iv.iov_len = ivsz;
+
+ r = crypto_random_bytes(iv.iov_base, iv.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to acquired randomized IV: %m");
}
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_EncryptInit_ex(context, cc, NULL, md, iv) != 1)
+ if (EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s",
ERR_error_string(ERR_get_error(), NULL));
/* Just an upper estimate */
- output_size =
+ output.iov_len =
ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) +
- ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) +
- ALIGN8(pubkey ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size : 0) +
+ ALIGN8(iovec_is_set(&tpm2_key) ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len : 0) +
+ ALIGN8(iovec_is_set(&pubkey) ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) +
- input_size + 2U * (size_t) bsz +
+ input->iov_len + 2U * (size_t) bsz +
tsz;
- output = malloc0(output_size);
- if (!output)
+ output.iov_base = malloc0(output.iov_len);
+ if (!output.iov_base)
return log_oom();
- h = (struct encrypted_credential_header*) output;
+ h = (struct encrypted_credential_header*) output.iov_base;
h->id = id;
h->block_size = htole32(bsz);
h->key_size = htole32(ksz);
h->tag_size = htole32(tsz);
h->iv_size = htole32(ivsz);
- memcpy(h->iv, iv, ivsz);
+ memcpy(h->iv, iv.iov_base, ivsz);
p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz);
- if (tpm2_key) {
+ if (iovec_is_set(&tpm2_key)) {
struct tpm2_credential_header *t;
- t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
+ t = (struct tpm2_credential_header*) ((uint8_t*) output.iov_base + p);
t->pcr_mask = htole64(tpm2_hash_pcr_mask);
t->pcr_bank = htole16(tpm2_pcr_bank);
t->primary_alg = htole16(tpm2_primary_alg);
- t->blob_size = htole32(tpm2_blob_size);
- t->policy_hash_size = htole32(tpm2_policy_hash_size);
- memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size);
- memcpy(t->policy_hash_and_blob + tpm2_blob_size, tpm2_policy_hash, tpm2_policy_hash_size);
+ t->blob_size = htole32(tpm2_blob.iov_len);
+ t->policy_hash_size = htole32(tpm2_policy_hash.iov_len);
+ memcpy(t->policy_hash_and_blob, tpm2_blob.iov_base, tpm2_blob.iov_len);
+ memcpy(t->policy_hash_and_blob + tpm2_blob.iov_len, tpm2_policy_hash.iov_base, tpm2_policy_hash.iov_len);
- p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size);
+ p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len);
}
- if (pubkey) {
+ if (iovec_is_set(&pubkey)) {
struct tpm2_public_key_credential_header *z;
- z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output + p);
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output.iov_base + p);
z->pcr_mask = htole64(tpm2_pubkey_pcr_mask);
- z->size = htole32(pubkey_size);
- memcpy(z->data, pubkey, pubkey_size);
+ z->size = htole32(pubkey.iov_len);
+ memcpy(z->data, pubkey.iov_base, pubkey.iov_len);
- p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size);
+ p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len);
}
/* Pass the encrypted + TPM2 header as AAD */
- if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1)
+ if (EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
ERR_error_string(ERR_get_error(), NULL));
memcpy_safe(m->name, name, ml);
/* And encrypt the metadata header */
- if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
+ if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
/* Then encrypt the plaintext */
- if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, input, input_size) != 1)
+ if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
/* Finalize */
- if (EVP_EncryptFinal_ex(context, (uint8_t*) output + p, &added) != 1)
+ if (EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
- assert(p <= output_size - tsz);
+ assert(p <= output.iov_len - tsz);
/* Append tag */
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output + p) != 1)
+ if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s",
ERR_error_string(ERR_get_error(), NULL));
p += tsz;
- assert(p <= output_size);
+ assert(p <= output.iov_len);
+ output.iov_len = p;
- if (DEBUG_LOGGING && input_size > 0) {
+ if (DEBUG_LOGGING && input->iov_len > 0) {
size_t base64_size;
- base64_size = DIV_ROUND_UP(p * 4, 3); /* Include base64 size increase in debug output */
- assert(base64_size >= input_size);
- log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input_size, base64_size, base64_size * 100 / input_size - 100);
+ base64_size = DIV_ROUND_UP(output.iov_len * 4, 3); /* Include base64 size increase in debug output */
+ assert(base64_size >= input->iov_len);
+ log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input->iov_len, base64_size, base64_size * 100 / input->iov_len - 100);
}
- *ret = TAKE_PTR(output);
- *ret_size = p;
-
+ *ret = TAKE_STRUCT(output);
return 0;
}
usec_t validate_timestamp,
const char *tpm2_device,
const char *tpm2_signature_path,
- const void *input,
- size_t input_size,
- void **ret,
- size_t *ret_size) {
+ const struct iovec *input,
+ CredentialFlags flags,
+ struct iovec *ret) {
- _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL;
+ _cleanup_(iovec_done_erase) struct iovec host_key = {}, plaintext = {}, tpm2_key = {};
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
- size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs;
struct encrypted_credential_header *h;
struct metadata_credential_header *m;
uint8_t md[SHA256_DIGEST_LENGTH];
- bool with_tpm2, with_host_key, is_tpm2_absent, with_tpm2_pk;
+ bool with_tpm2, with_tpm2_pk, with_host_key, with_null;
const EVP_CIPHER *cc;
+ size_t p, hs;
int r, added;
- assert(input || input_size == 0);
+ assert(iovec_is_valid(input));
assert(ret);
- assert(ret_size);
- h = (struct encrypted_credential_header*) input;
+ h = (struct encrypted_credential_header*) input->iov_base;
/* The ID must fit in, for the current and all future formats */
- if (input_size < sizeof(h->id))
+ if (input->iov_len < sizeof(h->id))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC) || with_tpm2_pk;
- is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT);
+ with_null = sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL);
- if (!with_host_key && !with_tpm2 && !is_tpm2_absent)
+ if (!with_host_key && !with_tpm2 && !with_null)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
if (with_tpm2_pk) {
return log_error_errno(r, "Failed to load pcr signature: %m");
}
- if (is_tpm2_absent) {
+ if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) {
/* So this is a credential encrypted with a zero length key. We support this to cover for the
* case where neither a host key not a TPM2 are available (specifically: initrd environments
* where the host key is not yet accessible and no TPM2 chip exists at all), to minimize
}
/* Now we know the minimum header size */
- if (input_size < offsetof(struct encrypted_credential_header, iv))
+ if (input->iov_len < offsetof(struct encrypted_credential_header, iv))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
/* Verify some basic header values */
/* Ensure we have space for the full header now (we don't know the size of the name hence this is a
* lower limit only) */
- if (input_size <
+ if (input->iov_len <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
if (with_tpm2) {
#if HAVE_TPM2
- struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p);
+ struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input->iov_base + p);
struct tpm2_public_key_credential_header *z = NULL;
if (!TPM2_PCR_MASK_VALID(t->pcr_mask))
/* Ensure we have space for the full TPM2 header now (still don't know the name, and its size
* though, hence still just a lower limit test only) */
- if (input_size <
- ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
+ if (input->iov_len <
+ p +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(t->policy_hash_size));
if (with_tpm2_pk) {
- z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input + p);
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input->iov_base + p);
if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
if (le32toh(z->size) > PUBLIC_KEY_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size.");
- if (input_size <
- ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
- ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
+ if (input->iov_len <
+ p +
ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
r = tpm2_unseal(tpm2_context,
le64toh(t->pcr_mask),
le16toh(t->pcr_bank),
- z ? z->data : NULL,
- z ? le32toh(z->size) : 0,
+ z ? &IOVEC_MAKE(z->data, le32toh(z->size)) : NULL,
z ? le64toh(z->pcr_mask) : 0,
signature_json,
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
le16toh(t->primary_alg),
- t->policy_hash_and_blob,
- le32toh(t->blob_size),
- t->policy_hash_and_blob + le32toh(t->blob_size),
- le32toh(t->policy_hash_size),
- /* srk_buf= */ NULL,
- /* srk_buf_size= */ 0,
- &tpm2_key,
- &tpm2_key_size);
+ &IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)),
+ &IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)),
+ /* srk= */ NULL,
+ &tpm2_key);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
#else
}
if (with_host_key) {
- r = get_credential_host_secret(
- 0,
- &host_key,
- &host_key_size);
+ r = get_credential_host_secret(/* flags= */ 0, &host_key);
if (r < 0)
return log_error_errno(r, "Failed to determine local credential key: %m");
}
- if (is_tpm2_absent)
+ if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Warning: using a null key for decryption and authentication. Confidentiality or authenticity are not provided.");
- sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
+ sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
assert_se(cc = EVP_aes_256_gcm());
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_DecryptUpdate(context, NULL, &added, input, p) != 1)
+ if (EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
ERR_error_string(ERR_get_error(), NULL));
- plaintext = malloc(input_size - p - le32toh(h->tag_size));
- if (!plaintext)
+ plaintext.iov_base = malloc(input->iov_len - p - le32toh(h->tag_size));
+ if (!plaintext.iov_base)
return -ENOMEM;
if (EVP_DecryptUpdate(
context,
- plaintext,
+ plaintext.iov_base,
&added,
- (uint8_t*) input + p,
- input_size - p - le32toh(h->tag_size)) != 1)
+ (uint8_t*) input->iov_base + p,
+ input->iov_len - p - le32toh(h->tag_size)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= input_size - p - le32toh(h->tag_size));
- plaintext_size = added;
+ assert((size_t) added <= input->iov_len - p - le32toh(h->tag_size));
+ plaintext.iov_len = added;
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input + input_size - le32toh(h->tag_size)) != 1)
+ if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext + plaintext_size, &added) != 1)
+ if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s",
ERR_error_string(ERR_get_error(), NULL));
- plaintext_size += added;
+ plaintext.iov_len += added;
- if (plaintext_size < ALIGN8(offsetof(struct metadata_credential_header, name)))
+ if (plaintext.iov_len < ALIGN8(offsetof(struct metadata_credential_header, name)))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
- m = plaintext;
+ m = plaintext.iov_base;
if (le64toh(m->timestamp) != USEC_INFINITY &&
le64toh(m->not_after) != USEC_INFINITY &&
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name too long, refusing.");
hs = ALIGN8(offsetof(struct metadata_credential_header, name) + le32toh(m->name_size));
- if (plaintext_size < hs)
+ if (plaintext.iov_len < hs)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
if (le32toh(m->name_size) > 0) {
}
if (ret) {
- char *without_metadata;
+ _cleanup_(iovec_done_erase) struct iovec without_metadata = {};
- without_metadata = memdup((uint8_t*) plaintext + hs, plaintext_size - hs);
- if (!without_metadata)
+ without_metadata.iov_len = plaintext.iov_len - hs;
+ without_metadata.iov_base = memdup_suffix0((uint8_t*) plaintext.iov_base + hs, without_metadata.iov_len);
+ if (!without_metadata.iov_base)
return log_oom();
- *ret = without_metadata;
+ *ret = TAKE_STRUCT(without_metadata);
}
- if (ret_size)
- *ret_size = plaintext_size - hs;
-
return 0;
}
#else
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
-int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) {
+int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
-int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size) {
+int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS = 1 << 2,
} CredentialSecretFlags;
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size);
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret);
int get_credential_user_password(const char *username, char **ret_password, bool *ret_is_hashed);
+typedef enum CredentialFlags {
+ CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */
+} CredentialFlags;
+
/* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of
* both, as well as one with a fixed zero length key if TPM2 is missing (the latter of course provides no
* authenticity or confidentiality, but is still useful for integrity protection, and makes things simpler
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53)
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK \
SD_ID128_MAKE(af,49,50,a8,49,13,4e,b1,a7,38,46,30,4f,f3,0c,05)
-#define CRED_AES256_GCM_BY_TPM2_ABSENT SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
+#define CRED_AES256_GCM_BY_NULL SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
/* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or
* an initrd-specific automatic mode (i.e. tpm2 if firmware can do it, otherwise fixed zero-length key, and
#define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01)
#define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71)
-int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
-int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size);
+int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
+int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"FIDO2 token data lacks 'fido2-credential' field.");
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size);
+ r = unbase64mem(json_variant_string(w), &cid, &cid_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'fido2-credential' field.");
assert(!salt);
assert(salt_size == 0);
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size);
+ r = unbase64mem(json_variant_string(w), &salt, &salt_size);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 encoded salt.");
#include "alloc-util.h"
#include "dev-setup.h"
#include "fd-util.h"
+#include "fs-util.h"
#include "label-util.h"
#include "lock-util.h"
#include "log.h"
uid_t uid,
gid_t gid) {
- static const struct {
- const char *name;
- mode_t mode;
- } table[] = {
- { "inaccessible", S_IFDIR | 0755 },
- { "inaccessible/reg", S_IFREG | 0000 },
- { "inaccessible/dir", S_IFDIR | 0000 },
- { "inaccessible/fifo", S_IFIFO | 0000 },
- { "inaccessible/sock", S_IFSOCK | 0000 },
+ static const mode_t table[] = {
+ S_IFREG,
+ S_IFDIR,
+ S_IFIFO,
+ S_IFSOCK,
/* The following two are likely to fail if we lack the privs for it (for example in an userns
* environment, if CAP_SYS_MKNOD is missing, or if a device node policy prohibits creation of
* should implement falling back to use a different node then, for example
* <root>/inaccessible/sock, which is close enough in behaviour and semantics for most uses.
*/
- { "inaccessible/chr", S_IFCHR | 0000 },
- { "inaccessible/blk", S_IFBLK | 0000 },
+ S_IFCHR,
+ S_IFBLK,
+
+ /* NB: S_IFLNK is not listed here, as there is no such thing as an inaccessible symlink */
};
+ _cleanup_close_ int parent_fd = -EBADF, inaccessible_fd = -EBADF;
int r;
if (!parent_dir)
BLOCK_WITH_UMASK(0000);
+ parent_fd = open(parent_dir, O_DIRECTORY|O_CLOEXEC|O_PATH, 0);
+ if (parent_fd < 0)
+ return -errno;
+
+ inaccessible_fd = open_mkdir_at(parent_fd, "inaccessible", O_CLOEXEC, 0755);
+ if (inaccessible_fd < 0)
+ return inaccessible_fd;
+
/* Set up inaccessible (and empty) file nodes of all types. This are used to as mount sources for over-mounting
* ("masking") file nodes that shall become inaccessible and empty for specific containers or services. We try
* to lock down these nodes as much as we can, but otherwise try to match them as closely as possible with the
* underlying file, i.e. in the best case we offer the same node type as the underlying node. */
- for (size_t i = 0; i < ELEMENTSOF(table); i++) {
+ FOREACH_ARRAY(m, table, ELEMENTSOF(table)) {
_cleanup_free_ char *path = NULL;
+ mode_t inode_type = *m;
+ const char *fn;
- path = path_join(parent_dir, table[i].name);
+ fn = inode_type_to_string(inode_type);
+ path = path_join(parent_dir, fn);
if (!path)
return log_oom();
- if (S_ISDIR(table[i].mode))
- r = mkdir_label(path, table[i].mode & 07777);
+ if (S_ISDIR(inode_type))
+ r = mkdirat_label(inaccessible_fd, fn, 0000);
else
- r = mknod_label(path, table[i].mode, makedev(0, 0));
- if (r < 0) {
+ r = RET_NERRNO(mknodat(inaccessible_fd, fn, inode_type | 0000, makedev(0, 0)));
+ if (r == -EEXIST) {
+ if (fchmodat(inaccessible_fd, fn, 0000, AT_SYMLINK_NOFOLLOW) < 0)
+ log_debug_errno(errno, "Failed to adjust access mode of existing inode '%s', ignoring: %m", path);
+ } else if (r < 0) {
log_debug_errno(r, "Failed to create '%s', ignoring: %m", path);
continue;
}
- if (uid != UID_INVALID || gid != GID_INVALID) {
- if (lchown(path, uid, gid) < 0)
- log_debug_errno(errno, "Failed to chown '%s': %m", path);
- }
+ if (uid_is_valid(uid) || gid_is_valid(gid))
+ if (fchownat(inaccessible_fd, fn, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)
+ log_debug_errno(errno, "Failed to chown '%s', ignoring: %m", path);
}
+ if (fchmod(inaccessible_fd, 0555) < 0)
+ log_debug_errno(errno, "Failed to mark inaccessible directory read-only, ignoring: %m");
+
return 0;
}
return 0;
}
+static void make_lock_dir(void) {
+ (void) mkdir_p("/run/systemd/nspawn", 0755);
+ (void) mkdir("/run/systemd/nspawn/locks", 0700);
+}
+
int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) {
_cleanup_free_ char *p = NULL;
LockFile t = LOCK_FILE_INIT;
}
if (p) {
- (void) mkdir_p("/run/systemd/nspawn/locks", 0700);
+ make_lock_dir();
r = make_lock_file(p, operation, global);
if (r < 0) {
return 0;
}
- (void) mkdir_p("/run/systemd/nspawn/locks", 0700);
+ make_lock_dir();
p = strjoina("/run/systemd/nspawn/locks/name-", name);
return make_lock_file(p, operation, ret);
/* Accept trailing slashes */
if (p[strspn(p, "/")] == 0)
return true;
-
}
return false;
}
if (text) {
- r = unhexmem(text, strlen(text), &root_hash, &root_hash_size);
+ r = unhexmem(text, &root_hash, &root_hash_size);
if (r < 0)
return r;
if (root_hash_size < sizeof(sd_id128_t))
if (!json_variant_is_string(rh))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'rootHash' field of signature JSON object is not a string.");
- r = unhexmem(json_variant_string(rh), SIZE_MAX, &root_hash, &root_hash_size);
+ r = unhexmem(json_variant_string(rh), &root_hash, &root_hash_size);
if (r < 0)
return log_debug_errno(r, "Failed to parse root hash field: %m");
if (!json_variant_is_string(sig))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'signature' field of signature JSON object is not a string.");
- r = unbase64mem(json_variant_string(sig), SIZE_MAX, &root_hash_sig, &root_hash_sig_size);
+ r = unbase64mem(json_variant_string(sig), &root_hash_sig, &root_hash_sig_size);
if (r < 0)
return log_debug_errno(r, "Failed to parse signature field: %m");
FOREACH_DIRENT(de, dir, return -errno) {
int id;
- if (strncmp(de->d_name, "Boot", 4) != 0)
+ if (!startswith(de->d_name, "Boot"))
continue;
if (strlen(de->d_name) != 45)
continue;
- if (strcmp(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR("")) != 0) /* generate variable suffix using macro */
+ if (!streq(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR(""))) /* generate variable suffix using macro */
continue;
id = boot_id_hex(de->d_name + 4);
if (rfd < 0)
return -errno;
- r = find_esp_and_warn_at(rfd, path, unprivileged_mode,
- ret_path ? &p : NULL,
- ret_part ? &part : NULL,
- ret_pstart ? &pstart : NULL,
- ret_psize ? &psize : NULL,
- ret_uuid ? &uuid : NULL,
- ret_devid ? &devid : NULL);
+ r = find_esp_and_warn_at(
+ rfd,
+ path,
+ unprivileged_mode,
+ ret_path ? &p : NULL,
+ ret_part ? &part : NULL,
+ ret_pstart ? &pstart : NULL,
+ ret_psize ? &psize : NULL,
+ ret_uuid ? &uuid : NULL,
+ ret_devid ? &devid : NULL);
if (r < 0)
return r;
}
int find_xbootldr_and_warn(
- const char *root,
- const char *path,
- int unprivileged_mode,
- char **ret_path,
- sd_id128_t *ret_uuid,
- dev_t *ret_devid) {
+ const char *root,
+ const char *path,
+ int unprivileged_mode,
+ char **ret_path,
+ sd_id128_t *ret_uuid,
+ dev_t *ret_devid) {
_cleanup_close_ int rfd = -EBADF;
_cleanup_free_ char *p = NULL;
if (rfd < 0)
return -errno;
- r = find_xbootldr_and_warn_at(rfd, path, unprivileged_mode,
- ret_path ? &p : NULL,
- ret_uuid ? &uuid : NULL,
- ret_devid ? &devid : NULL);
+ r = find_xbootldr_and_warn_at(
+ rfd,
+ path,
+ unprivileged_mode,
+ ret_path ? &p : NULL,
+ ret_uuid ? &uuid : NULL,
+ ret_devid ? &devid : NULL);
if (r < 0)
return r;
}
NULSTR_FOREACH(name, names) {
- if (end < word + strlen(name))
- continue;
- if (!strneq(word, name, strlen(name)))
+ x = startswith(word, name);
+ if (!x || x > end)
continue;
/* We know that the string is NUL terminated, so *x is valid */
- x = word + strlen(name);
if (IN_SET(*x, '\0', '=', ',')) {
namefound = name;
break;
}
}
- if (*end)
- word = end + 1;
- else
+ if (*end == '\0')
break;
+
+ word = end + 1;
}
answer:
const char *source,
const char *fn,
FILE **ret_file,
+ char **ret_final_path,
char **ret_temp_path) {
_cleanup_free_ char *p = NULL;
program_invocation_short_name);
*ret_file = f;
+
+ if (ret_final_path)
+ *ret_final_path = TAKE_PTR(p);
+
return 0;
}
-
int generator_add_symlink_full(
const char *dir,
const char *dst,
assert(dir);
assert(dst);
- assert(dep_type);
assert(src);
- /* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). If
- * <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
+ /* If 'dep_type' is specified adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise).
+ *
+ * If 'dep_type' is NULL, it will create a symlink to <src> (i.e. create an alias.
+ *
+ * If <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
r = path_extract_directory(src, &dn);
if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */
return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance);
}
- from = path_join(dn ?: "..", fn);
- if (!from)
- return log_oom();
+ if (dep_type) { /* Create a .wants/ style dep */
+ from = path_join(dn ?: "..", fn);
+ if (!from)
+ return log_oom();
+
+ to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
+ } else { /* or create an alias */
+ from = dn ? path_join(dn, fn) : strdup(fn);
+ if (!from)
+ return log_oom();
- to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
+ to = strjoin(dir, "/", dst);
+ }
if (!to)
return log_oom();
#include "macro.h"
#include "main-func.h"
-int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_temp_path);
+int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_final_path, char **ret_temp_path);
static inline int generator_open_unit_file(const char *dest, const char *source, const char *name, FILE **ret_file) {
- return generator_open_unit_file_full(dest, source, name, ret_file, NULL);
+ return generator_open_unit_file_full(dest, source, name, ret_file, NULL, NULL);
}
int generator_add_symlink_full(const char *dir, const char *dst, const char *dep_type, const char *src, const char *instance);
#include "group-record.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
GroupRecord* group_record_new(void) {
if (r < 0)
return r;
- r = json_dispatch(h->json, group_dispatch_table, json_flags, h);
+ r = json_dispatch(h->json, group_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h);
if (r < 0)
return r;
if (devno == 0 && offset > 0 && offset != UINT64_MAX)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
- "Found resume_offset=%" PRIu64 " but resume= is unset, refusing.", offset);
+ "Found populated /sys/power/resume_offset (%" PRIu64 ") but /sys/power/resume is not set, refusing.",
+ offset);
*ret_devno = devno;
*ret_offset = offset;
return unit_file_lookup_state(scope, &lp, name, ret);
}
-int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name) {
- _cleanup_(install_context_done) InstallContext c = { .scope = scope };
+int unit_file_exists_full(RuntimeScope scope, const LookupPaths *lp, const char *name, char **ret_path) {
+ _cleanup_(install_context_done) InstallContext c = {
+ .scope = scope,
+ };
int r;
assert(lp);
if (!unit_name_is_valid(name, UNIT_NAME_ANY))
return -EINVAL;
- r = install_info_discover(&c, lp, name, 0, NULL, NULL, NULL);
- if (r == -ENOENT)
+ InstallInfo *info = NULL;
+ r = install_info_discover(
+ &c,
+ lp,
+ name,
+ /* flags= */ 0,
+ ret_path ? &info : NULL,
+ /* changes= */ NULL,
+ /* n_changes= */ NULL);
+ if (r == -ENOENT) {
+ if (ret_path)
+ *ret_path = NULL;
return 0;
+ }
if (r < 0)
return r;
+ if (ret_path) {
+ assert(info);
+
+ _cleanup_free_ char *p = strdup(info->path);
+ if (!p)
+ return -ENOMEM;
+
+ *ret_path = TAKE_PTR(p);
+ }
+
return 1;
}
UnitFileState *ret);
int unit_file_get_state(RuntimeScope scope, const char *root_dir, const char *filename, UnitFileState *ret);
-int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name);
+
+int unit_file_exists_full(RuntimeScope scope, const LookupPaths *paths, const char *name, char **ret_path);
+static inline int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name) {
+ return unit_file_exists_full(scope, paths, name, NULL);
+}
int unit_file_get_list(RuntimeScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns);
#include "fd-util.h"
#include "fileio.h"
#include "float.h"
+#include "glyph-util.h"
#include "hexdecoct.h"
+#include "iovec-util.h"
#include "json-internal.h"
#include "json.h"
#include "macro.h"
/* Erase from memory when freeing */
bool sensitive:1;
+ /* True if we know that any referenced json object is marked sensitive */
+ bool recursive_sensitive:1;
+
/* If this is an object the fields are strictly ordered by name */
bool sorted:1;
return v->sensitive;
}
+bool json_variant_is_sensitive_recursive(JsonVariant *v) {
+ if (!v)
+ return false;
+ if (json_variant_is_sensitive(v))
+ return true;
+ if (!json_variant_is_regular(v))
+ return false;
+ if (v->recursive_sensitive) /* Already checked this before */
+ return true;
+ if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+ return false;
+ if (v->is_reference) {
+ if (!json_variant_is_sensitive_recursive(v->reference))
+ return false;
+
+ return (v->recursive_sensitive = true);
+ }
+
+ for (size_t i = 0; i < json_variant_elements(v); i++)
+ if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i)))
+ return (v->recursive_sensitive = true);
+
+ /* Note: we only cache the result here in case true, since we allow all elements down the tree to
+ * have their sensitive flag toggled later on (but never off) */
+ return false;
+}
+
static void json_variant_propagate_sensitive(JsonVariant *from, JsonVariant *to) {
if (json_variant_is_sensitive(from))
json_variant_sensitive(to);
assert(f);
assert(v);
+ if (FLAGS_SET(flags, JSON_FORMAT_CENSOR_SENSITIVE) && json_variant_is_sensitive(v)) {
+ if (flags & JSON_FORMAT_COLOR)
+ fputs(ansi_red(), f);
+ fputs("\"<sensitive data>\"", f);
+ if (flags & JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+ return 0;
+ }
+
switch (json_variant_type(v)) {
case JSON_VARIANT_REAL: {
return 0;
}
-static bool json_variant_is_sensitive_recursive(JsonVariant *v) {
- if (!v)
- return false;
- if (json_variant_is_sensitive(v))
- return true;
- if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
- v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
- return false;
- if (!json_variant_is_regular(v))
- return false;
- if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
- return false;
- if (v->is_reference)
- return json_variant_is_sensitive_recursive(v->reference);
-
- for (size_t i = 0; i < json_variant_elements(v); i++)
- if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i)))
- return true;
-
- return false;
-}
-
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) {
_cleanup_(memstream_done) MemStream m = {};
size_t sz;
if (flags & JSON_FORMAT_OFF)
return -ENOEXEC;
- if ((flags & JSON_FORMAT_REFUSE_SENSITIVE))
- if (json_variant_is_sensitive_recursive(v))
- return -EPERM;
-
f = memstream_init(&m);
if (!f)
return -ENOMEM;
break;
}
- case _JSON_BUILD_IOVEC_BASE64: {
+ case _JSON_BUILD_IOVEC_BASE64:
+ case _JSON_BUILD_IOVEC_HEX: {
const struct iovec *iov;
if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
goto finish;
}
- iov = ASSERT_PTR(va_arg(ap, const struct iovec*));
+ iov = va_arg(ap, const struct iovec*);
if (current->n_suppress == 0) {
- r = json_variant_new_base64(&add, iov->iov_base, iov->iov_len);
+ if (iov)
+ r = command == _JSON_BUILD_IOVEC_BASE64 ? json_variant_new_base64(&add, iov->iov_base, iov->iov_len) :
+ json_variant_new_hex(&add, iov->iov_base, iov->iov_len);
+ else
+ r = json_variant_new_string(&add, "");
if (r < 0)
goto finish;
}
done++;
} else {
- json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
+ if (flags & JSON_ALLOW_EXTENSIONS) {
+ json_log(value, flags|JSON_DEBUG, 0, "Unrecognized object field '%s', assuming extension.", json_variant_string(key));
+ continue;
+ }
+ json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
if (flags & JSON_PERMISSIVE)
continue;
return 0;
}
+int json_dispatch_byte_array_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ _cleanup_free_ uint8_t *buffer = NULL;
+ struct iovec *iov = ASSERT_PTR(userdata);
+ size_t sz, k = 0;
+
+ assert(variant);
+
+ if (!json_variant_is_array(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
+
+ sz = json_variant_elements(variant);
+
+ buffer = new(uint8_t, sz + 1);
+ if (!buffer)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ENOMEM), "Out of memory.");
+
+ JsonVariant *i;
+ JSON_VARIANT_ARRAY_FOREACH(i, variant) {
+ uint64_t b;
+
+ if (!json_variant_is_unsigned(i))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name));
+
+ b = json_variant_unsigned(i);
+ if (b > 0xff)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
+ "Element %zu of JSON field '%s' is out of range 0%s255.",
+ k, strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS));
+
+ buffer[k++] = (uint8_t) b;
+ }
+ assert(k == sz);
+
+ /* Append a NUL byte for safety, like we do in memdup_suffix0() and others. */
+ buffer[sz] = 0;
+
+ free_and_replace(iov->iov_base, buffer);
+ iov->iov_len = sz;
+ return 0;
+}
+
+int json_dispatch_in_addr(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ struct in_addr *address = ASSERT_PTR(userdata);
+ _cleanup_(iovec_done) struct iovec iov = {};
+ int r;
+
+ r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
+ if (r < 0)
+ return r;
+
+ if (iov.iov_len != sizeof(struct in_addr))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name));
+
+ memcpy(address, iov.iov_base, iov.iov_len);
+ return 0;
+}
+
static int json_cmp_strings(const void *x, const void *y) {
JsonVariant *const *a = x, *const *b = y;
if (!json_variant_is_string(v))
return -EINVAL;
- return unbase64mem(json_variant_string(v), SIZE_MAX, ret, ret_size);
+ return unbase64mem(json_variant_string(v), ret, ret_size);
}
int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size) {
if (!json_variant_is_string(v))
return -EINVAL;
- return unhexmem(json_variant_string(v), SIZE_MAX, ret, ret_size);
+ return unhexmem(json_variant_string(v), ret, ret_size);
}
static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = {
void json_variant_sensitive(JsonVariant *v);
bool json_variant_is_sensitive(JsonVariant *v);
+bool json_variant_is_sensitive_recursive(JsonVariant *v);
struct json_variant_foreach_state {
JsonVariant *variant;
JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
- JSON_FORMAT_REFUSE_SENSITIVE = 1 << 11, /* return EPERM if any node in the tree is marked as senstitive */
+ JSON_FORMAT_CENSOR_SENSITIVE = 1 << 11, /* Replace all sensitive elements with the string "<sensitive data>" */
} JsonFormatFlags;
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);
_JSON_BUILD_IOVEC_BASE64,
_JSON_BUILD_BASE32HEX,
_JSON_BUILD_HEX,
+ _JSON_BUILD_IOVEC_HEX,
_JSON_BUILD_OCTESCAPE,
_JSON_BUILD_ID128,
_JSON_BUILD_UUID,
#define JSON_BUILD_IOVEC_BASE64(iov) _JSON_BUILD_IOVEC_BASE64, (const struct iovec*) { iov }
#define JSON_BUILD_BASE32HEX(p, n) _JSON_BUILD_BASE32HEX, (const void*) { p }, (size_t) { n }
#define JSON_BUILD_HEX(p, n) _JSON_BUILD_HEX, (const void*) { p }, (size_t) { n }
+#define JSON_BUILD_IOVEC_HEX(iov) _JSON_BUILD_IOVEC_HEX, (const struct iovec*) { iov }
#define JSON_BUILD_OCTESCAPE(p, n) _JSON_BUILD_OCTESCAPE, (const void*) { p }, (size_t) { n }
#define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, (const sd_id128_t*) { &(id) }
#define JSON_BUILD_UUID(id) _JSON_BUILD_UUID, (const sd_id128_t*) { &(id) }
#define JSON_BUILD_PAIR_BASE64(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_BASE64(p, n))
#define JSON_BUILD_PAIR_IOVEC_BASE64(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_BASE64(iov))
#define JSON_BUILD_PAIR_HEX(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_HEX(p, n))
+#define JSON_BUILD_PAIR_IOVEC_HEX(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_HEX(iov))
#define JSON_BUILD_PAIR_ID128(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_ID128(id))
#define JSON_BUILD_PAIR_UUID(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_UUID(id))
#define JSON_BUILD_PAIR_BYTE_ARRAY(name, v, n) JSON_BUILD_PAIR(name, JSON_BUILD_BYTE_ARRAY(v, n))
* entry, as well the bitmask specified for json_log() calls */
typedef enum JsonDispatchFlags {
/* The following three may be set in JsonDispatch's .flags field or the json_dispatch() flags parameter */
- JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */
- JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */
- JSON_LOG = 1 << 2, /* Should the parser log about errors? */
- JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */
- JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */
+ JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this field or object? */
+ JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */
+ JSON_LOG = 1 << 2, /* Should the parser log about errors? */
+ JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */
+ JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */
+ JSON_ALLOW_EXTENSIONS = 1 << 5, /* Subset of JSON_PERMISSIVE: allow additional fields, but no other permissive handling */
/* The following two may be passed into log_json() in addition to those above */
- JSON_DEBUG = 1 << 5, /* Indicates that this log message is a debug message */
- JSON_WARNING = 1 << 6, /* Indicates that this log message is a warning message */
+ JSON_DEBUG = 1 << 6, /* Indicates that this log message is a debug message */
+ JSON_WARNING = 1 << 7, /* Indicates that this log message is a warning message */
} JsonDispatchFlags;
typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_unsupported(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_unbase64_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_byte_array_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_in_addr(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
assert_cc(sizeof(uint32_t) == sizeof(unsigned));
#define json_dispatch_uint json_dispatch_uint32
assert_cc(sizeof(int32_t) == sizeof(int));
#define json_dispatch_int json_dispatch_int32
+#define JSON_DISPATCH_ENUM_DEFINE(name, type, func) \
+ int name(const char *n, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { \
+ type *c = ASSERT_PTR(userdata); \
+ \
+ assert(variant); \
+ \
+ if (json_variant_is_null(variant)) { \
+ *c = (type) -EINVAL; \
+ return 0; \
+ } \
+ \
+ if (!json_variant_is_string(variant)) \
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(n)); \
+ \
+ type cc = func(json_variant_string(variant)); \
+ if (cc < 0) \
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized.", strna(n)); \
+ \
+ *c = cc; \
+ return 0; \
+ }
+
static inline int json_dispatch_level(JsonDispatchFlags flags) {
/* Did the user request no logging? If so, then never log higher than LOG_DEBUG. Also, if this is marked as
int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size);
int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size);
+static inline int json_variant_unbase64_iovec(JsonVariant *v, struct iovec *ret) {
+ return json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
+}
+
+static inline int json_variant_unhex_iovec(JsonVariant *v, struct iovec *ret) {
+ return json_variant_unhex(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
+}
+
const char *json_variant_type_to_string(JsonVariantType t);
JsonVariantType json_variant_type_from_string(const char *s);
return 0;
}
+ if (identifier && set_contains(j->exclude_syslog_identifiers, identifier))
+ return 0;
+
if (!(flags & OUTPUT_SHOW_ALL))
strip_tab_ansi(&message, &message_len, highlight_shifted);
'varlink-idl.c',
'varlink-io.systemd.c',
'varlink-io.systemd.Credentials.c',
+ 'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.ManagedOOM.c',
'varlink-io.systemd.Network.c',
"-T", "default",
node);
- if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
+ if (root && strv_extend_many(&argv, "-d", root) < 0)
return log_oom();
if (quiet && strv_extend(&argv, "-q") < 0)
if (!discard && strv_extend(&argv, "--nodiscard") < 0)
return log_oom();
- if (root && strv_extend_strv(&argv, STRV_MAKE("-r", root), false) < 0)
+ if (root && strv_extend_many(&argv, "-r", root) < 0)
return log_oom();
if (quiet && strv_extend(&argv, "-q") < 0)
if (!protofile_with_opt)
return -ENOMEM;
- if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile_with_opt), false) < 0)
+ if (strv_extend_many(&argv, "-p", protofile_with_opt) < 0)
return log_oom();
}
static int denylist_modules(const char *p, char ***denylist) {
_cleanup_strv_free_ char **k = NULL;
+ int r;
assert(p);
assert(denylist);
if (!k)
return -ENOMEM;
- if (strv_extend_strv(denylist, k, true) < 0)
- return -ENOMEM;
+ r = strv_extend_strv(denylist, k, /* filter_duplicates= */ true);
+ if (r < 0)
+ return r;
return 0;
}
if (!(ent->mask & MNT_INVERT))
mount_flags |= ent->id;
- else if (mount_flags & ent->id)
- mount_flags ^= ent->id;
+ else
+ mount_flags &= ~ent->id;
break;
}
return r;
}
+
+int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) {
+ va_list args;
+ int r;
+
+ assert(handle);
+ assert(fmt);
+
+ /* This is just like pam_prompt(), but does not noisily (i.e. beyond LOG_DEBUG) log on its own, but leaves that to the caller */
+
+ _cleanup_free_ char *msg = NULL;
+ va_start(args, fmt);
+ r = vasprintf(&msg, fmt, args);
+ va_end(args);
+ if (r < 0)
+ return PAM_BUF_ERR;
+
+ const struct pam_conv *conv = NULL;
+ r = pam_get_item(handle, PAM_CONV, (const void**) &conv);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM))
+ return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Failed to get conversation function structure: @PAMERR@");
+ if (!conv || !conv->conv) {
+ pam_syslog(handle, LOG_DEBUG, "No conversation function.");
+ return PAM_SYSTEM_ERR;
+ }
+
+ struct pam_message message = {
+ .msg_style = style,
+ .msg = msg,
+ };
+ const struct pam_message *pmessage = &message;
+ _cleanup_free_ struct pam_response *response = NULL;
+ r = conv->conv(1, &pmessage, &response, conv->appdata_ptr);
+ _cleanup_(erase_and_freep) char *rr = response ? response->resp : NULL; /* make sure string is freed + erased */
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Conversation function failed: @PAMERR@");
+
+ if (ret_response)
+ *ret_response = TAKE_PTR(rr);
+
+ return PAM_SUCCESS;
+}
int pam_get_item_many_internal(pam_handle_t *handle, ...);
#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1)
+
+int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) _printf_(4,5);
uint16_t *nr_ports,
uint16_t *port_min) {
+ int r;
+
assert(token);
assert(nr_ports);
assert(port_min);
*nr_ports = *port_min = 0;
else {
uint16_t mn = 0, mx = 0;
- int r = parse_ip_port_range(token, &mn, &mx);
+ r = parse_ip_port_range(token, &mn, &mx, /* allow_zero = */ true);
if (r < 0)
return r;
*ip_protocol = proto;
*nr_ports = nr;
*port_min = mn;
+
return 0;
}
int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string);
void (*sym_p11_kit_uri_free)(P11KitUri *uri);
CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs);
+CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attribute)(P11KitUri *uri, CK_ATTRIBUTE_TYPE attr_type);
+int (*sym_p11_kit_uri_set_attribute)(P11KitUri *uri, CK_ATTRIBUTE_PTR attr);
CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri);
CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri);
CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri);
DLSYM_ARG(p11_kit_uri_format),
DLSYM_ARG(p11_kit_uri_free),
DLSYM_ARG(p11_kit_uri_get_attributes),
+ DLSYM_ARG(p11_kit_uri_get_attribute),
+ DLSYM_ARG(p11_kit_uri_set_attribute),
DLSYM_ARG(p11_kit_uri_get_module_info),
DLSYM_ARG(p11_kit_uri_get_slot_info),
DLSYM_ARG(p11_kit_uri_get_token_info),
if (mechanism_info.flags & CKF_EC_COMPRESS) {
#if HAVE_OPENSSL
log_debug("CKM_ECDH1_DERIVE accepts compressed EC points only, trying to convert.");
- size_t compressed_point_size;
+ size_t compressed_point_size = 0; /* Explicit initialization to appease gcc */
r = ecc_convert_to_compressed(m, session, object, encrypted_data, encrypted_data_size, &compressed_point, &compressed_point_size);
if (r < 0)
return r;
extern int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string);
extern void (*sym_p11_kit_uri_free)(P11KitUri *uri);
extern CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs);
+extern CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attribute)(P11KitUri *uri, CK_ATTRIBUTE_TYPE attr_type);
+extern int (*sym_p11_kit_uri_set_attribute)(P11KitUri *uri, CK_ATTRIBUTE_PTR attr);
extern CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri);
extern CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri);
extern CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri);
LAZY_INITIALIZED,
} Initialized;
-static int mac_selinux_reload(int seqno);
-
static int cached_use = -1;
static Initialized initialized = UNINITIALIZED;
static int last_policyload = 0;
return 0;
}
+#if HAVE_SELINUX
+static int mac_selinux_reload(int seqno) {
+ log_debug("SELinux reload %d", seqno);
+
+ (void) open_label_db();
+
+ return 0;
+}
+#endif
+
void mac_selinux_maybe_reload(void) {
#if HAVE_SELINUX
int policyload;
#endif
}
-#if HAVE_SELINUX
-static int mac_selinux_reload(int seqno) {
- log_debug("SELinux reload %d", seqno);
-
- (void) open_label_db();
-
- return 0;
-}
-#endif
-
#if HAVE_SELINUX
static int selinux_fix_fd(
int fd,
#include <arpa/inet.h>
#include <errno.h>
+#include <linux/net_namespace.h>
#include <net/if.h>
#include <string.h>
#include "alloc-util.h"
#include "errno-util.h"
#include "extract-word.h"
+#include "fd-util.h"
#include "log.h"
#include "memory-util.h"
+#include "namespace-util.h"
#include "netlink-util.h"
#include "parse-util.h"
#include "socket-netlink.h"
return a->cached_server_string;
}
+
+int netns_get_nsid(int netnsfd, uint32_t *ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ _cleanup_close_ int _netns_fd = -EBADF;
+ int r;
+
+ if (netnsfd < 0) {
+ r = namespace_open(
+ 0,
+ /* pidns_fd= */ NULL,
+ /* mntns_fd= */ NULL,
+ &_netns_fd,
+ /* userns_fd= */ NULL,
+ /* root_fd= */ NULL);
+ if (r < 0)
+ return r;
+
+ netnsfd = _netns_fd;
+ }
+
+ r = sd_netlink_open(&rtnl);
+ if (r < 0)
+ return r;
+
+ r = sd_rtnl_message_new_nsid(rtnl, &req, RTM_GETNSID);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_s32(req, NETNSA_FD, netnsfd);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(rtnl, req, 0, &reply);
+ if (r < 0)
+ return r;
+
+ for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
+ uint16_t type;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_get_type(m, &type);
+ if (r < 0)
+ return r;
+ if (type != RTM_NEWNSID)
+ continue;
+
+ uint32_t u;
+ r = sd_netlink_message_read_u32(m, NETNSA_NSID, &u);
+ if (r < 0)
+ return r;
+
+ if (u == UINT32_MAX) /* no NSID assigned yet */
+ return -ENODATA;
+
+ if (ret)
+ *ret = u;
+
+ return 0;
+ }
+
+ return -ENXIO;
+}
int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret);
int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret);
const char *in_addr_full_to_string(struct in_addr_full *a);
+
+int netns_get_nsid(int netnsfd, uint32_t *ret);
#define DEFINE_HEX_PTR(name, hex) \
_cleanup_free_ void *name = NULL; \
size_t name##_len = 0; \
- assert_se(unhexmem(hex, strlen_ptr(hex), &name, &name##_len) >= 0);
+ assert_se(unhexmem_full(hex, strlen_ptr(hex), false, &name, &name##_len) >= 0);
#define TEST_REQ_RUNNING_SYSTEMD(x) \
if (sd_booted() > 0) { \
_cleanup_free_ void *buf = NULL;
size_t buf_size = 0;
- r = unhexmem(p, SIZE_MAX, &buf, &buf_size);
+ r = unhexmem(p, &buf, &buf_size);
if (r < 0)
return log_debug_errno(r, "Invalid pcr hash value '%s': %m", p);
}
}
+/* Be careful before changing anything in this function, as the TPM key "name" is calculated using the entire
+ * TPMT_PUBLIC (after marshalling), and that "name" is used (for example) to calculate the policy hash for
+ * the Authorize policy. So we must ensure this conversion of a PEM to TPM2B_PUBLIC does not change the
+ * "name", because it would break unsealing of previously-sealed objects that used (for example)
+ * tpm2_calculate_policy_authorize(). See bug #30546. */
int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) {
int key_id, r;
uint32_t exponent = 0;
memcpy(&exponent, e, e_size);
exponent = be32toh(exponent) >> (32 - e_size * 8);
- if (exponent == TPM2_RSA_DEFAULT_EXPONENT)
- exponent = 0;
+
+ /* TPM specification Part 2 ("Structures") section for TPMS_RSA_PARAMS states "An exponent of
+ * zero indicates that the exponent is the default of 2^16 + 1". However, we have no reason
+ * to special case it in our PEM->TPM2B_PUBLIC conversion, and doing so could break backwards
+ * compatibility, so even if it is the "default" value of 0x10001, we do not set it to 0. */
public.parameters.rsaDetail.exponent = exponent;
break;
size_t bits = (size_t) r * 8;
_cleanup_free_ void *seed = NULL;
- size_t seed_size;
+ size_t seed_size = 0; /* Explicit initialization to appease gcc */
r = tpm2_kdfe(parent->publicArea.nameAlg,
shared_secret,
shared_secret_size,
log_debug("Calculating encrypted seed for sealed object.");
_cleanup_free_ void *seed = NULL, *encrypted_seed = NULL;
- size_t seed_size, encrypted_seed_size;
+ size_t seed_size = 0, encrypted_seed_size = 0; /* Explicit initialization to appease gcc */
if (parent->publicArea.type == TPM2_ALG_RSA)
r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size);
else if (parent->publicArea.type == TPM2_ALG_ECC)
TPM2_HANDLE parent_handle,
const TPM2B_PUBLIC *parent_public,
const TPMA_OBJECT *attributes,
- const void *secret,
- size_t secret_size,
+ const struct iovec *secret,
const TPM2B_DIGEST *policy,
const char *pin,
- void **ret_secret,
- size_t *ret_secret_size,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_serialized_parent,
- size_t *ret_serialized_parent_size) {
+ struct iovec *ret_secret,
+ struct iovec *ret_blob,
+ struct iovec *ret_serialized_parent) {
#if HAVE_OPENSSL
int r;
assert(parent_public);
- assert(secret || secret_size == 0);
+ assert(iovec_is_valid(secret));
assert(secret || ret_secret);
assert(!(secret && ret_secret)); /* Either provide a secret, or we create one, but not both */
assert(ret_blob);
- assert(ret_blob_size);
assert(ret_serialized_parent);
- assert(ret_serialized_parent_size);
log_debug("Calculating sealed object.");
parent_handle);
}
- _cleanup_(erase_and_freep) void *generated_secret = NULL;
+ _cleanup_(iovec_done_erase) struct iovec generated_secret = {};
if (!secret) {
/* No secret provided, generate a random secret. We use SHA256 digest length, though it can
* be up to TPM2_MAX_SEALED_DATA. The secret length is not limited to the nameAlg hash
* size. */
- secret_size = TPM2_SHA256_DIGEST_SIZE;
- generated_secret = malloc(secret_size);
- if (!generated_secret)
+ generated_secret.iov_len = TPM2_SHA256_DIGEST_SIZE;
+ generated_secret.iov_base = malloc(generated_secret.iov_len);
+ if (!generated_secret.iov_base)
return log_oom_debug();
- r = crypto_random_bytes(generated_secret, secret_size);
+ r = crypto_random_bytes(generated_secret.iov_base, generated_secret.iov_len);
if (r < 0)
return log_debug_errno(r, "Failed to generate secret key: %m");
- secret = generated_secret;
+ secret = &generated_secret;
}
- if (secret_size > TPM2_MAX_SEALED_DATA)
+ if (secret->iov_len > TPM2_MAX_SEALED_DATA)
return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW),
"Secret size %zu too large, limit is %d bytes.",
- secret_size, TPM2_MAX_SEALED_DATA);
+ secret->iov_len, TPM2_MAX_SEALED_DATA);
TPM2B_DIGEST random_seed;
TPM2B_ENCRYPTED_SECRET seed;
return r;
TPM2B_PUBLIC public;
- r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret, secret_size, &public);
+ r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret->iov_base, secret->iov_len, &public);
if (r < 0)
return r;
return r;
TPM2B_PRIVATE private;
- r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret, secret_size, &private);
+ r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret->iov_base, secret->iov_len, &private);
if (r < 0)
return r;
- _cleanup_free_ void *blob = NULL;
- size_t blob_size;
- r = tpm2_marshal_blob(&public, &private, &seed, &blob, &blob_size);
+ _cleanup_(iovec_done) struct iovec blob = {};
+ r = tpm2_marshal_blob(&public, &private, &seed, &blob.iov_base, &blob.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
if (r < 0)
return r;
- _cleanup_free_ void *serialized_parent = NULL;
- size_t serialized_parent_size;
+ _cleanup_(iovec_done) struct iovec serialized_parent = {};
r = tpm2_calculate_serialize(
parent_handle,
&parent_name,
parent_public,
- &serialized_parent,
- &serialized_parent_size);
+ &serialized_parent.iov_base,
+ &serialized_parent.iov_len);
if (r < 0)
return r;
if (ret_secret)
- *ret_secret = TAKE_PTR(generated_secret);
- if (ret_secret_size)
- *ret_secret_size = secret_size;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
- *ret_serialized_parent = TAKE_PTR(serialized_parent);
- *ret_serialized_parent_size = serialized_parent_size;
+ *ret_secret = TAKE_STRUCT(generated_secret);
+ *ret_blob = TAKE_STRUCT(blob);
+ *ret_serialized_parent = TAKE_STRUCT(serialized_parent);
return 0;
#else /* HAVE_OPENSSL */
uint32_t seal_key_handle,
const TPM2B_DIGEST *policy,
const char *pin,
- void **ret_secret,
- size_t *ret_secret_size,
- void **ret_blob,
- size_t *ret_blob_size,
+ struct iovec *ret_secret,
+ struct iovec *ret_blob,
uint16_t *ret_primary_alg,
- void **ret_srk_buf,
- size_t *ret_srk_buf_size) {
+ struct iovec *ret_srk) {
uint16_t primary_alg = 0;
int r;
assert(ret_secret);
- assert(ret_secret_size);
assert(ret_blob);
- assert(ret_blob_size);
/* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that
* is randomized when the TPM2 is first initialized or reset and remains stable across boots. We
usec_t start = now(CLOCK_MONOTONIC);
+ TPMA_OBJECT hmac_attributes =
+ TPMA_OBJECT_FIXEDTPM |
+ TPMA_OBJECT_FIXEDPARENT;
+
+ /* If protected by PIN, a user-selected low-entropy password, enable DA protection.
+ Without a PIN, the key's left protected only by a PCR policy, which does not benefit
+ from DA protection. */
+ hmac_attributes |= pin ? 0 : TPMA_OBJECT_NODA;
+
/* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the
* LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it
* because it's a key type that is universally supported and suitable for symmetric binary blobs. */
TPMT_PUBLIC hmac_template = {
.type = TPM2_ALG_KEYEDHASH,
.nameAlg = TPM2_ALG_SHA256,
- .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT,
+ .objectAttributes = hmac_attributes,
.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
.unique.keyedHash.size = SHA256_DIGEST_SIZE,
.authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
return log_debug_errno(r, "Failed to generate secret key: %m");
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
- if (ret_srk_buf) {
+ if (ret_srk) {
_cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL;
if (IN_SET(seal_key_handle, 0, TPM2_SRK_HANDLE)) {
if (seal_key_handle != 0)
log_debug("Using primary alg sealing, but seal key handle also provided; ignoring seal key handle.");
- /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */
+ /* TODO: force all callers to provide ret_srk, so we can stop sealing with the legacy templates. */
primary_alg = TPM2_ALG_ECC;
TPM2B_PUBLIC template = {
if (r < 0)
return r;
- _cleanup_(erase_and_freep) void *secret = NULL;
- secret = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
- if (!secret)
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
+ secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
+ if (!secret.iov_base)
return log_oom_debug();
+ secret.iov_len = hmac_sensitive.data.size;
log_debug("Marshalling private and public part of HMAC key.");
- _cleanup_free_ void *blob = NULL;
- size_t blob_size = 0;
- r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob, &blob_size);
+ _cleanup_(iovec_done) struct iovec blob = {};
+ r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
- _cleanup_free_ void *srk_buf = NULL;
- size_t srk_buf_size = 0;
- if (ret_srk_buf) {
+ if (ret_srk) {
+ _cleanup_(iovec_done) struct iovec srk = {};
_cleanup_(Esys_Freep) void *tmp = NULL;
- r = tpm2_serialize(c, primary_handle, &tmp, &srk_buf_size);
+ size_t tmp_size;
+
+ r = tpm2_serialize(c, primary_handle, &tmp, &tmp_size);
if (r < 0)
return r;
/*
* make a copy since we don't want the caller to understand that
* ESYS allocated the pointer. It would make tracking what deallocator
- * to use for srk_buf in which context a PITA.
+ * to use for srk in which context a PITA.
*/
- srk_buf = memdup(tmp, srk_buf_size);
- if (!srk_buf)
+ srk.iov_base = memdup(tmp, tmp_size);
+ if (!srk.iov_base)
return log_oom_debug();
+ srk.iov_len = tmp_size;
- *ret_srk_buf = TAKE_PTR(srk_buf);
- *ret_srk_buf_size = srk_buf_size;
+ *ret_srk = TAKE_STRUCT(srk);
}
- *ret_secret = TAKE_PTR(secret);
- *ret_secret_size = hmac_sensitive.data.size;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
+ *ret_secret = TAKE_STRUCT(secret);
+ *ret_blob = TAKE_STRUCT(blob);
if (ret_primary_alg)
*ret_primary_alg = primary_alg;
int tpm2_unseal(Tpm2Context *c,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
JsonVariant *signature,
const char *pin,
const Tpm2PCRLockPolicy *pcrlock_policy,
uint16_t primary_alg,
- const void *blob,
- size_t blob_size,
- const void *known_policy_hash,
- size_t known_policy_hash_size,
- const void *srk_buf,
- size_t srk_buf_size,
- void **ret_secret,
- size_t *ret_secret_size) {
+ const struct iovec *blob,
+ const struct iovec *known_policy_hash,
+ const struct iovec *srk,
+ struct iovec *ret_secret) {
TSS2_RC rc;
int r;
- assert(blob);
- assert(blob_size > 0);
- assert(known_policy_hash_size == 0 || known_policy_hash);
- assert(pubkey_size == 0 || pubkey);
+ assert(iovec_is_set(blob));
+ assert(iovec_is_valid(known_policy_hash));
+ assert(iovec_is_valid(pubkey));
assert(ret_secret);
- assert(ret_secret_size);
assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
TPM2B_PUBLIC public;
TPM2B_PRIVATE private;
TPM2B_ENCRYPTED_SECRET seed = {};
- r = tpm2_unmarshal_blob(blob, blob_size, &public, &private, &seed);
+ r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed);
if (r < 0)
return log_debug_errno(r, "Could not extract parts from blob: %m");
}
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
- if (srk_buf) {
- r = tpm2_deserialize(c, srk_buf, srk_buf_size, &primary_handle);
+ if (iovec_is_set(srk)) {
+ r = tpm2_deserialize(c, srk->iov_base, srk->iov_len, &primary_handle);
if (r < 0)
return r;
} else if (primary_alg != 0) {
return r;
TPM2B_PUBLIC pubkey_tpm2b;
- _cleanup_free_ void *fp = NULL;
- size_t fp_size = 0;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b);
+ _cleanup_(iovec_done) struct iovec fp = {};
+ if (iovec_is_set(pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey->iov_base, pubkey->iov_len, &pubkey_tpm2b);
if (r < 0)
return log_debug_errno(r, "Could not create TPMT_PUBLIC: %m");
- r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp, &fp_size);
+ r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp.iov_base, &fp.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not get key fingerprint: %m");
}
policy_session,
hash_pcr_mask,
pcr_bank,
- pubkey ? &pubkey_tpm2b : NULL,
- fp, fp_size,
+ iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL,
+ fp.iov_base, fp.iov_len,
pubkey_pcr_mask,
signature,
!!pin,
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
* wait until the TPM2 tells us to go away. */
- if (known_policy_hash_size > 0 &&
- memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0)
+ if (iovec_is_set(known_policy_hash) && memcmp_nn(policy_digest->buffer,
+ policy_digest->size,
+ known_policy_hash->iov_base,
+ known_policy_hash->iov_len) != 0) {
+#if HAVE_OPENSSL
+ if (iovec_is_set(pubkey) &&
+ pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA &&
+ pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) {
+ /* Due to bug #30546, if using RSA pubkey with the default exponent, we may
+ * need to set the exponent to the TPM special-case value of 0 and retry. */
+ log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0.");
+ pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0;
+ continue;
+ } else
+#endif
return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
"Current policy digest does not match stored policy digest, cancelling "
"TPM2 authentication attempt.");
+ }
log_debug("Unsealing HMAC key.");
log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i);
}
- _cleanup_(erase_and_freep) char *secret = NULL;
- secret = memdup(unsealed->buffer, unsealed->size);
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
+ secret.iov_base = memdup(unsealed->buffer, unsealed->size);
explicit_bzero_safe(unsealed->buffer, unsealed->size);
- if (!secret)
+ if (!secret.iov_base)
return log_oom_debug();
+ secret.iov_len = unsealed->size;
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
- *ret_secret = TAKE_PTR(secret);
- *ret_secret_size = unsealed->size;
+ *ret_secret = TAKE_STRUCT(secret);
return 0;
}
int keyslot,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
uint16_t primary_alg,
- const void *blob,
- size_t blob_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *blob,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
TPM2Flags flags,
JsonVariant **ret) {
_cleanup_free_ char *keyslot_as_string = NULL;
int r;
- assert(blob || blob_size == 0);
- assert(policy_hash || policy_hash_size == 0);
- assert(pubkey || pubkey_size == 0);
+ assert(iovec_is_valid(pubkey));
+ assert(iovec_is_valid(blob));
+ assert(iovec_is_valid(policy_hash));
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return -ENOMEM;
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")),
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
- JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)),
+ JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_IOVEC_BASE64(blob)),
JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(hmj)),
JSON_BUILD_PAIR_CONDITION(!!tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))),
JSON_BUILD_PAIR_CONDITION(!!tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))),
- JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)),
+ JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_IOVEC_HEX(policy_hash)),
JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)),
JSON_BUILD_PAIR("tpm2_pcrlock", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PCRLOCK)),
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
- JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)),
- JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)),
- JSON_BUILD_PAIR_CONDITION(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size))));
+ JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_IOVEC_BASE64(pubkey)),
+ JSON_BUILD_PAIR_CONDITION(iovec_is_set(salt), "tpm2_salt", JSON_BUILD_IOVEC_BASE64(salt)),
+ JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk))));
if (r < 0)
return r;
int *ret_keyslot,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_buf_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
TPM2Flags *ret_flags) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
- size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
+ _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {};
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field.");
- r = json_variant_unbase64(w, &blob, &blob_size);
+ r = json_variant_unbase64_iovec(w, &blob);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field.");
if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field.");
- r = json_variant_unhex(w, &policy_hash, &policy_hash_size);
+ r = json_variant_unhex_iovec(w, &policy_hash);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field.");
w = json_variant_by_key(v, "tpm2_salt");
if (w) {
- r = json_variant_unbase64(w, &salt, &salt_size);
+ r = json_variant_unbase64_iovec(w, &salt);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2_salt' field.");
}
w = json_variant_by_key(v, "tpm2_pubkey");
if (w) {
- r = json_variant_unbase64(w, &pubkey, &pubkey_size);
+ r = json_variant_unbase64_iovec(w, &pubkey);
if (r < 0)
return log_debug_errno(r, "Failed to decode PCR public key.");
} else if (pubkey_pcr_mask != 0)
w = json_variant_by_key(v, "tpm2_srk");
if (w) {
- r = json_variant_unbase64(w, &srk_buf, &srk_buf_size);
+ r = json_variant_unbase64_iovec(w, &srk);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field.");
}
if (ret_pcr_bank)
*ret_pcr_bank = pcr_bank;
if (ret_pubkey)
- *ret_pubkey = TAKE_PTR(pubkey);
- if (ret_pubkey_size)
- *ret_pubkey_size = pubkey_size;
+ *ret_pubkey = TAKE_STRUCT(pubkey);
if (ret_pubkey_pcr_mask)
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
if (ret_primary_alg)
*ret_primary_alg = primary_alg;
if (ret_blob)
- *ret_blob = TAKE_PTR(blob);
- if (ret_blob_size)
- *ret_blob_size = blob_size;
+ *ret_blob = TAKE_STRUCT(blob);
if (ret_policy_hash)
- *ret_policy_hash = TAKE_PTR(policy_hash);
- if (ret_policy_hash_size)
- *ret_policy_hash_size = policy_hash_size;
+ *ret_policy_hash = TAKE_STRUCT(policy_hash);
if (ret_salt)
- *ret_salt = TAKE_PTR(salt);
- if (ret_salt_size)
- *ret_salt_size = salt_size;
+ *ret_salt = TAKE_STRUCT(salt);
if (ret_flags)
*ret_flags = flags;
- if (ret_srk_buf)
- *ret_srk_buf = TAKE_PTR(srk_buf);
- if (ret_srk_buf_size)
- *ret_srk_buf_size = srk_buf_size;
+ if (ret_srk)
+ *ret_srk = TAKE_STRUCT(srk);
return 0;
}
int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy);
int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest);
-int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const void *secret, size_t secret_size, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_serialized_parent, size_t *ret_serialized_parent_size);
+int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, struct iovec *ret_serialized_parent);
int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template);
int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template);
int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
-int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size);
-int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size);
+int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, uint16_t *ret_primary_alg, struct iovec *ret_srk);
+int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret);
#if HAVE_OPENSSL
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret);
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, const void *srk_buf, size_t srk_buf_size, TPM2Flags flags, JsonVariant **ret);
-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags);
+int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, TPM2Flags flags, JsonVariant **ret);
+int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, TPM2Flags *ret_flags);
/* Default to PCR 7 only */
#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7)
#include "udev-util.h"
#include "utf8.h"
-int udev_set_max_log_level(char *str) {
- size_t n;
+int udev_parse_config_full(const ConfigTableItem config_table[]) {
+ int r;
- /* This may modify input string. */
+ assert(config_table);
- if (isempty(str))
+ r = config_parse_config_file_full(
+ "udev.conf",
+ "udev",
+ /* sections = */ NULL,
+ config_item_table_lookup,
+ config_table,
+ CONFIG_PARSE_WARN,
+ /* userdata = */ NULL);
+ if (r == -ENOENT)
return 0;
-
- /* unquote */
- n = strlen(str);
- if (n >= 2 &&
- ((str[0] == '"' && str[n - 1] == '"') ||
- (str[0] == '\'' && str[n - 1] == '\''))) {
- str[n - 1] = '\0';
- str++;
- }
-
- /* we set the udev log level here explicitly, this is supposed
- * to regulate the code in libudev/ and udev/. */
- return log_set_max_level_from_string(str);
+ return r;
}
int udev_parse_config(void) {
- _cleanup_free_ char *log_val = NULL;
- int r;
+ int r, log_val = -1;
+ const ConfigTableItem config_table[] = {
+ { NULL, "udev_log", config_parse_log_level, 0, &log_val },
+ {}
+ };
- r = parse_env_file(NULL, "/etc/udev/udev.conf",
- "udev_log", &log_val);
- if (r == -ENOENT)
- return 0;
+ r = udev_parse_config_full(config_table);
if (r < 0)
return r;
- r = udev_set_max_log_level(log_val);
- if (r < 0)
- log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r,
- "Failed to set udev log level '%s', ignoring: %m", log_val);
+ if (log_val >= 0)
+ log_set_max_level(log_val);
return 0;
}
#include "sd-device.h"
+#include "conf-parser.h"
#include "hashmap.h"
#include "time-util.h"
-int udev_set_max_log_level(char *str);
+int udev_parse_config_full(const ConfigTableItem config_table[]);
int udev_parse_config(void);
int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout_usec, sd_device **ret);
#include "rlimit-util.h"
#include "string-table.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-record.h"
#include "user-util.h"
return strv_free_and_replace(*l, n);
}
-int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
- UserDisposition *disposition = userdata, k;
-
- if (json_variant_is_null(variant)) {
- *disposition = _USER_DISPOSITION_INVALID;
- return 0;
- }
-
- if (!json_variant_is_string(variant))
- return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
-
- k = user_disposition_from_string(json_variant_string(variant));
- if (k < 0)
- return json_log(variant, flags, k, "Disposition type '%s' not known.", json_variant_string(variant));
-
- *disposition = k;
- return 0;
-}
-
-static int json_dispatch_storage(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
- UserStorage *storage = userdata, k;
-
- if (json_variant_is_null(variant)) {
- *storage = _USER_STORAGE_INVALID;
- return 0;
- }
-
- if (!json_variant_is_string(variant))
- return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
-
- k = user_storage_from_string(json_variant_string(variant));
- if (k < 0)
- return json_log(variant, flags, k, "Storage type '%s' not known.", json_variant_string(variant));
-
- *storage = k;
- return 0;
-}
+JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
+static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
static int json_dispatch_tasks_or_memory_max(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uint64_t *limit = userdata, k;
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m");
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
if (!array)
return log_oom();
- r = unbase64mem(json_variant_string(e), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(e), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
if (r < 0)
return r;
- r = json_dispatch(h->json, user_dispatch_table, json_flags, h);
+ r = json_dispatch(h->json, user_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h);
if (r < 0)
return r;
assert_se(!iterator->found_user);
- r = json_dispatch(parameters, dispatch_table, 0, &user_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &user_data);
if (r < 0)
goto finish;
assert_se(!iterator->found_group);
- r = json_dispatch(parameters, dispatch_table, 0, &group_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &group_data);
if (r < 0)
goto finish;
assert(!iterator->found_user_name);
assert(!iterator->found_group_name);
- r = json_dispatch(parameters, dispatch_table, 0, &membership_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &membership_data);
if (r < 0)
goto finish;
_COLOR_MAX,
};
+#define varlink_idl_log(error, format, ...) log_debug_errno(error, "Varlink-IDL: " format, ##__VA_ARGS__)
+#define varlink_idl_log_full(level, error, format, ...) log_full_errno(level, error, "Varlink-IDL: " format, ##__VA_ARGS__)
+
static int varlink_idl_format_all_fields(FILE *f, const VarlinkSymbol *symbol, VarlinkFieldDirection direction, const char *indent, const char *const colors[static _COLOR_MAX]);
static int varlink_idl_format_enum_values(
l = token_match(*p, allowed_delimiters, allowed_chars);
if (l == 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters));
}
t = strndup(*p, l);
if (r < 0)
return r;
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
field->named_type = TAKE_PTR(token);
field->field_type = VARLINK_NAMED_TYPE;
assert(n_fields);
if (depth > DEPTH_MAX)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX);
while (state != STATE_DONE) {
_cleanup_free_ char *token = NULL;
case STATE_OPEN:
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (!streq(token, "("))
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
state = STATE_NAME;
allowed_delimiters = ")";
assert(!field_name);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (streq(token, ")"))
state = STATE_DONE;
else {
assert(field_name);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (streq(token, ":")) {
VarlinkField *field;
if ((*symbol)->symbol_type < 0)
(*symbol)->symbol_type = VARLINK_STRUCT_TYPE;
if ((*symbol)->symbol_type == VARLINK_ENUM_TYPE)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column);
r = varlink_symbol_realloc(symbol, *n_fields + 1);
if (r < 0)
if ((*symbol)->symbol_type < 0)
(*symbol)->symbol_type = VARLINK_ENUM_TYPE;
if ((*symbol)->symbol_type != VARLINK_ENUM_TYPE)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column);
r = varlink_symbol_realloc(symbol, *n_fields + 1);
if (r < 0)
state = STATE_DONE;
}
} else
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
break;
assert(!field_name);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (streq(token, ",")) {
state = STATE_NAME;
allowed_delimiters = NULL;
} else if (streq(token, ")"))
state = STATE_DONE;
else
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
break;
default:
/* If we don't know the type of the symbol by now it was an empty () which doesn't allow us to
* determine if we look at an enum or a struct */
if ((*symbol)->symbol_type < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column);
return 0;
}
continue;
if (!field->named_type)
- return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name.");
+ return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name.");
found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->named_type);
if (!found)
- return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type);
+ return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type);
if (!IN_SET(found->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE))
- return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type);
+ return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type);
field->symbol = found;
}
case STATE_PRE_INTERFACE:
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (streq(token, "#")) {
r = varlink_idl_subparse_comment(&text, line, column);
if (r < 0)
allowed_delimiters = NULL;
allowed_chars = VALID_CHARS_INTERFACE_NAME;
} else
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
break;
case STATE_INTERFACE:
assert(n_symbols == 0);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
r = varlink_interface_realloc(&interface, n_symbols);
if (r < 0)
state = STATE_ERROR;
allowed_chars = VALID_CHARS_IDENTIFIER;
} else
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
break;
n_fields = 0;
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
r = varlink_symbol_realloc(&symbol, n_fields);
if (r < 0)
assert(symbol);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (!streq(token, "->"))
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_OUTPUT, 0);
if (r < 0)
n_fields = 0;
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
r = varlink_symbol_realloc(&symbol, n_fields);
if (r < 0)
n_fields = 0;
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
r = varlink_symbol_realloc(&symbol, n_fields);
if (r < 0)
symbol_name = symbol->name ?: "<anonymous>";
if (field->field_type <= 0 || field->field_type >= _VARLINK_FIELD_TYPE_MAX)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name);
if (field->field_type == VARLINK_ENUM_VALUE) {
if (symbol->symbol_type != VARLINK_ENUM_TYPE)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name);
if (field->field_flags != 0)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name);
} else {
if (symbol->symbol_type == VARLINK_ENUM_TYPE)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name);
if (!IN_SET(field->field_flags & ~VARLINK_NULLABLE, 0, VARLINK_ARRAY, VARLINK_MAP))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name);
}
if (symbol->symbol_type != VARLINK_METHOD) {
if (field->field_direction != VARLINK_REGULAR)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name);
} else {
if (!IN_SET(field->field_direction, VARLINK_INPUT, VARLINK_OUTPUT))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name);
}
if (field->symbol) {
if (!IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
if (field->field_type == VARLINK_NAMED_TYPE) {
const VarlinkSymbol *found;
if (!field->symbol->name || !field->named_type || !streq(field->symbol->name, field->named_type))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do do not match, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do do not match, refusing.", field->name, symbol_name);
/* If this is a named type, then check if it's properly part of the interface */
found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->symbol->name);
if (!found)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name);
if (!IN_SET(found->symbol_type, VARLINK_ENUM_TYPE, VARLINK_STRUCT_TYPE))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name);
} else {
/* If this is an anonymous type, then we recursively check if it's consistent, since
* it's not part of the interface, and hence we won't validate it from there. */
} else {
if (IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
if (field->named_type)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name);
}
if (field->named_type) {
if (field->field_type != VARLINK_NAMED_TYPE)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name);
} else {
if (field->field_type == VARLINK_NAMED_TYPE)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name);
}
return 0;
symbol_name = symbol->name ?: "<anonymous>";
if (symbol->symbol_type < 0 || symbol->symbol_type >= _VARLINK_SYMBOL_TYPE_MAX)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name);
if (IN_SET(symbol->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE) && varlink_symbol_is_empty(symbol))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name);
for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
Set **name_set = field->field_direction == VARLINK_OUTPUT ? &output_set : &input_set; /* for the method case we need two separate sets, otherwise we use the same */
if (!varlink_idl_field_name_is_valid(field->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name);
if (set_contains(*name_set, field->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name);
if (set_ensure_put(name_set, &string_hash_ops, field->name) < 0)
return log_oom();
assert(interface);
if (!varlink_idl_interface_name_is_valid(interface->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name);
for (const VarlinkSymbol *const *symbol = interface->symbols; *symbol; symbol++) {
if (!varlink_idl_symbol_name_is_valid((*symbol)->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name));
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name));
if (set_contains(name_set, (*symbol)->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name);
if (set_ensure_put(&name_set, &string_hash_ops, (*symbol)->name) < 0)
return log_oom();
case VARLINK_BOOL:
if (!json_variant_is_boolean(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name));
break;
case VARLINK_INT:
if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name));
break;
case VARLINK_FLOAT:
if (!json_variant_is_number(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name));
break;
case VARLINK_STRING:
if (!json_variant_is_string(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name));
break;
case VARLINK_OBJECT:
if (!json_variant_is_object(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
break;
if (!v || json_variant_is_null(v)) {
if (!FLAGS_SET(field->field_flags, VARLINK_NULLABLE))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name));
} else if (FLAGS_SET(field->field_flags, VARLINK_ARRAY)) {
JsonVariant *i;
if (!json_variant_is_array(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name));
JSON_VARIANT_ARRAY_FOREACH(i, v) {
r = varlink_idl_validate_field_element_type(field, i);
JsonVariant *e;
if (!json_variant_is_object(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
JSON_VARIANT_OBJECT_FOREACH(k, e, v) {
r = varlink_idl_validate_field_element_type(field, e);
if (!v) {
if (bad_field)
*bad_field = NULL;
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing.");
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing.");
}
switch (symbol->symbol_type) {
if (!json_variant_is_string(v)) {
if (bad_field)
*bad_field = symbol->name;
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name));
}
assert_se(s = json_variant_string(v));
if (!found) {
if (bad_field)
*bad_field = s;
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name));
}
break;
if (!json_variant_is_object(v)) {
if (bad_field)
*bad_field = symbol->name;
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name));
}
for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
if (!varlink_idl_find_field(symbol, name)) {
if (bad_field)
*bad_field = name;
- return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name);
}
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.Credentials.h"
+
+static VARLINK_DEFINE_METHOD(
+ Describe,
+ VARLINK_DEFINE_OUTPUT(Hostname, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(StaticHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(PrettyHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(DefaultHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HostnameSource, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(IconName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Chassis, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Deployment, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Location, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(KernelName, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(KernelRelease, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(KernelVersion, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HardwareVendor, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HardwareModel, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HardwareSerial, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareVersion, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareVendor, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareDate, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(MachineID, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(BootID, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(ProductUUID, VARLINK_STRING, VARLINK_NULLABLE));
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_Hostname,
+ "io.systemd.Hostname",
+ &vl_method_Describe);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_Hostname;
VARLINK_DEFINE_OUTPUT(IPv4AddressState, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(IPv6AddressState, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(CarrierState, VARLINK_STRING, 0),
- VARLINK_DEFINE_OUTPUT(OnlineState, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(OnlineState, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(OperationalState, VARLINK_STRING, 0));
static VARLINK_DEFINE_METHOD(GetNamespaceId,
- VARLINK_DEFINE_OUTPUT(NamespaceId, VARLINK_INT, 0));
+ VARLINK_DEFINE_OUTPUT(NamespaceId, VARLINK_INT, 0),
+ VARLINK_DEFINE_OUTPUT(NamespaceNSID, VARLINK_INT, VARLINK_NULLABLE));
VARLINK_DEFINE_INTERFACE(
io_systemd_Network,
VARLINK_DEFINE_OUTPUT(ready, VARLINK_BOOL, VARLINK_NULLABLE),
/* Subsequent replies */
VARLINK_DEFINE_OUTPUT(state, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(result, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(rcode, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(errno, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT_BY_TYPE(question, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(collectedQuestions, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(answer, Answer, VARLINK_NULLABLE|VARLINK_ARRAY));
static VARLINK_DEFINE_ERROR(QueryAborted);
static VARLINK_DEFINE_ERROR(
DNSSECValidationFailed,
- VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0));
+ VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(NoTrustAnchor);
static VARLINK_DEFINE_ERROR(ResourceRecordTypeUnsupported);
static VARLINK_DEFINE_ERROR(NetworkDown);
static VARLINK_DEFINE_ERROR(StubLoop);
static VARLINK_DEFINE_ERROR(
DNSError,
- VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0));
+ VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(CNAMELoop);
static VARLINK_DEFINE_ERROR(BadAddressSize);
bool allow_fd_passing_output:1;
bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */
+ bool input_sensitive:1; /* Whether incoming messages might be sensitive */
int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */
if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+ r = fd_nonblock(pair[1], false);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to disable O_NONBLOCK for varlink socket: %m");
+
r = safe_fork_full(
"(sd-vlexec)",
/* stdio_fds= */ NULL,
return 0;
}
+static int varlink_connect_ssh(Varlink **ret, const char *where) {
+ _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
+ _cleanup_(sigkill_waitp) pid_t pid = 0;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ assert_return(where, -EINVAL);
+
+ /* Connects to an SSH server via OpenSSH 9.4's -W switch to connect to a remote AF_UNIX socket. For
+ * now we do not expose this function directly, but only via varlink_connect_url(). */
+
+ const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh";
+ if (!path_is_valid(ssh))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH path is not valid, refusing: %s", ssh);
+
+ const char *e = strchr(where, ':');
+ if (!e)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH specification lacks a : separator between host and path, refusing: %s", where);
+
+ _cleanup_free_ char *h = strndup(where, e - where);
+ if (!h)
+ return log_oom_debug();
+
+ _cleanup_free_ char *c = strdup(e + 1);
+ if (!c)
+ return log_oom_debug();
+
+ if (!path_is_absolute(c))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Remote AF_UNIX socket path is not absolute, refusing: %s", c);
+
+ _cleanup_free_ char *p = NULL;
+ r = path_simplify_alloc(c, &p);
+ if (r < 0)
+ return r;
+
+ if (!path_is_normalized(p))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing: %s", p);
+
+ log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
+ return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+
+ r = safe_fork_full(
+ "(sd-vlssh)",
+ /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO },
+ /* except_fds= */ NULL,
+ /* n_except_fds= */ 0,
+ FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO,
+ &pid);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to spawn process: %m");
+ if (r == 0) {
+ /* Child */
+
+ execlp(ssh, "ssh", "-W", p, h, NULL);
+ log_debug_errno(errno, "Failed to invoke %s: %m", ssh);
+ _exit(EXIT_FAILURE);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ Varlink *v;
+ r = varlink_new(&v);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create varlink object: %m");
+
+ v->fd = TAKE_FD(pair[0]);
+ v->af = AF_UNIX;
+ v->exec_pid = TAKE_PID(pid);
+ varlink_set_state(v, VARLINK_IDLE_CLIENT);
+
+ *ret = v;
+ return 0;
+}
+
int varlink_connect_url(Varlink **ret, const char *url) {
_cleanup_free_ char *c = NULL;
const char *p;
- bool exec;
+ enum {
+ SCHEME_UNIX,
+ SCHEME_EXEC,
+ SCHEME_SSH,
+ } scheme;
int r;
assert_return(ret, -EINVAL);
assert_return(url, -EINVAL);
- // FIXME: Add support for vsock:, ssh-exec:, ssh-unix: URL schemes here. (The latter with OpenSSH
- // 9.4's -W switch for referencing remote AF_UNIX sockets.)
+ // FIXME: Maybe add support for vsock: and ssh-exec: URL schemes here.
- /* The Varlink URL scheme is a bit underdefined. We support only the unix: transport for now, plus an
- * exec: transport we made up ourselves. Strictly speaking this shouldn't even be called URL, since
- * it has nothing to do with Internet URLs by RFC. */
+ /* The Varlink URL scheme is a bit underdefined. We support only the spec-defined unix: transport for
+ * now, plus exec:, ssh: transports we made up ourselves. Strictly speaking this shouldn't even be
+ * called "URL", since it has nothing to do with Internet URLs by RFC. */
p = startswith(url, "unix:");
if (p)
- exec = false;
- else {
- p = startswith(url, "exec:");
- if (!p)
- return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported.");
-
- exec = true;
- }
+ scheme = SCHEME_UNIX;
+ else if ((p = startswith(url, "exec:")))
+ scheme = SCHEME_EXEC;
+ else if ((p = startswith(url, "ssh:")))
+ scheme = SCHEME_SSH;
+ else
+ return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported.");
/* The varlink.org reference C library supports more than just file system paths. We might want to
* support that one day too. For now simply refuse that. */
if (p[strcspn(p, ";?#")] != '\0')
return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported.");
- if (exec || p[0] != '@') { /* no validity checks for abstract namespace */
+ if (scheme == SCHEME_SSH)
+ return varlink_connect_ssh(ret, p);
+
+ if (scheme == SCHEME_EXEC || p[0] != '@') { /* no path validity checks for abstract namespace sockets */
if (!path_is_absolute(p))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path not absolute, refusing.");
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing.");
}
- if (exec)
+ if (scheme == SCHEME_EXEC)
return varlink_connect_exec(ret, c, NULL);
return varlink_connect_address(ret, c ?: p);
varlink_clear_current(v);
- v->input_buffer = mfree(v->input_buffer);
+ v->input_buffer = v->input_sensitive ? erase_and_free(v->input_buffer) : mfree(v->input_buffer);
v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer);
v->input_control_buffer = mfree(v->input_control_buffer);
}
static int varlink_parse_message(Varlink *v) {
- const char *e, *begin;
+ const char *e;
+ char *begin;
size_t sz;
int r;
sz = e - begin + 1;
r = json_parse(begin, 0, &v->current, NULL, NULL);
+ if (v->input_sensitive)
+ explicit_bzero_safe(begin, sz);
if (r < 0) {
/* If we encounter a parse failure flush all data. We cannot possibly recover from this,
* hence drop all buffered data now. */
return varlink_log_errno(v, r, "Failed to parse JSON: %m");
}
+ if (v->input_sensitive) {
+ /* Mark the parameters subfield as sensitive right-away, if that's requested */
+ JsonVariant *parameters = json_variant_by_key(v->current, "parameters");
+ if (parameters)
+ json_variant_sensitive(parameters);
+ }
+
+ if (DEBUG_LOGGING) {
+ _cleanup_(erase_and_freep) char *censored_text = NULL;
+
+ /* Suppress sensitive fields in the debug output */
+ r = json_variant_format(v->current, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text);
+ if (r < 0)
+ return r;
+
+ varlink_log(v, "Received message: %s", censored_text);
+ }
+
v->input_buffer_size -= sz;
if (v->input_buffer_size == 0)
static int varlink_format_json(Varlink *v, JsonVariant *m) {
_cleanup_(erase_and_freep) char *text = NULL;
- bool sensitive = false;
- int r;
+ int sz, r;
assert(v);
assert(m);
- r = json_variant_format(m, JSON_FORMAT_REFUSE_SENSITIVE, &text);
- if (r == -EPERM) {
- sensitive = true;
- r = json_variant_format(m, /* flags= */ 0, &text);
- }
- if (r < 0)
- return r;
- assert(text[r] == '\0');
+ sz = json_variant_format(m, /* flags= */ 0, &text);
+ if (sz < 0)
+ return sz;
+ assert(text[sz] == '\0');
- if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX)
+ if (v->output_buffer_size + sz + 1 > VARLINK_BUFFER_MAX)
return -ENOBUFS;
- varlink_log(v, "Sending message: %s", sensitive ? "<sensitive data>" : text);
+ if (DEBUG_LOGGING) {
+ _cleanup_(erase_and_freep) char *censored_text = NULL;
+
+ /* Suppress sensitive fields in the debug output */
+ r = json_variant_format(m, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text);
+ if (r < 0)
+ return r;
+
+ varlink_log(v, "Sending message: %s", censored_text);
+ }
if (v->output_buffer_size == 0) {
free_and_replace(v->output_buffer, text);
- v->output_buffer_size = r + 1;
+ v->output_buffer_size = sz + 1;
v->output_buffer_index = 0;
} else if (v->output_buffer_index == 0) {
- if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + r + 1))
+ if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + sz + 1))
return -ENOMEM;
- memcpy(v->output_buffer + v->output_buffer_size, text, r + 1);
- v->output_buffer_size += r + 1;
+ memcpy(v->output_buffer + v->output_buffer_size, text, sz + 1);
+ v->output_buffer_size += sz + 1;
} else {
char *n;
- const size_t new_size = v->output_buffer_size + r + 1;
+ const size_t new_size = v->output_buffer_size + sz + 1;
n = new(char, new_size);
if (!n)
return -ENOMEM;
- memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, r + 1);
+ memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1);
free_and_replace(v->output_buffer, n);
v->output_buffer_size = new_size;
v->output_buffer_index = 0;
}
- if (sensitive)
+ if (json_variant_is_sensitive_recursive(m))
v->output_buffer_sensitive = true; /* Propagate sensitive flag */
else
text = mfree(text); /* No point in the erase_and_free() destructor declared above */
return 0;
}
+int varlink_set_input_sensitive(Varlink *v) {
+ assert_return(v, -EINVAL);
+
+ v->input_sensitive = true;
+ return 0;
+}
+
int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
_cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
int r;
TAKE_FD(cfd);
+ if (FLAGS_SET(ss->server->flags, VARLINK_SERVER_INPUT_SENSITIVE))
+ varlink_set_input_sensitive(v);
+
if (ss->server->connect_callback) {
r = ss->server->connect_callback(ss->server, v, ss->server->userdata);
if (r < 0) {
n++;
}
+ /* For debug purposes let's listen on an explicitly specified address */
+ const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN");
+ if (e) {
+ r = varlink_server_listen_address(s, e, FLAGS_SET(s->flags, VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666);
+ if (r < 0)
+ return r;
+ }
+
return n;
}
/* Returns true if this is a "pure" varlink server invocation, i.e. with one fd passed. */
+ const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); /* Permit a manual override for testing purposes */
+ if (e)
+ return true;
+
r = sd_listen_fds_with_names(/* unset_environment= */ false, &names);
if (r < 0)
return r;
VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */
VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */
VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from VarlinkServer userdata */
- _VARLINK_SERVER_FLAGS_ALL = (1 << 4) - 1,
+ VARLINK_SERVER_INPUT_SENSITIVE = 1 << 4, /* Automatically mark al connection input as sensitive */
+ _VARLINK_SERVER_FLAGS_ALL = (1 << 5) - 1,
} VarlinkServerFlags;
typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
int varlink_set_description(Varlink *v, const char *d);
+/* Automatically mark the parameters part of incoming messages as security sensitive */
+int varlink_set_input_sensitive(Varlink *v);
+
/* Create a varlink server */
int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags);
VarlinkServer *varlink_server_ref(VarlinkServer *s);
if (!wildcard)
goto bypass; /* Not a pattern, then bypass */
- /* We found the '___' wildcard, hence evertyhing after it is our filter suffix, and
- * evertyhing before is our filter basename */
+ /* We found the '___' wildcard, hence everything after it is our filter suffix, and
+ * everything before is our filter basename */
*wildcard = 0;
filter_suffix = empty_to_null(wildcard + 3);
typedef struct PickFilter {
uint32_t type_mask; /* A mask of 1U << DT_REG, 1U << DT_DIR, … */
- const char *basename; /* Can be overriden by search pattern */
+ const char *basename; /* Can be overridden by search pattern */
const char *version;
Architecture architecture;
- const char *suffix; /* Can be overriden by search pattern */
+ const char *suffix; /* Can be overridden by search pattern */
} PickFilter;
typedef struct PickResult {
.tries_done = UINT_MAX, \
}
-#define TAKE_PICK_RESULT(ptr) TAKE_GENERIC(ptr, PickResult, PICK_RESULT_NULL)
+#define TAKE_PICK_RESULT(pick) TAKE_GENERIC(pick, PickResult, PICK_RESULT_NULL)
void pick_result_done(PickResult *p);
static int update_timeout(void) {
int r;
+ usec_t previous_timeout;
assert(watchdog_timeout > 0);
if (watchdog_fd < 0)
return 0;
+ previous_timeout = watchdog_timeout;
+
if (watchdog_timeout != USEC_INFINITY) {
r = watchdog_set_timeout();
if (r < 0) {
if (watchdog_timeout == USEC_INFINITY) {
r = watchdog_read_timeout();
- if (r < 0)
- return log_error_errno(r, "Failed to query watchdog HW timeout: %m");
+ if (r < 0) {
+ if (!ERRNO_IS_NOT_SUPPORTED(r))
+ return log_error_errno(r, "Failed to query watchdog HW timeout: %m");
+ log_info("Reading watchdog timeout is not supported, reusing the configured timeout.");
+ watchdog_timeout = previous_timeout;
+ }
}
/* If the watchdog timeout was changed, the pretimeout could have been
if (r < 0)
return log_debug_errno(r, "Failed to get NL80211_ATTR_IFTYPE attribute: %m");
- r = sd_netlink_message_read_data_suffix0(reply, NL80211_ATTR_SSID, &len, (void**) &ssid);
+ r = sd_netlink_message_read_data(reply, NL80211_ATTR_SSID, &len, (void**) &ssid);
if (r < 0 && r != -ENODATA)
return log_debug_errno(r, "Failed to get NL80211_ATTR_SSID attribute: %m");
if (r >= 0) {
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Make sure unix/* and vsock/* can be used to connect to AF_UNIX and AF_VSOCK paths
+#
+Host unix/* vsock/*
+ ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy %h %p
+ ProxyUseFdpass yes
+ CheckHostIP no
+
+ # Disable all kinds of host identity checks, since these addresses are generally ephemeral.
+ StrictHostKeyChecking no
+ UserKnownHostsFile /dev/null
+
+# Allow connecting to the local host directly via ".host"
+Host .host
+ ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p
+ ProxyUseFdpass yes
+ CheckHostIP no
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+executables += [
+ generator_template + {
+ 'name' : 'systemd-ssh-generator',
+ 'sources' : files('ssh-generator.c'),
+ },
+ libexec_template + {
+ 'name' : 'systemd-ssh-proxy',
+ 'sources' : files('ssh-proxy.c'),
+ },
+]
+
+custom_target(
+ '20-systemd-ssh-proxy.conf',
+ input : '20-systemd-ssh-proxy.conf.in',
+ output : '20-systemd-ssh-proxy.conf',
+ command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
+ install : true,
+ install_dir : libexecdir / 'ssh_config.d')
+
+install_emptydir(sshconfdir)
+
+meson.add_install_script(sh, '-c',
+ ln_s.format(libexecdir / 'ssh_config.d' / '20-systemd-ssh-proxy.conf', sshconfdir / '20-systemd-ssh-proxy.conf'))
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "creds-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "generator.h"
+#include "install.h"
+#include "missing_socket.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "proc-cmdline.h"
+#include "socket-netlink.h"
+#include "socket-util.h"
+#include "special.h"
+#include "virt.h"
+
+/* A small generator binding potentially five or more SSH sockets:
+ *
+ * 1. Listen on AF_VSOCK port 22 if we run in a VM with AF_VSOCK enabled
+ * 2. Listen on AF_UNIX socket /run/host/unix-export/ssh if we run in a container with /run/host/ support
+ * 3. Listen on AF_UNIX socket /run/ssh-unix-local/socket (always)
+ * 4. Listen on any socket specified via kernel command line option systemd.ssh_listen=
+ * 5. Similar, but from system credential ssh.listen
+ *
+ * The first two provide a nice way for hosts to connect to containers and VMs they invoke via the usual SSH
+ * logic, but without waiting for networking or suchlike. The third allows the same for local clients. */
+
+static const char *arg_dest = NULL;
+static bool arg_auto = true;
+static char **arg_listen_extra = NULL;
+
+static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
+ int r;
+
+ assert(key);
+
+ if (proc_cmdline_key_streq(key, "systemd.ssh_auto")) {
+ r = value ? parse_boolean(value) : 1;
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse systemd.ssh_auto switch \"%s\", ignoring: %m", value);
+ else
+ arg_auto = r;
+
+ } else if (proc_cmdline_key_streq(key, "systemd.ssh_listen")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ SocketAddress sa;
+ r = socket_address_parse(&sa, value);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", value);
+ else {
+ _cleanup_free_ char *s = NULL;
+ r = socket_address_print(&sa, &s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format socket address: %m");
+
+ if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
+ return log_oom();
+ }
+ }
+
+ return 0;
+}
+
+static int make_sshd_template_unit(
+ const char *dest,
+ const char *template,
+ const char *sshd_binary,
+ const char *found_sshd_template_service,
+ char **generated_sshd_template_unit) {
+
+ int r;
+
+ assert(dest);
+ assert(template);
+ assert(sshd_binary);
+ assert(generated_sshd_template_unit);
+
+ /* If the system has a suitable template already, symlink it to the name we want to reuse it */
+ if (found_sshd_template_service)
+ return generator_add_symlink(
+ dest,
+ template,
+ /* dep_type= */ NULL,
+ found_sshd_template_service);
+
+ if (!*generated_sshd_template_unit) {
+ _cleanup_fclose_ FILE *f = NULL;
+
+ r = generator_open_unit_file_full(
+ dest,
+ /* source= */ NULL,
+ "sshd-generated@.service", /* Give this generated unit a generic name, since we want to use it for both AF_UNIX and AF_VSOCK */
+ &f,
+ generated_sshd_template_unit,
+ /* ret_temp_path= */ NULL);
+ if (r < 0)
+ return r;
+
+ fprintf(f,
+ "[Unit]\n"
+ "Description=OpenSSH Per-Connection Server Daemon\n"
+ "Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n"
+ "[Service]\n"
+ "ExecStart=-%s -i\n"
+ "StandardInput=socket",
+ sshd_binary);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write sshd template: %m");
+ }
+
+ return generator_add_symlink(
+ dest,
+ template,
+ /* dep_type= */ NULL,
+ *generated_sshd_template_unit);
+}
+
+static int write_socket_unit(
+ const char *dest,
+ const char *unit,
+ const char *listen_stream,
+ const char *comment) {
+
+ int r;
+
+ assert(dest);
+ assert(unit);
+ assert(listen_stream);
+ assert(comment);
+
+ _cleanup_fclose_ FILE *f = NULL;
+ r = generator_open_unit_file(
+ dest,
+ /* source= */ NULL,
+ unit,
+ &f);
+ if (r < 0)
+ return r;
+
+ fprintf(f,
+ "[Unit]\n"
+ "Description=OpenSSH Server Socket (systemd-ssh-generator, %s)\n"
+ "Documentation=man:systemd-ssh-generator(8)\n"
+ "\n[Socket]\n"
+ "ListenStream=%s\n"
+ "Accept=yes\n"
+ "PollLimitIntervalSec=30s\n"
+ "PollLimitBurst=50\n",
+ comment,
+ listen_stream);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment);
+
+ r = generator_add_symlink(
+ dest,
+ SPECIAL_SOCKETS_TARGET,
+ "wants",
+ unit);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int add_vsock_socket(
+ const char *dest,
+ const char *sshd_binary,
+ const char *found_sshd_template_unit,
+ char **generated_sshd_template_unit) {
+
+ int r;
+
+ assert(dest);
+ assert(generated_sshd_template_unit);
+
+ Virtualization v = detect_vm();
+ if (v < 0)
+ return log_error_errno(v, "Failed to detect if we run in a VM: %m");
+ if (v == VIRTUALIZATION_NONE) {
+ log_debug("Not running in a VM, not listening on AF_VSOCK.");
+ return 0;
+ }
+
+ _cleanup_close_ int vsock_fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ if (vsock_fd < 0) {
+ if (ERRNO_IS_NOT_SUPPORTED(errno)) {
+ log_debug("Not creating AF_VSOCK ssh listener, since AF_VSOCK is not available.");
+ return 0;
+ }
+
+ return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m");
+ }
+
+ vsock_fd = safe_close(vsock_fd);
+
+ /* Determine the local CID so that we can log it to help users to connect to this VM */
+ unsigned local_cid;
+ r = vsock_get_local_cid(&local_cid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m");
+
+ r = make_sshd_template_unit(
+ dest,
+ "sshd-vsock@.service",
+ sshd_binary,
+ found_sshd_template_unit,
+ generated_sshd_template_unit);
+ if (r < 0)
+ return r;
+
+ r = write_socket_unit(
+ dest,
+ "sshd-vsock.socket",
+ "vsock::22",
+ "AF_VSOCK");
+ if (r < 0)
+ return r;
+
+ log_info("Binding SSH to AF_VSOCK vsock::22.\n"
+ "→ connect via 'ssh vsock/%u' from host", local_cid);
+ return 0;
+}
+
+static int add_local_unix_socket(
+ const char *dest,
+ const char *sshd_binary,
+ const char *found_sshd_template_unit,
+ char **generated_sshd_template_unit) {
+
+ int r;
+
+ assert(dest);
+ assert(sshd_binary);
+ assert(generated_sshd_template_unit);
+
+ r = make_sshd_template_unit(
+ dest,
+ "sshd-unix-local@.service",
+ sshd_binary,
+ found_sshd_template_unit,
+ generated_sshd_template_unit);
+ if (r < 0)
+ return r;
+
+ r = write_socket_unit(
+ dest,
+ "sshd-unix-local.socket",
+ "/run/ssh-unix-local/socket",
+ "AF_UNIX Local");
+ if (r < 0)
+ return r;
+
+
+ log_info("Binding SSH to AF_UNIX socket /run/ssh-unix-local/socket.\n"
+ "→ connect via 'ssh .host' locally");
+ return 0;
+}
+
+static int add_export_unix_socket(
+ const char *dest,
+ const char *sshd_binary,
+ const char *found_sshd_template_unit,
+ char **generated_sshd_template_unit) {
+
+ int r;
+
+ assert(dest);
+ assert(sshd_binary);
+ assert(generated_sshd_template_unit);
+
+ Virtualization v = detect_container();
+ if (v < 0)
+ return log_error_errno(v, "Failed to detect if we run in a container: %m");
+ if (v == VIRTUALIZATION_NONE) {
+ log_debug("Not running in container, not listening on /run/host/unix-export/ssh");
+ return 0;
+ }
+
+ if (access("/run/host/unix-export/", W_OK) < 0) {
+ if (errno == ENOENT) {
+ log_debug("Container manager does not provide /run/host/unix-export/ mount, not binding AF_UNIX socket there.");
+ return 0;
+ }
+ if (errno == EROFS || ERRNO_IS_PRIVILEGE(errno)) {
+ log_debug("Container manager does not provide write access to /run/host/unix-export/, not binding AF_UNIX socket there.");
+ return 0;
+ }
+
+ return log_error_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
+ }
+
+ r = make_sshd_template_unit(
+ dest,
+ "sshd-unix-export@.service",
+ sshd_binary,
+ found_sshd_template_unit,
+ generated_sshd_template_unit);
+ if (r < 0)
+ return r;
+
+ r = write_socket_unit(
+ dest,
+ "sshd-unix-export.socket",
+ "/run/host/unix-export/ssh",
+ "AF_UNIX Export");
+ if (r < 0)
+ return r;
+
+ log_info("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n"
+ "→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host");
+
+ return 0;
+}
+
+static int add_extra_sockets(
+ const char *dest,
+ const char *sshd_binary,
+ const char *found_sshd_template_unit,
+ char **generated_sshd_template_unit) {
+
+ unsigned n = 1;
+ int r;
+
+ assert(dest);
+ assert(sshd_binary);
+ assert(generated_sshd_template_unit);
+
+ if (strv_isempty(arg_listen_extra))
+ return 0;
+
+ STRV_FOREACH(i, arg_listen_extra) {
+ _cleanup_free_ char *service = NULL, *socket = NULL;
+
+ if (n > 1) {
+ if (asprintf(&service, "sshd-extra-%u@.service", n) < 0)
+ return log_oom();
+
+ if (asprintf(&socket, "sshd-extra-%u.socket", n) < 0)
+ return log_oom();
+ }
+
+ r = make_sshd_template_unit(
+ dest,
+ service ?: "sshd-extra@.service",
+ sshd_binary,
+ found_sshd_template_unit,
+ generated_sshd_template_unit);
+ if (r < 0)
+ return r;
+
+ r = write_socket_unit(
+ dest,
+ socket ?: "sshd-extra.socket",
+ *i,
+ *i);
+ if (r < 0)
+ return r;
+
+ log_info("Binding SSH to socket %s.", *i);
+ n++;
+ }
+
+ return 0;
+}
+
+static int parse_credentials(void) {
+ _cleanup_free_ char *b = NULL;
+ size_t sz = 0;
+ int r;
+
+ r = read_credential_with_decryption("ssh.listen", (void*) &b, &sz);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 0;
+
+ _cleanup_fclose_ FILE *f = NULL;
+ f = fmemopen_unlocked(b, sz, "r");
+ if (!f)
+ return log_oom();
+
+ for (;;) {
+ _cleanup_free_ char *item = NULL;
+
+ r = read_stripped_line(f, LINE_MAX, &item);
+ if (r == 0)
+ break;
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse credential 'ssh.listen': %m");
+ break;
+ }
+
+ if (startswith(item, "#"))
+ continue;
+
+ SocketAddress sa;
+ r = socket_address_parse(&sa, item);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", item);
+ continue;
+ }
+
+ _cleanup_free_ char *s = NULL;
+ r = socket_address_print(&sa, &s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format socket address: %m");
+
+ if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+static int run(const char *dest, const char *dest_early, const char *dest_late) {
+ int r;
+
+ assert_se(arg_dest = dest);
+
+ r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, /* flags= */ 0);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+
+ (void) parse_credentials();
+
+ strv_sort(arg_listen_extra);
+ strv_uniq(arg_listen_extra);
+
+ if (!arg_auto && strv_isempty(arg_listen_extra)) {
+ log_debug("Disabling SSH generator logic, because as it has been turned off explicitly.");
+ return 0;
+ }
+
+ _cleanup_free_ char *sshd_binary = NULL;
+ r = find_executable("sshd", &sshd_binary);
+ if (r == -ENOENT) {
+ log_info("Disabling SSH generator logic, since sshd is not installed.");
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine if sshd is installed: %m");
+
+ _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, /* root_dir= */ NULL);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ char *found_sshd_template_unit = NULL;
+ r = unit_file_exists_full(RUNTIME_SCOPE_SYSTEM, &lp, "sshd@.service", &found_sshd_template_unit);
+ if (r < 0)
+ return log_error_errno(r, "Unable to detect if sshd@.service exists: %m");
+
+ _cleanup_free_ char *generated_sshd_template_unit = NULL;
+ RET_GATHER(r, add_extra_sockets(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
+
+ if (arg_auto) {
+ RET_GATHER(r, add_vsock_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
+ RET_GATHER(r, add_local_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
+ RET_GATHER(r, add_export_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
+ }
+
+ return r;
+}
+
+DEFINE_MAIN_GENERATOR_FUNCTION(run);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <net/if_arp.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "iovec-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "missing_socket.h"
+#include "parse-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "strv.h"
+
+static int process_vsock(const char *host, const char *port) {
+ int r;
+
+ assert(host);
+ assert(port);
+
+ union sockaddr_union sa = {
+ .vm.svm_family = AF_VSOCK,
+ };
+
+ r = vsock_parse_cid(host, &sa.vm.svm_cid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse vsock cid: %s", host);
+
+ r = vsock_parse_port(port, &sa.vm.svm_port);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse vsock port: %s", port);
+
+ _cleanup_close_ int fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to allocate AF_VSOCK socket: %m");
+
+ if (connect(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0)
+ return log_error_errno(errno, "Failed to connect to vsock:%u:%u: %m", sa.vm.svm_cid, sa.vm.svm_port);
+
+ /* OpenSSH wants us to send a single byte along with the file descriptor, hence do so */
+ r = send_one_fd_iov(STDOUT_FILENO, fd, &IOVEC_NUL_BYTE, /* n_iovec= */ 1, /* flags= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to send socket via STDOUT: %m");
+
+ log_debug("Successfully sent AF_VSOCK socket via STDOUT.");
+ return 0;
+}
+
+static int process_unix(const char *path) {
+ int r;
+
+ assert(path);
+
+ /* We assume the path is absolute unless it starts with a dot (or is already explicitly absolute) */
+ _cleanup_free_ char *prefixed = NULL;
+ if (!STARTSWITH_SET(path, "/", "./")) {
+ prefixed = strjoin("/", path);
+ if (!prefixed)
+ return log_oom();
+
+ path = prefixed;
+ }
+
+ _cleanup_close_ int fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to allocate AF_UNIX socket: %m");
+
+ r = connect_unix_path(fd, AT_FDCWD, path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to AF_UNIX socket %s: %m", path);
+
+ r = send_one_fd_iov(STDOUT_FILENO, fd, &IOVEC_NUL_BYTE, /* n_iovec= */ 1, /* flags= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to send socket via STDOUT: %m");
+
+ log_debug("Successfully sent AF_UNIX socket via STDOUT.");
+ return 0;
+}
+
+static int run(int argc, char* argv[]) {
+
+ log_setup();
+
+ if (argc != 3)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments: host and port.");
+
+ const char *host = argv[1], *port = argv[2];
+
+ const char *p = startswith(host, "vsock/");
+ if (p)
+ return process_vsock(p, port);
+
+ p = startswith(host, "unix/");
+ if (p)
+ return process_unix(p);
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Don't know how to parse host name specification: %s", host);
+}
+
+DEFINE_MAIN_FUNCTION(run);
opterr = 0; /* do not print errors */
}
+ /* We need to reset some global state manually here since libfuzzer feeds a single process with
+ * multiple inputs, so we might carry over state from previous invocations that can trigger
+ * certain asserts. */
optind = 0; /* this tells the getopt machinery to reinitialize */
+ arg_transport = BUS_TRANSPORT_LOCAL;
r = systemctl_dispatch_parse_argv(strv_length(argv), argv);
if (r < 0)
sd_bus *bus;
int r;
- if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) {
+ if (!isempty(arg_root) || running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) {
if (!arg_quiet)
puts("offline");
return EXIT_FAILURE;
while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) {
- r = strv_extend(p, type);
- if (r < 0)
- return r;
-
- r = strv_extend(p, path);
+ r = strv_extend_many(p, type, path);
if (r < 0)
return r;
}
/* Reading messages */
int sd_netlink_message_read(sd_netlink_message *m, uint16_t attr_type, size_t size, void *data);
int sd_netlink_message_read_data(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data);
-int sd_netlink_message_read_data_suffix0(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data);
int sd_netlink_message_read_string_strdup(sd_netlink_message *m, uint16_t attr_type, char **data);
int sd_netlink_message_read_string(sd_netlink_message *m, uint16_t attr_type, const char **data);
int sd_netlink_message_read_strv(sd_netlink_message *m, uint16_t container_type, uint16_t attr_type, char ***ret);
int sd_rtnl_message_new_mdb(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int mdb_ifindex);
+int sd_rtnl_message_new_nsid(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type);
+
/* genl */
int sd_genl_socket_open(sd_netlink **ret);
int sd_genl_message_new(sd_netlink *genl, const char *family_name, uint8_t cmd, sd_netlink_message **ret);
if (strlen(t) != sizeof(found.sha256sum) * 2)
goto nope;
- r = unhexmem(t, sizeof(found.sha256sum) * 2, &d, &l);
+ r = unhexmem_full(t, sizeof(found.sha256sum) * 2, /* secure = */ false, &d, &l);
if (r == -ENOMEM)
return r;
if (r < 0)
if (p[0] == '\\')
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr);
- r = unhexmem(p, 64, &h, &hlen);
+ r = unhexmem_full(p, 64, /* secure = */ false, &h, &hlen);
if (r < 0)
return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr);
#include "strv.h"
#include "sync-util.h"
#include "tmpfile-util-label.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "uid-range.h"
#include "user-util.h"
#include "utf8.h"
Set *names;
uid_t search_uid;
- UidRange *uid_range;
+ UIDRange *uid_range;
UGIDAllocationRange login_defs;
bool login_defs_need_warning;
FILE *group) {
char **a;
+ int r;
assert(c);
assert(gr);
if (strv_contains(l, *i))
continue;
- if (strv_extend(&l, *i) < 0)
- return -ENOMEM;
+ r = strv_extend(&l, *i);
+ if (r < 0)
+ return r;
added = true;
}
if (added) {
struct group t;
- int r;
strv_uniq(l);
strv_sort(l);
FILE *gshadow) {
char **a;
+ int r;
assert(sg);
assert(gshadow);
if (strv_contains(l, *i))
continue;
- if (strv_extend(&l, *i) < 0)
- return -ENOMEM;
+ r = strv_extend(&l, *i);
+ if (r < 0)
+ return r;
added = true;
}
if (added) {
struct sgrp t;
- int r;
strv_uniq(l);
strv_sort(l);
'test-install-file.c',
'test-install-root.c',
'test-io-util.c',
+ 'test-iovec-util.c',
'test-journal-importer.c',
'test-kbd-util.c',
'test-limits-util.c',
'test-terminal-util.c',
'test-tmpfile-util.c',
'test-udev-util.c',
- 'test-uid-alloc-range.c',
+ 'test-uid-classification.c',
'test-uid-range.c',
'test-umask-util.c',
'test-unaligned.c',
#include "tests.h"
#include "tmpfile-util.h"
#include "tomoyo-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#include "virt.h"
#include "creds-util.h"
#include "fileio.h"
+#include "id128-util.h"
+#include "iovec-util.h"
#include "path-util.h"
#include "rm-rf.h"
#include "tests.h"
#include "tmpfile-util.h"
+#include "tpm2-util.h"
TEST(read_credential_strings) {
_cleanup_free_ char *x = NULL, *y = NULL, *saved = NULL, *p = NULL;
assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0);
assert_se(x == NULL);
- assert_se(streq(y, "piff"));
+ assert_se(streq(y, "paff"));
p = mfree(p);
assert_se(p = path_join(tmp, "foo"));
assert_se(write_string_file(p, "knurz", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
- assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0);
- assert_se(streq(x, "knurz"));
- assert_se(streq(y, "piff"));
-
- y = mfree(y);
-
assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0);
assert_se(streq(x, "knurz"));
assert_se(streq(y, "paff"));
assert_se(fwrite("x\0y", 1, 3, f) == 3); /* embedded NUL byte should result in EBADMSG when reading back with read_credential_strings_many() */
f = safe_fclose(f);
- assert_se(read_credential_strings_many("bazz", &x, "foo", &y) == -EBADMSG);
+ y = mfree(y);
+
+ assert_se(read_credential_strings_many("bazz", &x, "bar", &y) == -EBADMSG);
assert_se(streq(x, "knurz"));
assert_se(streq(y, "paff"));
assert_se(credential_glob_valid(buf));
}
+static void test_encrypt_decrypt_with(sd_id128_t mode) {
+ static const struct iovec plaintext = CONST_IOVEC_MAKE_STRING("this is a super secret string");
+ int r;
+
+ log_notice("Running encryption/decryption test with mode " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(mode));
+
+ _cleanup_(iovec_done) struct iovec encrypted = {};
+ r = encrypt_credential_and_warn(
+ mode,
+ "foo",
+ /* timestamp= */ USEC_INFINITY,
+ /* not_after=*/ USEC_INFINITY,
+ /* tpm2_device= */ NULL,
+ /* tpm2_hash_pcr_mask= */ 0,
+ /* tpm2_pubkey_path= */ NULL,
+ /* tpm2_pubkey_pcr_mask= */ 0,
+ &plaintext,
+ CREDENTIAL_ALLOW_NULL,
+ &encrypted);
+ if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) {
+ log_notice_errno(r, "Skipping test encryption mode " SD_ID128_FORMAT_STR ", because /etc/machine-id is not initialized.", SD_ID128_FORMAT_VAL(mode));
+ return;
+ }
+ if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
+ log_notice_errno(r, "Skipping test encryption mode " SD_ID128_FORMAT_STR ", because encrypted credentials are not supported.", SD_ID128_FORMAT_VAL(mode));
+ return;
+ }
+
+ assert_se(r >= 0);
+
+ _cleanup_(iovec_done) struct iovec decrypted = {};
+ r = decrypt_credential_and_warn(
+ "bar",
+ /* validate_timestamp= */ USEC_INFINITY,
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ &encrypted,
+ CREDENTIAL_ALLOW_NULL,
+ &decrypted);
+ assert_se(r == -EREMOTE); /* name didn't match */
+
+ r = decrypt_credential_and_warn(
+ "foo",
+ /* validate_timestamp= */ USEC_INFINITY,
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ &encrypted,
+ CREDENTIAL_ALLOW_NULL,
+ &decrypted);
+ assert_se(r >= 0);
+
+ assert_se(iovec_memcmp(&plaintext, &decrypted) == 0);
+}
+
+static bool try_tpm2(void) {
+#if HAVE_TPM2
+ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
+ int r;
+
+ r = tpm2_context_new(/* device= */ NULL, &tpm2_context);
+ if (r < 0)
+ log_notice_errno(r, "Failed to create TPM2 context, assuming no TPM2 support or privileges: %m");
+
+ return r >= 0;
+#else
+ return false;
+#endif
+}
+
+TEST(credential_encrypt_decrypt) {
+ _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+ _cleanup_free_ char *j = NULL;
+
+ test_encrypt_decrypt_with(CRED_AES256_GCM_BY_NULL);
+
+ assert_se(mkdtemp_malloc(NULL, &d) >= 0);
+ j = path_join(d, "secret");
+ assert_se(j);
+
+ const char *e = getenv("SYSTEMD_CREDENTIAL_SECRET");
+ _cleanup_free_ char *ec = NULL;
+
+ if (e)
+ assert_se(ec = strdup(e));
+
+ assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", j, true) >= 0);
+
+ test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST);
+
+ if (try_tpm2()) {
+ test_encrypt_decrypt_with(CRED_AES256_GCM_BY_TPM2_HMAC);
+ test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
+ }
+
+ if (ec)
+ assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true) >= 0);
+
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
assert_se(mkdir_p(f, 0755) >= 0);
assert_se(make_inaccessible_nodes(f, 1, 1) >= 0);
+ assert_se(make_inaccessible_nodes(f, 1, 1) >= 0); /* 2nd call should be a clean NOP */
f = prefix_roota(p, "/run/systemd/inaccessible/reg");
assert_se(stat(f, &st) >= 0);
assert_se( unit_has_dependency(manager_get_unit(m, "non-existing-on-failure.target"), UNIT_ATOM_ON_FAILURE_OF, a));
assert_se( unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, "non-existing-on-success.target")));
assert_se( unit_has_dependency(manager_get_unit(m, "non-existing-on-success.target"), UNIT_ATOM_ON_SUCCESS_OF, a));
- assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, "basic.target")));
- assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, "basic.target")));
- assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE_OF, manager_get_unit(m, "basic.target")));
- assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS_OF, manager_get_unit(m, "basic.target")));
+ assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, SPECIAL_BASIC_TARGET)));
+ assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, SPECIAL_BASIC_TARGET)));
+ assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE_OF, manager_get_unit(m, SPECIAL_BASIC_TARGET)));
+ assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS_OF, manager_get_unit(m, SPECIAL_BASIC_TARGET)));
assert_se(!unit_has_dependency(a, UNIT_ATOM_PROPAGATES_RELOAD_TO, manager_get_unit(m, "non-existing-on-failure.target")));
assert_se(unit_has_name(a, "a.service"));
static char *user_runtime_unit_dir = NULL;
static bool can_unshare;
static bool have_net_dummy;
+static bool have_netns;
static unsigned n_ran_tests = 0;
STATIC_DESTRUCTOR_REGISTER(user_runtime_unit_dir, freep);
if (!have_net_dummy)
return (void)log_notice("Skipping %s, dummy network interface not available", __func__);
+ if (!have_netns)
+ return (void)log_notice("Skipping %s, network namespace not available", __func__);
+
r = find_executable("ip", NULL);
if (r < 0) {
log_notice_errno(r, "Skipping %s, could not find ip binary: %m", __func__);
if (have_net_dummy) {
/* Create a network namespace and a dummy interface in it for NetworkNamespacePath= */
- (void) system("ip netns add test-execute-netns");
- (void) system("ip netns exec test-execute-netns ip link add dummy-test-ns type dummy");
+ have_netns = system("ip netns add test-execute-netns") == 0;
+ have_netns = have_netns && system("ip netns exec test-execute-netns ip link add dummy-test-ns type dummy") == 0;
}
return EXIT_SUCCESS;
log_debug("hexmem(\"%s\") → \"%s\" (expected: \"%s\")", strnull(in), result, expected);
assert_se(streq(result, expected));
- assert_se(unhexmem(result, SIZE_MAX, &mem, &len) >= 0);
+ assert_se(unhexmem(result, &mem, &len) >= 0);
assert_se(memcmp_safe(mem, in, len) == 0);
}
_cleanup_free_ void *mem = NULL;
size_t len;
- assert_se(unhexmem(s, l, &mem, &len) == retval);
+ assert_se(unhexmem_full(s, l, /* secure = */ false, &mem, &len) == retval);
if (retval == 0) {
char *answer;
assert_se(encoded);
assert_se((size_t) l == strlen(encoded));
- assert_se(unbase64mem(encoded, SIZE_MAX, &decoded, &decoded_size) >= 0);
+ assert_se(unbase64mem(encoded, &decoded, &decoded_size) >= 0);
assert_se(decoded_size == n);
assert_se(memcmp(data, decoded, n) == 0);
_cleanup_free_ void *buffer = NULL;
size_t size = 0;
- assert_se(unbase64mem(input, SIZE_MAX, &buffer, &size) == ret);
+ assert_se(unbase64mem(input, &buffer, &size) == ret);
if (ret >= 0) {
assert_se(size == strlen(output));
assert_se(memcmp(buffer, output, size) == 0);
size_t size;
/* This is regular base64 */
- assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g/xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5+p8e6itqrIwzecu98+rNyUVDhWBzS0PMwxEw==", SIZE_MAX, &buffer, &size) >= 0);
+ assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g/xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5+p8e6itqrIwzecu98+rNyUVDhWBzS0PMwxEw==", &buffer, &size) >= 0);
assert_se(memcmp_nn(plaintext, sizeof(plaintext), buffer, size) == 0);
buffer = mfree(buffer);
/* This is the same but in base64url */
- assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g_xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5-p8e6itqrIwzecu98-rNyUVDhWBzS0PMwxEw==", SIZE_MAX, &buffer, &size) >= 0);
+ assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g_xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5-p8e6itqrIwzecu98-rNyUVDhWBzS0PMwxEw==", &buffer, &size) >= 0);
assert_se(memcmp_nn(plaintext, sizeof(plaintext), buffer, size) == 0);
/* Hint: use xxd -i to generate the static C array from some data, and basenc --base64 + basenc
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "iovec-util.h"
+#include "tests.h"
+
+TEST(iovec_memcmp) {
+ struct iovec iov1 = CONST_IOVEC_MAKE_STRING("abcdef"), iov2 = IOVEC_MAKE_STRING("bcdefg"), empty = {};
+
+ struct iovec iov1_truncated = iov1;
+ iov1_truncated.iov_len /= 2;
+
+ assert_se(iovec_memcmp(NULL, NULL) == 0);
+ assert_se(iovec_memcmp(&iov1, &iov1) == 0);
+ assert_se(iovec_memcmp(&iov2, &iov2) == 0);
+ assert_se(iovec_memcmp(&empty, &empty) == 0);
+ assert_se(iovec_memcmp(&iov1_truncated, &iov1_truncated) == 0);
+ assert_se(iovec_memcmp(&empty, NULL) == 0);
+ assert_se(iovec_memcmp(NULL, &empty) == 0);
+ assert_se(iovec_memcmp(&iov1, &iov2) < 0);
+ assert_se(iovec_memcmp(&iov2, &iov1) > 0);
+ assert_se(iovec_memcmp(&iov1, &empty) > 0);
+ assert_se(iovec_memcmp(&empty, &iov1) < 0);
+ assert_se(iovec_memcmp(&iov2, &empty) > 0);
+ assert_se(iovec_memcmp(&empty, &iov2) < 0);
+ assert_se(iovec_memcmp(&iov1_truncated, &empty) > 0);
+ assert_se(iovec_memcmp(&empty, &iov1_truncated) < 0);
+ assert_se(iovec_memcmp(&iov1, &iov1_truncated) > 0);
+ assert_se(iovec_memcmp(&iov1_truncated, &iov1) < 0);
+ assert_se(iovec_memcmp(&iov2, &iov1_truncated) > 0);
+ assert_se(iovec_memcmp(&iov1_truncated, &iov2) < 0);
+
+ _cleanup_(iovec_done) struct iovec copy = {};
+
+ assert_se(iovec_memdup(&iov1, ©));
+ assert_se(iovec_memcmp(&iov1, ©) == 0);
+}
+
+TEST(iovec_set_and_valid) {
+ struct iovec empty = {},
+ filled = CONST_IOVEC_MAKE_STRING("waldo"),
+ half = { .iov_base = (char*) "piff", .iov_len = 0 },
+ invalid = { .iov_base = NULL, .iov_len = 47 };
+
+ assert_se(!iovec_is_set(NULL));
+ assert_se(!iovec_is_set(&empty));
+ assert_se(iovec_is_set(&filled));
+ assert_se(!iovec_is_set(&half));
+ assert_se(!iovec_is_set(&invalid));
+
+ assert_se(iovec_is_valid(NULL));
+ assert_se(iovec_is_valid(&empty));
+ assert_se(iovec_is_valid(&filled));
+ assert_se(iovec_is_valid(&half));
+ assert_se(!iovec_is_valid(&invalid));
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
+#include "iovec-util.h"
#include "json-internal.h"
#include "json.h"
#include "math-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
assert_se(json_variant_equal(v, w));
s = mfree(s);
- r = json_variant_format(w, JSON_FORMAT_REFUSE_SENSITIVE, &s);
- assert_se(r == -EPERM);
- assert_se(!s);
+ r = json_variant_format(w, JSON_FORMAT_CENSOR_SENSITIVE, &s);
+ assert_se(s);
+ assert_se(streq_ptr(s, "\"<sensitive data>\""));
s = mfree(s);
r = json_variant_format(w, JSON_FORMAT_PRETTY, &s);
assert_se(foobar.l == INT16_MIN);
}
+typedef enum mytestenum {
+ myfoo, mybar, mybaz, _mymax, _myinvalid = -EINVAL,
+} mytestenum;
+
+static const char *mytestenum_table[_mymax] = {
+ [myfoo] = "myfoo",
+ [mybar] = "mybar",
+ [mybaz] = "mybaz",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(mytestenum, mytestenum);
+
+static JSON_DISPATCH_ENUM_DEFINE(dispatch_mytestenum, mytestenum, mytestenum_from_string);
+
+TEST(json_dispatch_enum_define) {
+
+ struct data {
+ mytestenum a, b, c, d;
+ } data = {
+ .a = _myinvalid,
+ .b = _myinvalid,
+ .c = _myinvalid,
+ .d = mybar,
+ };
+
+ _cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
+
+ assert_se(json_build(&j, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("a", JSON_BUILD_STRING("mybaz")),
+ JSON_BUILD_PAIR("b", JSON_BUILD_STRING("mybar")),
+ JSON_BUILD_PAIR("c", JSON_BUILD_STRING("myfoo")),
+ JSON_BUILD_PAIR("d", JSON_BUILD_NULL))) >= 0);
+
+ assert_se(json_dispatch(j,
+ (const JsonDispatch[]) {
+ { "a", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, a), 0 },
+ { "b", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, b), 0 },
+ { "c", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, c), 0 },
+ { "d", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, d), 0 },
+ {},
+ },
+ /* flags= */ 0,
+ &data) >= 0);
+
+ assert(data.a == mybaz);
+ assert(data.b == mybar);
+ assert(data.c == myfoo);
+ assert(data.d < 0);
+}
+
TEST(json_sensitive) {
_cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL, *v = NULL;
_cleanup_free_ char *s = NULL;
json_variant_sensitive(a);
- assert_se(json_variant_format(a, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
- assert_se(!s);
+ assert_se(json_variant_format(a, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+ assert_se(streq_ptr(s, "\"<sensitive data>\""));
+ s = mfree(s);
- r = json_variant_format(b, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ r = json_variant_format(b, JSON_FORMAT_CENSOR_SENSITIVE, &s);
assert_se(r >= 0);
assert_se(s);
assert_se((size_t) r == strlen(s));
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
- r = json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ r = json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s);
assert_se(r >= 0);
assert_se(s);
assert_se((size_t) r == strlen(s));
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
- r = json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ r = json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s);
assert_se(r >= 0);
assert_se(s);
assert_se((size_t) r == strlen(s));
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
- assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
- assert_se(!s);
+ assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+ assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"a\":\"<sensitive data>\",\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"e\":{}}"));
+ s = mfree(s);
v = json_variant_unref(v);
assert_se(json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
- assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
- assert_se(!s);
+ assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+ assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"a\":\"<sensitive data>\",\"d\":\"-9223372036854775808\",\"e\":{}}"));
+ s = mfree(s);
v = json_variant_unref(v);
assert_se(json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
- assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
- assert_se(!s);
+ assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+ assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"a\":\"<sensitive data>\",\"e\":{}}"));
+ s = mfree(s);
v = json_variant_unref(v);
assert_se(json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_VARIANT("a", a))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
- assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
- assert_se(!s);
+ assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
+ assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"e\":{},\"a\":\"<sensitive data>\"}"));
+}
+
+TEST(json_iovec) {
+ struct iovec iov1 = CONST_IOVEC_MAKE_STRING("üxknürz"), iov2 = CONST_IOVEC_MAKE_STRING("wuffwuffmiau");
+
+ _cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
+ assert_se(json_build(&j, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("nr1", JSON_BUILD_IOVEC_BASE64(&iov1)),
+ JSON_BUILD_PAIR("nr2", JSON_BUILD_IOVEC_HEX(&iov2)))) >= 0);
+
+ json_variant_dump(j, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, /* f= */ NULL, /* prefix= */ NULL);
+
+ _cleanup_(iovec_done) struct iovec a = {}, b = {};
+ assert_se(json_variant_unbase64_iovec(json_variant_by_key(j, "nr1"), &a) >= 0);
+ assert_se(json_variant_unhex_iovec(json_variant_by_key(j, "nr2"), &b) >= 0);
+
+ assert_se(iovec_memcmp(&iov1, &a) == 0);
+ assert_se(iovec_memcmp(&iov2, &b) == 0);
+ assert_se(iovec_memcmp(&iov2, &a) < 0);
+ assert_se(iovec_memcmp(&iov1, &b) > 0);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
TEST(valid_items) {
test_valid_item("any", AF_UNSPEC, 0, 0, 0);
+ test_valid_item("0-65535", AF_UNSPEC, 0, 0, 0);
test_valid_item("ipv4", AF_INET, 0, 0, 0);
test_valid_item("ipv6", AF_INET6, 0, 0, 0);
test_valid_item("ipv4:any", AF_INET, 0, 0, 0);
test_valid_item("udp", AF_UNSPEC, IPPROTO_UDP, 0, 0);
test_valid_item("tcp:any", AF_UNSPEC, IPPROTO_TCP, 0, 0);
test_valid_item("udp:any", AF_UNSPEC, IPPROTO_UDP, 0, 0);
+ test_valid_item("0", AF_UNSPEC, 0, 1, 0);
test_valid_item("6666", AF_UNSPEC, 0, 1, 6666);
test_valid_item("6666-6667", AF_UNSPEC, 0, 2, 6666);
test_valid_item("65535", AF_UNSPEC, 0, 1, 65535);
test_valid_item("ipv6:tcp:6666", AF_INET6, IPPROTO_TCP, 1, 6666);
test_valid_item("ipv6:udp:6666-6667", AF_INET6, IPPROTO_UDP, 2, 6666);
test_valid_item("ipv6:tcp:any", AF_INET6, IPPROTO_TCP, 0, 0);
+ test_valid_item("ipv6:tcp:0", AF_INET6, IPPROTO_TCP, 1, 0);
}
TEST(invalid_items) {
test_invalid_item("ipv6::");
test_invalid_item("ipv6:ipv6");
test_invalid_item("ipv6:icmp");
- test_invalid_item("ipv6:tcp:0");
test_invalid_item("65536");
- test_invalid_item("0-65535");
test_invalid_item("ipv6:tcp:6666-6665");
test_invalid_item("ipv6:tcp:6666-100000");
test_invalid_item("ipv6::6666");
test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%lo#hoge.com", AF_INET6, 53, 1, "hoge.com", "[fe80::18]:53%1#hoge.com");
}
+TEST(netns_get_nsid) {
+ uint32_t u;
+ int r;
+
+ r = netns_get_nsid(-EBADF, &u);
+ assert_se(r == -ENODATA || r >= 0);
+ if (r == -ENODATA)
+ log_info("Our network namespace has no NSID assigned.");
+ else
+ log_info("Our NSID is %" PRIu32, u);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("knurz", "", "waldo")), ""));
}
+TEST(strv_extend_many) {
+ _cleanup_strv_free_ char **l = NULL;
+
+ assert_se(strv_extend_many(&l, NULL) >= 0);
+ assert_se(strv_isempty(l));
+
+ assert_se(strv_extend_many(&l, NULL, NULL, NULL) >= 0);
+ assert_se(strv_isempty(l));
+
+ assert_se(strv_extend_many(&l, "foo") >= 0);
+ assert_se(strv_equal(l, STRV_MAKE("foo")));
+
+ assert_se(strv_extend_many(&l, NULL, "bar", NULL) >= 0);
+ assert_se(strv_equal(l, STRV_MAKE("foo", "bar")));
+
+ assert_se(strv_extend_many(&l, "waldo", "quux") >= 0);
+ assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux")));
+
+ assert_se(strv_extend_many(&l, "1", "2", "3", "4") >= 0);
+ assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux", "1", "2", "3", "4")));
+
+ assert_se(strv_extend_many(&l, "yes", NULL, "no") >= 0);
+ assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux", "1", "2", "3", "4", "yes", "no")));
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
assert_se(memcmp_nn(fp, fp_size, expected, expected_len) == 0);
}
-TEST(tpm2b_public_from_openssl_pkey) {
- TPM2B_PUBLIC public;
+static void check_tpm2b_public_name(const TPM2B_PUBLIC *public, const char *hexname) {
+ DEFINE_HEX_PTR(expected, hexname);
+ TPM2B_NAME name = {};
+
+ assert_se(tpm2_calculate_pubkey_name(&public->publicArea, &name) >= 0);
+ assert_se(memcmp_nn(name.name, name.size, expected, expected_len) == 0);
+}
+
+static void check_tpm2b_public_from_ecc_pem(const char *pem, const char *hexx, const char *hexy, const char *hexfp, const char *hexname) {
+ TPM2B_PUBLIC public = {};
TPMT_PUBLIC *p = &public.publicArea;
- DEFINE_HEX_PTR(key_ecc, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a");
- get_tpm2b_public_from_pem(key_ecc, key_ecc_len, &public);
+ DEFINE_HEX_PTR(key, pem);
+ get_tpm2b_public_from_pem(key, key_len, &public);
assert_se(p->type == TPM2_ALG_ECC);
assert_se(p->parameters.eccDetail.curveID == TPM2_ECC_NIST_P256);
- DEFINE_HEX_PTR(expected_x, "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de");
+ DEFINE_HEX_PTR(expected_x, hexx);
assert_se(memcmp_nn(p->unique.ecc.x.buffer, p->unique.ecc.x.size, expected_x, expected_x_len) == 0);
- DEFINE_HEX_PTR(expected_y, "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5");
+ DEFINE_HEX_PTR(expected_y, hexy);
assert_se(memcmp_nn(p->unique.ecc.y.buffer, p->unique.ecc.y.size, expected_y, expected_y_len) == 0);
- check_tpm2b_public_fingerprint(&public, "cd3373293b62a52b48c12100e80ea9bfd806266ce76893a5ec31cb128052d97c");
+ check_tpm2b_public_fingerprint(&public, hexfp);
+ check_tpm2b_public_name(&public, hexname);
+}
+
+static void check_tpm2b_public_from_rsa_pem(const char *pem, const char *hexn, uint32_t exponent, const char *hexfp, const char *hexname) {
+ TPM2B_PUBLIC public = {};
+ TPMT_PUBLIC *p = &public.publicArea;
+
+ DEFINE_HEX_PTR(key, pem);
+ get_tpm2b_public_from_pem(key, key_len, &public);
- DEFINE_HEX_PTR(key_rsa, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a");
- get_tpm2b_public_from_pem(key_rsa, key_rsa_len, &public);
+ assert_se(p->type == TPM2_ALG_RSA);
- DEFINE_HEX_PTR(expected_n, "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b");
- assert_se(p->unique.rsa.size == expected_n_len);
- assert_se(memcmp(p->unique.rsa.buffer, expected_n, expected_n_len) == 0);
+ DEFINE_HEX_PTR(expected_n, hexn);
+ assert_se(memcmp_nn(p->unique.rsa.buffer, p->unique.rsa.size, expected_n, expected_n_len) == 0);
assert_se(p->parameters.rsaDetail.keyBits == expected_n_len * 8);
- assert_se(p->parameters.rsaDetail.exponent == 0);
+ assert_se(p->parameters.rsaDetail.exponent == exponent);
- check_tpm2b_public_fingerprint(&public, "d9186d13a7fd5b3644cee05448f49ad3574e82a2942ff93cf89598d36cca78a9");
+ check_tpm2b_public_fingerprint(&public, hexfp);
+ check_tpm2b_public_name(&public, hexname);
+}
+
+TEST(tpm2b_public_from_openssl_pkey) {
+ /* standard ECC key */
+ check_tpm2b_public_from_ecc_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a",
+ "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de",
+ "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5",
+ "cd3373293b62a52b48c12100e80ea9bfd806266ce76893a5ec31cb128052d97c",
+ "000b5c127e4dbaf8fb7bac641e8db25a84a48db876ca7ee3bd317ae1a4554ff72f17");
+
+ /* standard RSA key */
+ check_tpm2b_public_from_rsa_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a",
+ "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b",
+ 0x10001,
+ "d9186d13a7fd5b3644cee05448f49ad3574e82a2942ff93cf89598d36cca78a9",
+ "000be1bd75c7976e7a30e9e82223b81a9eff0d42c30618e588db592ed5da94455e81");
+
+ /* RSA key with non-default (i.e. not 0x10001) exponent */
+ check_tpm2b_public_from_rsa_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b434151454179566c7551664b75565171596a5a71436a657a760a364e4a6f58654c736f702f72765375666330773769544d4f73566741557462515452505451725874397065537a4370524467634378656b6a544144577279304b0a6d59786a7a3634776c6a7030463959383068636a6b6b4b3759414d333054664c4648656c2b377574427370777142467a6e2b385a6659567353434b397354706f0a316c61376e5347514e7451576f36444a366c525a336a676d6d584f61544654416145304a432b7046584273564471736d46326438362f314e51714a755a5154520a575852636954704e58357649792f37766b6c5a6a685569526c78764e594f4e3070636476534a37364e74496e447a3048506f775a38705a454f4d2f4a454f59780a617a4c4a6a644936446b355279593578325a7949375074566a3057537242524f4d696f2b674c6556457a43343456336438315a38445138564e334c69625130330a70514944415141460a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a",
+ "c9596e41f2ae550a988d9a828decefe8d2685de2eca29febbd2b9f734c3b89330eb1580052d6d04d13d342b5edf69792cc2a510e0702c5e9234c00d6af2d0a998c63cfae30963a7417d63cd217239242bb600337d137cb1477a5fbbbad06ca70a811739fef197d856c4822bdb13a68d656bb9d219036d416a3a0c9ea5459de382699739a4c54c0684d090bea455c1b150eab2617677cebfd4d42a26e6504d159745c893a4d5f9bc8cbfeef925663854891971bcd60e374a5c76f489efa36d2270f3d073e8c19f2964438cfc910e6316b32c98dd23a0e4e51c98e71d99c88ecfb558f4592ac144e322a3e80b7951330b8e15dddf3567c0d0f153772e26d0d37a5",
+ 0x10005,
+ "c8ca80a687d5972e1d961aaa2cfde2ff2e7a20d85e3ea0382804e70e013d65af",
+ "000beb8974d36d8cf58fdc87460dda00319e10c94c1b9f222ac9ce29d1c4776246cc");
}
#endif
assert_se(asprintf(&secret_string, "The classified documents are in room %x", parent_index) > 0);
size_t secret_size = strlen(secret_string) + 1;
- _cleanup_free_ void *blob = NULL;
- size_t blob_size = 0;
- _cleanup_free_ void *serialized_parent = NULL;
- size_t serialized_parent_size;
+ _cleanup_(iovec_done) struct iovec blob = {}, serialized_parent = {};
assert_se(tpm2_calculate_seal(
parent_index,
parent_public,
/* attributes= */ NULL,
- secret_string, secret_size,
+ &IOVEC_MAKE(secret_string, secret_size),
/* policy= */ NULL,
/* pin= */ NULL,
- /* ret_secret= */ NULL, /* ret_secret_size= */ 0,
- &blob, &blob_size,
- &serialized_parent, &serialized_parent_size) >= 0);
+ /* ret_secret= */ NULL,
+ &blob,
+ &serialized_parent) >= 0);
- _cleanup_free_ void *unsealed_secret = NULL;
- size_t unsealed_secret_size;
+ _cleanup_(iovec_done) struct iovec unsealed_secret = {};
assert_se(tpm2_unseal(
c,
/* hash_pcr_mask= */ 0,
/* pcr_bank= */ 0,
- /* pubkey= */ NULL, /* pubkey_size= */ 0,
+ /* pubkey= */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature= */ NULL,
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
/* primary_alg= */ 0,
- blob, blob_size,
- /* known_policy_hash= */ NULL, /* known_policy_hash_size= */ 0,
- serialized_parent, serialized_parent_size,
- &unsealed_secret, &unsealed_secret_size) >= 0);
+ &blob,
+ /* known_policy_hash= */ NULL,
+ &serialized_parent,
+ &unsealed_secret) >= 0);
- assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret, unsealed_secret_size) == 0);
+ assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret.iov_base, unsealed_secret.iov_len) == 0);
- char unsealed_string[unsealed_secret_size];
- assert_se(snprintf(unsealed_string, unsealed_secret_size, "%s", (char*) unsealed_secret) == (int) unsealed_secret_size - 1);
+ char unsealed_string[unsealed_secret.iov_len];
+ assert_se(snprintf(unsealed_string, unsealed_secret.iov_len, "%s", (char*) unsealed_secret.iov_base) == (int) unsealed_secret.iov_len - 1);
log_debug("Unsealed secret is: %s", unsealed_string);
}
log_debug("Check seal/unseal for handle 0x%" PRIx32, handle);
- _cleanup_free_ void *secret = NULL, *blob = NULL, *srk = NULL, *unsealed_secret = NULL;
- size_t secret_size, blob_size, srk_size, unsealed_secret_size;
+ _cleanup_(iovec_done) struct iovec secret = {}, blob = {}, srk = {}, unsealed_secret = {};
assert_se(tpm2_seal(
c,
handle,
&policy,
/* pin= */ NULL,
- &secret, &secret_size,
- &blob, &blob_size,
+ &secret,
+ &blob,
/* ret_primary_alg= */ NULL,
- &srk, &srk_size) >= 0);
+ &srk) >= 0);
assert_se(tpm2_unseal(
c,
/* hash_pcr_mask= */ 0,
/* pcr_bank= */ 0,
- /* pubkey= */ NULL, /* pubkey_size= */ 0,
+ /* pubkey= */ NULL,
/* pubkey_pcr_mask= */ 0,
/* signature= */ NULL,
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
/* primary_alg= */ 0,
- blob, blob_size,
- /* policy_hash= */ NULL, /* policy_hash_size= */ 0,
- srk, srk_size,
- &unsealed_secret, &unsealed_secret_size) >= 0);
+ &blob,
+ /* policy_hash= */ NULL,
+ &srk,
+ &unsealed_secret) >= 0);
- assert_se(memcmp_nn(secret, secret_size, unsealed_secret, unsealed_secret_size) == 0);
+ assert_se(iovec_memcmp(&secret, &unsealed_secret) == 0);
}
static void check_seal_unseal(Tpm2Context *c) {
#include "fs-util.h"
#include "tests.h"
#include "tmpfile-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
static void test_read_login_defs_one(const char *path) {
log_info("/* %s(\"%s\") */", __func__, path ?: "<custom>");
#include "virt.h"
TEST(uid_range) {
- _cleanup_(uid_range_freep) UidRange *p = NULL;
+ _cleanup_(uid_range_freep) UIDRange *p = NULL;
uid_t search;
assert_se(uid_range_covers(p, 0, 0));
}
TEST(load_userns) {
- _cleanup_(uid_range_freep) UidRange *p = NULL;
+ _cleanup_(uid_range_freep) UIDRange *p = NULL;
_cleanup_(unlink_and_freep) char *fn = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
}
TEST(uid_range_coalesce) {
- _cleanup_(uid_range_freep) UidRange *p = NULL;
+ _cleanup_(uid_range_freep) UIDRange *p = NULL;
for (size_t i = 0; i < 10; i++) {
assert_se(uid_range_add_internal(&p, i * 10, 10, /* coalesce = */ false) >= 0);
#include "clock-util.h"
#include "conf-files.h"
#include "constants.h"
+#include "daemon-util.h"
#include "fd-util.h"
#include "fileio-label.h"
#include "fileio.h"
umask(0022);
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
-
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
(void) sd_event_set_watchdog(event, true);
- r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to install SIGINT handler: %m");
-
- r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
+ r = sd_event_set_signal_exit(event, true);
if (r < 0)
- return log_error_errno(r, "Failed to install SIGTERM handler: %m");
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
r = connect_bus(&context, event, &bus);
if (r < 0)
if (r < 0)
return r;
+ r = sd_notify(false, NOTIFY_READY);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
+
r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
if (r < 0)
return r;
- if (addr.sa.sa_family == AF_INET)
- (void) setsockopt_int(m->server_socket, IPPROTO_IP, IP_TOS, IPTOS_LOWDELAY);
+ (void) socket_set_option(m->server_socket, addr.sa.sa_family, IP_TOS, IPV6_TCLASS, IPTOS_DSCP_EF);
return sd_event_add_io(m->event, &m->event_receive, m->server_socket, EPOLLIN, manager_receive_response, m);
}
#include "log.h"
#include "macro.h"
#include "main-func.h"
-#include "missing_stat.h"
#include "missing_syscall.h"
#include "mkdir-label.h"
#include "mount-util.h"
#include "set.h"
#include "sort-util.h"
#include "specifier.h"
+#include "stat-util.h"
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
Set *unix_sockets;
} Context;
-STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep);
-STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
if (r < 0)
return r;
- r = strv_extend(&res, persistent_config);
- if (r < 0)
- return r;
-
- r = strv_extend(&res, runtime_config);
- if (r < 0)
- return r;
-
- r = strv_extend(&res, data_home);
+ r = strv_extend_many(
+ &res,
+ persistent_config,
+ runtime_config,
+ data_home);
if (r < 0)
return r;
static bool needs_glob(ItemType t) {
return IN_SET(t,
WRITE_FILE,
- IGNORE_PATH,
- IGNORE_DIRECTORY_PATH,
- REMOVE_PATH,
- RECURSIVE_REMOVE_PATH,
EMPTY_DIRECTORY,
- ADJUST_MODE,
- RELABEL_PATH,
- RECURSIVE_RELABEL_PATH,
SET_XATTR,
RECURSIVE_SET_XATTR,
SET_ACL,
RECURSIVE_SET_ACL,
SET_ATTRIBUTE,
- RECURSIVE_SET_ATTRIBUTE);
+ RECURSIVE_SET_ATTRIBUTE,
+ IGNORE_PATH,
+ IGNORE_DIRECTORY_PATH,
+ REMOVE_PATH,
+ RECURSIVE_REMOVE_PATH,
+ RELABEL_PATH,
+ RECURSIVE_RELABEL_PATH,
+ ADJUST_MODE);
}
static bool takes_ownership(ItemType t) {
CREATE_FILE,
TRUNCATE_FILE,
CREATE_DIRECTORY,
- EMPTY_DIRECTORY,
TRUNCATE_DIRECTORY,
CREATE_SUBVOLUME,
CREATE_SUBVOLUME_INHERIT_QUOTA,
CREATE_BLOCK_DEVICE,
COPY_FILES,
WRITE_FILE,
+ EMPTY_DIRECTORY,
IGNORE_PATH,
IGNORE_DIRECTORY_PATH,
REMOVE_PATH,
return xopendirat_nomod(AT_FDCWD, path);
}
-static nsec_t load_statx_timestamp_nsec(const struct statx_timestamp *ts) {
- assert(ts);
-
- if (ts->tv_sec < 0)
- return NSEC_INFINITY;
-
- if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
- return NSEC_INFINITY;
-
- return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec;
-}
-
static bool needs_cleanup(
nsec_t atime,
nsec_t btime,
}
}
- atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? load_statx_timestamp_nsec(&sx.stx_atime) : 0;
- mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? load_statx_timestamp_nsec(&sx.stx_mtime) : 0;
- ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? load_statx_timestamp_nsec(&sx.stx_ctime) : 0;
- btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? load_statx_timestamp_nsec(&sx.stx_btime) : 0;
+ atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0;
+ mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : 0;
+ ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : 0;
+ btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : 0;
sub_path = path_join(p, de->d_name);
if (!sub_path) {
}
return dir_cleanup(c, i, instance, d,
- load_statx_timestamp_nsec(&sx.stx_atime),
- load_statx_timestamp_nsec(&sx.stx_mtime),
+ statx_timestamp_load_nsec(&sx.stx_atime),
+ statx_timestamp_load_nsec(&sx.stx_mtime),
cutoff * NSEC_PER_USEC,
sx.stx_dev_major, sx.stx_dev_minor, mountpoint,
MAX_DEPTH, i->keep_first_level,
switch (i->type) {
case CREATE_DIRECTORY:
+ case TRUNCATE_DIRECTORY:
case CREATE_SUBVOLUME:
case CREATE_SUBVOLUME_INHERIT_QUOTA:
case CREATE_SUBVOLUME_NEW_QUOTA:
- case TRUNCATE_DIRECTORY:
- case IGNORE_PATH:
case COPY_FILES:
clean_item_instance(c, i, i->path, CREATION_EXISTING);
return 0;
case EMPTY_DIRECTORY:
+ case IGNORE_PATH:
case IGNORE_DIRECTORY_PATH:
return glob_item(c, i, clean_item_instance);
_cleanup_free_ void *data = NULL;
size_t data_size = 0;
- r = unbase64mem(item_binary_argument(&i), item_binary_argument_size(&i), &data, &data_size);
+ r = unbase64mem_full(item_binary_argument(&i), item_binary_argument_size(&i), /* secure = */ false,
+ &data, &data_size);
if (r < 0)
return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument);
* likely over-mounted if the root directory is actually used, and it wouldbe less than ideal to have
* all kinds of files created/adjusted underneath these mount points. */
- r = strv_extend_strv(
+ r = strv_extend_many(
&arg_exclude_prefixes,
- STRV_MAKE("/dev",
- "/proc",
- "/run",
- "/sys"),
- true);
+ "/dev",
+ "/proc",
+ "/run",
+ "/sys");
if (r < 0)
return log_oom();
+ strv_uniq(arg_exclude_prefixes);
return 0;
}
break;
case ARG_PREFIX:
- if (strv_push(&arg_include_prefixes, optarg) < 0)
+ if (strv_extend(&arg_include_prefixes, optarg) < 0)
return log_oom();
break;
case ARG_EXCLUDE_PREFIX:
- if (strv_push(&arg_exclude_prefixes, optarg) < 0)
+ if (strv_extend(&arg_exclude_prefixes, optarg) < 0)
return log_oom();
break;
* non-zero with errno set.
*/
static int disk_identify(int fd,
- uint8_t out_identify[512]) {
+ uint8_t out_identify[512],
+ int *ret_peripheral_device_type) {
uint8_t inquiry_buf[36];
int peripheral_device_type, r;
if (all_nul_bytes)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "IDENTIFY data is all zeroes.");
+ if (ret_peripheral_device_type)
+ *ret_peripheral_device_type = peripheral_device_type;
+
return 0;
}
char model[41], model_enc[256], serial[21], revision[9];
_cleanup_close_ int fd = -EBADF;
uint16_t word;
- int r;
+ int r, peripheral_device_type = -1;
log_set_target(LOG_TARGET_AUTO);
udev_parse_config();
if (fd < 0)
return log_error_errno(errno, "Cannot open %s: %m", arg_device);
- if (disk_identify(fd, identify.byte) >= 0) {
+ if (disk_identify(fd, identify.byte, &peripheral_device_type) >= 0) {
/*
* fix up only the fields from the IDENTIFY data that we are going to
* use and copy it into the hd_driveid struct for convenience
if (IN_SET(identify.wyde[0], 0x848a, 0x844a) ||
(identify.wyde[83] & 0xc004) == 0x4004)
printf("ID_ATA_CFA=1\n");
+
+ if (peripheral_device_type >= 0)
+ printf("ID_ATA_PERIPHERAL_DEVICE_TYPE=%d\n", peripheral_device_type);
} else {
if (serial[0] != '\0')
printf("%s_%s\n", model, serial);
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(LinkConfig, conditions)
Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(LinkConfig, conditions)
Link.Description, config_parse_string, 0, offsetof(LinkConfig, description)
+Link.Property, config_parse_udev_property, 0, offsetof(LinkConfig, properties)
+Link.ImportProperty, config_parse_udev_property_name, 0, offsetof(LinkConfig, import_properties)
+Link.UnsetProperty, config_parse_udev_property_name, 0, offsetof(LinkConfig, unset_properties)
Link.MACAddressPolicy, config_parse_mac_address_policy, 0, offsetof(LinkConfig, mac_address_policy)
Link.MACAddress, config_parse_hw_addr, 0, offsetof(LinkConfig, hw_addr)
Link.NamePolicy, config_parse_name_policy, 0, offsetof(LinkConfig, name_policy)
#include "creds-util.h"
#include "device-private.h"
#include "device-util.h"
+#include "env-util.h"
+#include "escape.h"
#include "ethtool-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "random-util.h"
+#include "specifier.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
+#include "udev-builtin.h"
#include "utf8.h"
+static const Specifier link_specifier_table[] = {
+ COMMON_SYSTEM_SPECIFIERS,
+ COMMON_TMP_SPECIFIERS,
+ {}
+};
+
struct LinkConfigContext {
LIST_HEAD(LinkConfig, configs);
int ethtool_fd;
condition_free_list(config->conditions);
free(config->description);
+ strv_free(config->properties);
+ strv_free(config->import_properties);
+ strv_free(config->unset_properties);
free(config->name_policy);
free(config->name);
strv_free(config->alternative_names);
return NULL;
sd_device_unref(link->device);
+ sd_device_unref(link->device_db_clone);
free(link->kind);
strv_free(link->altnames);
return mfree(link);
}
-int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link **ret) {
+int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, sd_device *device_db_clone, Link **ret) {
_cleanup_(link_freep) Link *link = NULL;
int r;
assert(ctx);
assert(rtnl);
assert(device);
+ assert(device_db_clone);
assert(ret);
link = new(Link, 1);
*link = (Link) {
.device = sd_device_ref(device),
+ .device_db_clone = sd_device_ref(device_db_clone),
};
r = sd_device_get_sysname(device, &link->ifname);
return 0;
}
-int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link) {
- int r;
+static int link_apply_udev_properties(Link *link, bool test) {
+ LinkConfig *config;
+ sd_device *device;
- assert(ctx);
- assert(rtnl);
assert(link);
- if (!IN_SET(link->action, SD_DEVICE_ADD, SD_DEVICE_BIND, SD_DEVICE_MOVE)) {
- log_link_debug(link, "Skipping to apply .link settings on '%s' uevent.",
- device_action_to_string(link->action));
+ config = ASSERT_PTR(link->config);
+ device = ASSERT_PTR(link->device);
- link->new_name = link->ifname;
- return 0;
+ /* 1. apply ImportProperty=. */
+ STRV_FOREACH(p, config->import_properties)
+ (void) udev_builtin_import_property(device, link->device_db_clone, test, *p);
+
+ /* 2. apply Property=. */
+ STRV_FOREACH(p, config->properties) {
+ _cleanup_free_ char *key = NULL;
+ const char *eq;
+
+ eq = strchr(*p, '=');
+ if (!eq)
+ continue;
+
+ key = strndup(*p, eq - *p);
+ if (!key)
+ return log_oom();
+
+ (void) udev_builtin_add_property(device, test, key, eq + 1);
}
+ /* 3. apply UnsetProperty=. */
+ STRV_FOREACH(p, config->unset_properties)
+ (void) udev_builtin_add_property(device, test, *p, NULL);
+
+ /* 4. set the default properties. */
+ (void) udev_builtin_add_property(device, test, "ID_NET_LINK_FILE", config->filename);
+
+ _cleanup_free_ char *joined = NULL;
+ STRV_FOREACH(d, config->dropins) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = xescape(*d, ":");
+ if (!escaped)
+ return log_oom();
+
+ if (!strextend_with_separator(&joined, ":", escaped))
+ return log_oom();
+ }
+
+ (void) udev_builtin_add_property(device, test, "ID_NET_LINK_FILE_DROPINS", joined);
+
+ if (link->new_name)
+ (void) udev_builtin_add_property(device, test, "ID_NET_NAME", link->new_name);
+
+ return 0;
+}
+
+int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, bool test) {
+ int r;
+
+ assert(ctx);
+ assert(rtnl);
+ assert(link);
+
r = link_apply_ethtool_settings(link, &ctx->ethtool_fd);
if (r < 0)
return r;
if (r < 0)
return r;
+ r = link_apply_udev_properties(link, test);
+ if (r < 0)
+ return r;
+
return 0;
}
+int config_parse_udev_property(
+ 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) {
+
+ char ***properties = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ *properties = strv_free(*properties);
+ return 0;
+ }
+
+ for (const char *p = rvalue;; ) {
+ _cleanup_free_ char *word = NULL, *resolved = NULL, *key = NULL;
+ const char *eq;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid syntax, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = specifier_printf(word, SIZE_MAX, link_specifier_table, NULL, NULL, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve specifiers in %s, ignoring assignment: %m", word);
+ continue;
+ }
+
+ /* The restriction for udev property is not clear. Let's apply the one for environment variable here. */
+ if (!env_assignment_is_valid(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property, ignoring assignment: %s", word);
+ continue;
+ }
+
+ assert_se(eq = strchr(resolved, '='));
+ key = strndup(resolved, eq - resolved);
+ if (!key)
+ return log_oom();
+
+ if (!device_property_can_set(key)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name '%s', ignoring assignment: %s", key, resolved);
+ continue;
+ }
+
+ r = strv_env_replace_consume(properties, TAKE_PTR(resolved));
+ if (r < 0)
+ return log_error_errno(r, "Failed to update properties: %m");
+ }
+}
+
+int config_parse_udev_property_name(
+ 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) {
+
+ char ***properties = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ *properties = strv_free(*properties);
+ return 0;
+ }
+
+ for (const char *p = rvalue;; ) {
+ _cleanup_free_ char *word = NULL, *resolved = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid syntax, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = specifier_printf(word, SIZE_MAX, link_specifier_table, NULL, NULL, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve specifiers in %s, ignoring assignment: %m", word);
+ continue;
+ }
+
+ /* The restriction for udev property is not clear. Let's apply the one for environment variable here. */
+ if (!env_name_is_valid(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name, ignoring assignment: %s", resolved);
+ continue;
+ }
+
+ if (!device_property_can_set(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name, ignoring assignment: %s", resolved);
+ continue;
+ }
+
+ r = strv_consume(properties, TAKE_PTR(resolved));
+ if (r < 0)
+ return log_error_errno(r, "Failed to update properties: %m");
+ }
+}
+
int config_parse_ifalias(
const char *unit,
const char *filename,
LinkConfig *config;
sd_device *device;
+ sd_device *device_db_clone;
sd_device_action_t action;
char *kind;
LIST_HEAD(Condition, conditions);
char *description;
+ char **properties;
+ char **import_properties;
+ char **unset_properties;
struct hw_addr_data hw_addr;
MACAddressPolicy mac_address_policy;
NamePolicy *name_policy;
int link_config_load(LinkConfigContext *ctx);
bool link_config_should_reload(LinkConfigContext *ctx);
-int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link **ret);
+int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, sd_device *device_db_clone, Link **ret);
Link *link_free(Link *link);
DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free);
int link_get_config(LinkConfigContext *ctx, Link *link);
-int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link);
+int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, bool test);
const char *mac_address_policy_to_string(MACAddressPolicy p) _const_;
MACAddressPolicy mac_address_policy_from_string(const char *p) _pure_;
/* gperf lookup function */
const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+CONFIG_PARSER_PROTOTYPE(config_parse_udev_property);
+CONFIG_PARSER_PROTOTYPE(config_parse_udev_property_name);
CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
CONFIG_PARSER_PROTOTYPE(config_parse_rx_tx_queues);
CONFIG_PARSER_PROTOTYPE(config_parse_txqueuelen);
case 0xf:
type = "optical";
break;
+ case 0x14:
+ /*
+ * Use "zbc" here to be brief and consistent with "lsscsi" command.
+ * Other tools, e.g., "sg3_utils" would say "host managed zoned block".
+ */
+ type = "zbc";
+ break;
default:
type = "generic";
break;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
+#include "device-private.h"
#include "device-util.h"
-#include "escape.h"
#include "errno-util.h"
#include "link-config.h"
#include "log.h"
static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool test) {
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
_cleanup_(link_freep) Link *link = NULL;
- _cleanup_free_ char *joined = NULL;
int r;
if (argc > 1)
return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
- r = link_new(ctx, &event->rtnl, dev, &link);
+ sd_device_action_t action;
+ r = sd_device_get_action(dev, &action);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to get action: %m");
+
+ if (!IN_SET(action, SD_DEVICE_ADD, SD_DEVICE_BIND, SD_DEVICE_MOVE)) {
+ log_device_debug(dev, "Skipping to apply .link settings on '%s' uevent.",
+ device_action_to_string(action));
+
+ /* Import previously assigned .link file name. */
+ (void) udev_builtin_import_property(dev, event->dev_db_clone, test, "ID_NET_LINK_FILE");
+ (void) udev_builtin_import_property(dev, event->dev_db_clone, test, "ID_NET_LINK_FILE_DROPINS");
+
+ /* Set ID_NET_NAME= with the current interface name. */
+ const char *value;
+ if (sd_device_get_sysname(dev, &value) >= 0)
+ (void) udev_builtin_add_property(dev, test, "ID_NET_NAME", value);
+
+ return 0;
+ }
+
+ r = link_new(ctx, &event->rtnl, dev, event->dev_db_clone, &link);
if (r == -ENODEV) {
log_device_debug_errno(dev, r, "Link vanished while getting information, ignoring.");
return 0;
return log_device_error_errno(dev, r, "Failed to get link config: %m");
}
- r = link_apply_config(ctx, &event->rtnl, link);
+ r = link_apply_config(ctx, &event->rtnl, link, test);
if (r == -ENODEV)
log_device_debug_errno(dev, r, "Link vanished while applying configuration, ignoring.");
else if (r < 0)
log_device_warning_errno(dev, r, "Could not apply link configuration, ignoring: %m");
- udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE", link->config->filename);
- if (link->new_name)
- udev_builtin_add_property(dev, test, "ID_NET_NAME", link->new_name);
-
event->altnames = TAKE_PTR(link->altnames);
- STRV_FOREACH(d, link->config->dropins) {
- _cleanup_free_ char *escaped = NULL;
-
- escaped = xescape(*d, ":");
- if (!escaped)
- return log_oom();
-
- if (!strextend_with_separator(&joined, ":", escaped))
- return log_oom();
- }
-
- udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE_DROPINS", joined);
-
return 0;
}
return NULL;
/* Check if we are simple disk */
- if (strncmp(phy_count, "1", 2) != 0)
+ if (!streq(phy_count, "1"))
return handle_scsi_sas_wide_port(parent, path);
/* Get connected phy */
static void add_id_with_usb_revision(sd_device *dev, bool test, char *path) {
char *p;
- int r;
assert(dev);
assert(path);
if (p[1] != '-')
return;
- r = udev_builtin_add_property(dev, test, "ID_PATH_WITH_USB_REVISION", path);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH_WITH_USB_REVISION property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_WITH_USB_REVISION", path);
/* Drop the USB revision specifier for backward compatibility. */
memmove(p - 1, p + 1, strlen(p + 1) + 1);
static void add_id_tag(sd_device *dev, bool test, const char *path) {
char tag[UDEV_NAME_SIZE];
size_t i = 0;
- int r;
/* compose valid udev tag name */
for (const char *p = path; *p; p++) {
i--;
tag[i] = '\0';
- r = udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH_TAG property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
}
static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) {
add_id_with_usb_revision(dev, test, path);
- r = udev_builtin_add_property(dev, test, "ID_PATH", path);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH", path);
add_id_tag(dev, test, path);
* ID_PATH_ATA_COMPAT
*/
if (compat_path)
- udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path);
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path);
return 0;
}
return udev_builtin_add_property(dev, test, key, val);
}
+
+int udev_builtin_import_property(sd_device *dev, sd_device *src, bool test, const char *key) {
+ const char *val;
+ int r;
+
+ assert(dev);
+ assert(key);
+
+ if (!src)
+ return 0;
+
+ r = sd_device_get_property_value(src, key, &val);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_device_debug_errno(src, r, "Failed to get property \"%s\", ignoring: %m", key);
+
+ r = udev_builtin_add_property(dev, test, key, val);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
bool udev_builtin_should_reload(void);
int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val);
int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const char *valf, ...) _printf_(4, 5);
+int udev_builtin_import_property(sd_device *dev, sd_device *src, bool test, const char *key);
int udev_builtin_hwdb_lookup(sd_device *dev, const char *prefix, const char *modalias,
const char *filter, bool test);
static usec_t extra_timeout_usec(void) {
static usec_t saved = 10 * USEC_PER_SEC;
static bool parsed = false;
+ usec_t timeout;
const char *e;
int r;
if (!e)
return saved;
- r = parse_sec(e, &saved);
+ r = parse_sec(e, &timeout);
if (r < 0)
log_debug_errno(r, "Failed to parse $SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC=%s, ignoring: %m", e);
+ if (timeout > 5 * USEC_PER_HOUR) /* Add an arbitrary upper bound */
+ log_debug("Parsed $SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC=%s is too large, ignoring.", e);
+ else
+ saved = timeout;
+
return saved;
}
}
if (!is_match) {
- if (STR_IN_SET(attr,
- "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER",
- "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS"))
+ if (!device_property_can_set(attr))
return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
"Invalid ENV attribute. '%s' cannot be set.", attr);
#include <stdio.h>
#include <stdlib.h>
+#include "device-private.h"
+#include "device-util.h"
#include "log.h"
#include "udev-builtin.h"
#include "udevadm.h"
int r;
log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
}
+ if (arg_action != SD_DEVICE_REMOVE) {
+ /* For net_setup_link */
+ r = device_clone_with_db(dev, &event->dev_db_clone);
+ if (r < 0) {
+ log_device_error_errno(dev, r, "Failed to clone device: %m");
+ goto finish;
+ }
+ }
+
r = udev_builtin_run(event, cmd, arg_command, true);
if (r < 0) {
log_debug_errno(r, "Builtin command '%s' fails: %m", arg_command);
int r;
log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
r = parse_argv(argc, argv);
if (r <= 0)
#include "sd-daemon.h"
+#include "conf-parser.h"
#include "env-file.h"
#include "errno-util.h"
#include "fd-util.h"
return 0;
}
+static DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_name_timing, resolve_name_timing, ResolveNameTiming, "Failed to parse resolve name timing");
+
static int manager_parse_udev_config(Manager *manager) {
- _cleanup_free_ char *log_val = NULL, *children_max = NULL, *exec_delay = NULL,
- *event_timeout = NULL, *resolve_names = NULL, *timeout_signal = NULL;
- int r;
+ int r, log_val = -1;
assert(manager);
- r = parse_env_file(NULL, "/etc/udev/udev.conf",
- "udev_log", &log_val,
- "children_max", &children_max,
- "exec_delay", &exec_delay,
- "event_timeout", &event_timeout,
- "resolve_names", &resolve_names,
- "timeout_signal", &timeout_signal);
- if (r == -ENOENT)
- return 0;
- if (r < 0)
- return r;
+ const ConfigTableItem config_table[] = {
+ { NULL, "udev_log", config_parse_log_level, 0, &log_val },
+ { NULL, "children_max", config_parse_unsigned, 0, &manager->children_max },
+ { NULL, "exec_delay", config_parse_sec, 0, &manager->exec_delay_usec },
+ { NULL, "event_timeout", config_parse_sec, 0, &manager->timeout_usec },
+ { NULL, "resolve_names", config_parse_resolve_name_timing, 0, &manager->resolve_name_timing },
+ { NULL, "timeout_signal", config_parse_signal, 0, &manager->timeout_signal },
+ {}
+ };
- r = udev_set_max_log_level(log_val);
+ r = udev_parse_config_full(config_table);
if (r < 0)
- log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r,
- "Failed to set udev log level '%s', ignoring: %m", log_val);
-
- if (children_max) {
- r = safe_atou(children_max, &manager->children_max);
- if (r < 0)
- log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r,
- "Failed to parse children_max=%s, ignoring: %m", children_max);
- }
-
- if (exec_delay) {
- r = parse_sec(exec_delay, &manager->exec_delay_usec);
- if (r < 0)
- log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r,
- "Failed to parse exec_delay=%s, ignoring: %m", exec_delay);
- }
-
- if (event_timeout) {
- r = parse_sec(event_timeout, &manager->timeout_usec);
- if (r < 0)
- log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r,
- "Failed to parse event_timeout=%s, ignoring: %m", event_timeout);
- }
-
- if (resolve_names) {
- ResolveNameTiming t;
-
- t = resolve_name_timing_from_string(resolve_names);
- if (t < 0)
- log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r,
- "Failed to parse resolve_names=%s, ignoring.", resolve_names);
- else
- manager->resolve_name_timing = t;
- }
+ return r;
- if (timeout_signal) {
- r = signal_from_string(timeout_signal);
- if (r < 0)
- log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r,
- "Failed to parse timeout_signal=%s, ignoring: %m", timeout_signal);
- else
- manager->timeout_signal = r;
- }
+ if (log_val >= 0)
+ log_set_max_level(log_val);
return 0;
}
# supported/expected:
# https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-secure-boot-key-creation-and-management-guidance?view=windows-11#12-public-key-cryptography
- now = datetime.datetime.now(datetime.UTC)
+ now = datetime.datetime.now(datetime.timezone.utc)
key = rsa.generate_private_key(
public_exponent=65537,
},
};
-static int table_add_uid_boundaries(Table *table, const UidRange *p) {
+static int table_add_uid_boundaries(Table *table, const UIDRange *p) {
int r;
assert(table);
static int table_add_uid_map(
Table *table,
- const UidRange *p,
+ const UIDRange *p,
int (*add_unavailable)(Table *t, uid_t start, uid_t end)) {
uid_t focus = 0;
assert(add_unavailable);
for (size_t i = 0; p && i < p->n_entries; i++) {
- UidRangeEntry *x = p->entries + i;
+ UIDRangeEntry *x = p->entries + i;
if (focus < x->start) {
r = add_unavailable(table, focus, x->start-1);
}
if (table) {
- _cleanup_(uid_range_freep) UidRange *uid_range = NULL;
+ _cleanup_(uid_range_freep) UIDRange *uid_range = NULL;
int boundary_lines, uid_map_lines;
r = uid_range_load_userns(&uid_range, "/proc/self/uid_map");
return 0;
}
-static int table_add_gid_boundaries(Table *table, const UidRange *p) {
+static int table_add_gid_boundaries(Table *table, const UIDRange *p) {
int r;
assert(table);
}
if (table) {
- _cleanup_(uid_range_freep) UidRange *gid_range = NULL;
+ _cleanup_(uid_range_freep) UIDRange *gid_range = NULL;
int boundary_lines, gid_map_lines;
r = uid_range_load_userns(&gid_range, "/proc/self/gid_map");
#include "sd-daemon.h"
#include "common-signal.h"
+#include "env-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "mkdir.h"
if (r < 0)
return log_error_errno(r, "Failed to fork new worker child: %m");
if (r == 0) {
- char pids[DECIMAL_STR_MAX(pid_t)];
/* Child */
if (m->listen_fd == 3) {
safe_close(m->listen_fd);
}
- xsprintf(pids, PID_FMT, pid);
- if (setenv("LISTEN_PID", pids, 1) < 0) {
- log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
+ r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid);
+ if (r < 0) {
+ log_error_errno(r, "Failed to set $LISTEN_PID: %m");
_exit(EXIT_FAILURE);
}
if (*data_what && *hash_what)
return 0;
- r = unhexmem(hash, strlen(hash), &m, &l);
+ r = unhexmem(hash, &m, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse hash: %s", hash);
if (l < sizeof(sd_id128_t)) {
size_t l;
void *m;
- r = unhexmem(val, strlen(val), &m, &l);
+ r = unhexmem(val, &m, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse salt '%s': %m", word);
if (!filename_is_valid(volume))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
- r = unhexmem(root_hash, SIZE_MAX, &m, &l);
+ r = unhexmem(root_hash, &m, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash: %m");
char *value;
if ((value = startswith(arg_root_hash_signature, "base64:"))) {
- r = unbase64mem(value, strlen(value), (void *)&hash_sig, &hash_sig_size);
+ r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature);
} else {
{}
};
- return json_dispatch(v, table, 0, userdata);
+ return json_dispatch(v, table, flags, userdata);
}
static int firmware_nvram_template(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
{}
};
- return json_dispatch(v, table, 0, userdata);
+ return json_dispatch(v, table, flags, userdata);
}
static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
{}
};
- return json_dispatch(v, table, 0, userdata);
+ return json_dispatch(v, table, flags, userdata);
}
int find_ovmf_config(int search_sb, OvmfConfig **ret) {
if (!fwd)
return -ENOMEM;
- r = json_dispatch(config_json, table, 0, fwd);
+ r = json_dispatch(config_json, table, JSON_ALLOW_EXTENSIONS, fwd);
if (r == -ENOMEM)
return r;
if (r < 0) {
continue;
}
- int sb_present = !!strv_find(fwd->features, "secure-boot");
+ if (strv_contains(fwd->features, "enrolled-keys")) {
+ log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues", *file);
+ continue;
+ }
+
+ bool sb_present = strv_contains(fwd->features, "secure-boot");
/* exclude firmware which doesn't match our Secure Boot requirements */
if (search_sb >= 0 && search_sb != sb_present) {
#define ARCHITECTURE_SUPPORTS_SMBIOS 0
#endif
+#if defined(__arm__) || defined(__aarch64__)
+#define DEFAULT_SERIAL_TTY "ttyAMA0"
+#elif defined(__s390__) || defined(__s390x__)
+#define DEFAULT_SERIAL_TTY "ttysclp0"
+#elif defined(__powerpc__) || defined(__powerpc64__)
+#define DEFAULT_SERIAL_TTY "hvc0"
+#else
+#define DEFAULT_SERIAL_TTY "ttyS0"
+#endif
+
typedef struct OvmfConfig {
char *path;
char *vars;
static uint64_t arg_qemu_mem = 2ULL * 1024ULL * 1024ULL * 1024ULL;
static int arg_qemu_kvm = -1;
static int arg_qemu_vsock = -1;
-static uint64_t arg_vsock_cid = UINT64_MAX;
+static unsigned arg_vsock_cid = VMADDR_CID_ANY;
static bool arg_qemu_gui = false;
static int arg_secure_boot = -1;
static MachineCredentialContext arg_credentials = {};
"%5$sSpawn a command or OS in a virtual machine.%6$s\n\n"
" -h --help Show this help\n"
" --version Print version string\n"
- " --no-pager Do not pipe output into a pager\n\n"
- "%3$sImage:%4$s\n"
+ " --no-pager Do not pipe output into a pager\n"
+ "\n%3$sImage:%4$s\n"
" -i --image=PATH Root file system disk image (or device node) for\n"
- " the virtual machine\n\n"
- "%3$sHost Configuration:%4$s\n"
+ " the virtual machine\n"
+ "\n%3$sHost Configuration:%4$s\n"
" --qemu-smp=SMP Configure guest's SMP settings\n"
" --qemu-mem=MEM Configure guest's RAM size\n"
" --qemu-kvm=BOOL Configure whether to use KVM or not\n"
" --vsock-cid= Specify the CID to use for the qemu guest's vsock\n"
" --qemu-gui Start QEMU in graphical mode\n"
" --secure-boot=BOOL Configure whether to search for firmware which\n"
- " supports Secure Boot\n\n"
- "%3$sSystem Identity:%4$s\n"
+ " supports Secure Boot\n"
+ "\n%3$sSystem Identity:%4$s\n"
" -M --machine=NAME Set the machine name for the container\n"
- "%3$sCredentials:%4$s\n"
+ "\n%3$sCredentials:%4$s\n"
" --set-credential=ID:VALUE\n"
" Pass a credential with literal value to container.\n"
" --load-credential=ID:PATH\n"
return log_error_errno(r, "Failed to parse --qemu-vsock=%s: %m", optarg);
break;
- case ARG_VSOCK_CID: {
- unsigned cid;
+ case ARG_VSOCK_CID:
if (isempty(optarg))
- cid = VMADDR_CID_ANY;
+ arg_vsock_cid = VMADDR_CID_ANY;
else {
- r = safe_atou_bounded(optarg, 3, UINT_MAX - 1, &cid);
- if (r == -ERANGE)
- return log_error_errno(r, "Invalid value for --vsock-cid=: %m");
+ unsigned cid;
+
+ r = vsock_parse_cid(optarg, &cid);
if (r < 0)
- return log_error_errno(r, "Failed to parse --vsock-cid=%s: %m", optarg);
+ return log_error_errno(r, "Failed to parse --vsock-cid: %s", optarg);
+ if (!VSOCK_CID_IS_REGULAR(cid))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified CID is not regular, refusing: %u", cid);
+
+ arg_vsock_cid = cid;
}
- arg_vsock_cid = (uint64_t)cid;
break;
- }
case ARG_QEMU_GUI:
arg_qemu_gui = true;
unsigned child_cid = VMADDR_CID_ANY;
_cleanup_close_ int child_vsock_fd = -EBADF;
if (use_vsock) {
- if (arg_vsock_cid < UINT_MAX)
- child_cid = (unsigned)arg_vsock_cid;
+ child_cid = arg_vsock_cid;
r = vsock_fix_child_cid(&child_cid, arg_machine, &child_vsock_fd);
if (r < 0)
return log_oom();
}
- r = strv_extend_strv(&cmdline, STRV_MAKE("-cpu", "max"), /* filter_duplicates= */ false);
+ r = strv_extend_many(&cmdline, "-cpu", "max");
if (r < 0)
return log_oom();
- if (arg_qemu_gui) {
- r = strv_extend_strv(&cmdline, STRV_MAKE("-vga", "virtio"), /* filter_duplicates= */ false);
- if (r < 0)
- return log_oom();
- } else {
- r = strv_extend_strv(&cmdline, STRV_MAKE(
- "-nographic",
- "-nodefaults",
- "-chardev", "stdio,mux=on,id=console,signal=off",
- "-serial", "chardev:console",
- "-mon", "console"
- ), /* filter_duplicates= */ false);
- if (r < 0)
- return log_oom();
- }
+ if (arg_qemu_gui)
+ r = strv_extend_many(
+ &cmdline,
+ "-vga",
+ "virtio");
+ else
+ r = strv_extend_many(
+ &cmdline,
+ "-nographic",
+ "-nodefaults",
+ "-chardev", "stdio,mux=on,id=console,signal=off",
+ "-serial", "chardev:console",
+ "-mon", "console");
+ if (r < 0)
+ return log_oom();
if (ARCHITECTURE_SUPPORTS_SMBIOS)
FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
(void) copy_access(source_fd, target_fd);
(void) copy_times(source_fd, target_fd, 0);
- r = strv_extend_strv(&cmdline, STRV_MAKE(
- "-global", "ICH9-LPC.disable_s3=1",
- "-global", "driver=cfi.pflash01,property=secure,value=on",
- "-drive"
- ), /* filter_duplicates= */ false);
+ r = strv_extend_many(
+ &cmdline,
+ "-global", "ICH9-LPC.disable_s3=1",
+ "-global", "driver=cfi.pflash01,property=secure,value=on",
+ "-drive");
if (r < 0)
return log_oom();
if (r < 0)
return log_oom();
- r = strv_extend_strv(&cmdline, STRV_MAKE(
- "-device", "virtio-scsi-pci,id=scsi",
- "-device", "scsi-hd,drive=mkosi,bootindex=1"
- ), /* filter_duplicates= */ false);
+ r = strv_extend_many(
+ &cmdline,
+ "-device", "virtio-scsi-pci,id=scsi",
+ "-device", "scsi-hd,drive=mkosi,bootindex=1");
if (r < 0)
return log_oom();
- if (!strv_isempty(arg_parameters)) {
- if (ARCHITECTURE_SUPPORTS_SMBIOS) {
- _cleanup_free_ char *kcl = strv_join(arg_parameters, " ");
- if (!kcl)
- return log_oom();
+ r = strv_prepend(&arg_parameters, "console=" DEFAULT_SERIAL_TTY);
+ if (r < 0)
+ return log_oom();
- r = strv_extend(&cmdline, "-smbios");
- if (r < 0)
- return log_oom();
+ if (ARCHITECTURE_SUPPORTS_SMBIOS) {
+ _cleanup_free_ char *kcl = strv_join(arg_parameters, " ");
+ if (!kcl)
+ return log_oom();
- r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl);
- if (r < 0)
- return log_oom();
- } else
- log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS");
- }
+ r = strv_extend(&cmdline, "-smbios");
+ if (r < 0)
+ return log_oom();
+
+ r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl);
+ if (r < 0)
+ return log_oom();
+ } else
+ log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS");
if (use_vsock) {
vsock_fd = open_vsock();
_exit(EXIT_FAILURE);
}
-
int exit_status = INT_MAX;
if (use_vsock) {
r = setup_notify_parent(event, vsock_fd, &exit_status, ¬ify_event_source);
}
/* shutdown qemu when we are shutdown */
- (void) sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(child_pid));
- (void) sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(child_pid));
+ (void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, PID_TO_PTR(child_pid));
+ (void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, PID_TO_PTR(child_pid));
- (void) sd_event_add_signal(event, NULL, SIGRTMIN+18, sigrtmin18_handler, NULL);
+ (void) sd_event_add_signal(event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL);
/* Exit when the child exits */
(void) sd_event_add_child(event, NULL, child_pid, WEXITED, on_child_exit, NULL);
int r;
if (!arg_image)
- return log_error_errno(SYNTHETIC_ERRNO(-EINVAL), "Missing required argument -i/--image=, quitting");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing required argument -i/--image=, quitting");
if (!arg_machine) {
char *e;
if (r < 0)
return r;
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
return run_virtual_machine();
}
#include "format-table.h"
#include "fs-util.h"
#include "main-func.h"
-#include "path-util.h"
#include "parse-util.h"
+#include "path-util.h"
#include "pretty-print.h"
#include "stat-util.h"
#include "string-util.h"
ddebug "Support for p11-kit is disabled, skipping the PKCS#11 test"
return 1
fi
+ if ! "${SYSTEMCTL:?}" --version | grep -q "+OPENSSL"; then
+ ddebug "Support for openssl is disabled, skipping the PKCS#11 test"
+ return 1
+ fi
if ! "${SYSTEMCTL:?}" --version | grep -q "+LIBCRYPTSETUP\b"; then
ddebug "Support for libcryptsetup is disabled, skipping the PKCS#11 test"
return 1
# (Hopefully) a temporary workaround for https://github.com/systemd/systemd/issues/30573
KERNEL_APPEND="${KERNEL_APPEND:-} SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST=100"
+# Make sure vsock is available in the VM
+CID=$((RANDOM + 3))
+QEMU_OPTIONS+=" -device vhost-vsock-pci,guest-cid=$CID"
+
test_append_files() {
local workspace="${1:?}"
install_mdadm
generate_module_dependencies
fi
+
+ inst_binary socat
+ inst_binary ssh
+ inst_binary sshd
+ inst_binary ssh-keygen
+ inst_binary usermod
+ instmods vmw_vsock_virtio_transport
+ instmods vsock_loopback
+ instmods vmw_vsock_vmci_transport
+ generate_module_dependencies
}
do_test "$@"
address: fd00:dead:beef:cafe::/64
action: update
+ - id: transfer_acl
+ address: 10.0.0.0/24
+ address: fd00:dead:beef:cafe::/64
+ action: transfer
+
remote:
- id: parent_zone_server
address: 10.0.0.1@53
address: fd00:dead:beef:cafe::1@53
+ - id: forwarded
+ address: 10.99.0.1@53
+
submission:
- id: parent_zone_sbm
check-interval: 2s
parent: [parent_zone_server]
-# Auto ZSK/KSK rollover for DNSSEC-enabled zones + pushing the respective DS
-# records to the parent zone
policy:
+ # Auto ZSK/KSK rollover for DNSSEC-enabled zones + pushing the respective DS
+ # records to the parent zone
- id: auto_rollover
algorithm: ECDSAP256SHA256
cds-cdnskey-publish: always
zone-max-ttl: 1s
zsk-lifetime: 60d
-# Same as auto_rollover, but with NSEC3 turned on
-policy:
+ # Same as auto_rollover, but with NSEC3 turned on
- id: auto_rollover_nsec3
algorithm: ECDSAP256SHA256
cds-cdnskey-publish: always
zone-max-ttl: 1s
zsk-lifetime: 60d
-policy:
- id: untrusted
cds-cdnskey-publish: none
-# Manual ZSK/KSK management
-policy:
+ # Manual ZSK/KSK management
- id: manual
manual: on
-# Sign everything by default and propagate the respective DS records to the parent
+mod-dnsproxy:
+ - id: forwarded
+ remote: forwarded
+ fallback: off
+
template:
+ # Sign everything by default and propagate the respective DS records to the parent
- id: default
acl: update_acl
dnssec-policy: auto_rollover
semantic-checks: on
storage: "/var/lib/knot/zones"
-# A template for unsigned zones (i.e. without DNSSEC)
-template:
+ # A template for unsigned zones (i.e. without DNSSEC)
- id: unsigned
dnssec-signing: off
file: "%s.zone"
semantic-checks: on
storage: "/var/lib/knot/zones"
+ - id: forwarded
+ dnssec-signing: off
+ module: mod-dnsproxy/forwarded
+ zonefile-load: none
+
zone:
# Create our own DNSSEC-aware root zone, so we can test the whole chain of
# trust. This needs a ZSK/KSK keypair to be generated before running knot +
- domain: test
dnssec-policy: auto_rollover_nsec3
- # A fully (pre-)signed zone
+ # A fully (pre-)signed zone with allowed zone transfers (AXFR/IXFR)
- domain: signed.test
+ acl: [update_acl, transfer_acl]
# A fully (online)-signed zone
# See: https://www.knot-dns.cz/docs/3.1/singlehtml/index.html#mod-onlinesign
# An unsigned zone
- domain: unsigned.test
template: unsigned
+
+ # Forward all queries for this zone to our dummy test server
+ - domain: forwarded.test
+ template: forwarded
Description=Test for AmbientCapabilities (dynamic user)
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002081"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002081"'
Type=oneshot
AmbientCapabilities=CAP_CHOWN CAP_SETUID CAP_NET_RAW
DynamicUser=yes
Description=Test for AmbientCapabilities
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=nfsnobody
AmbientCapabilities=CAP_CHOWN
Description=Test for AmbientCapabilities
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=nobody
AmbientCapabilities=CAP_CHOWN
Description=Test for AmbientCapabilities (daemon)
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=daemon
AmbientCapabilities=CAP_CHOWN
Description=Test for AmbientCapabilities
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=nfsnobody
AmbientCapabilities=CAP_CHOWN CAP_NET_RAW
Description=Test for AmbientCapabilities
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=nobody
AmbientCapabilities=CAP_CHOWN CAP_NET_RAW
Description=Test for AmbientCapabilities (daemon)
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
+ExecStart=sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000002001"'
Type=oneshot
User=daemon
AmbientCapabilities=CAP_CHOWN CAP_NET_RAW
# Also, through /tmp/test-exec-bindreadonlypaths
ExecStart=test -f /tmp/test-exec-bindreadonlypaths/thisisasimpletest
# The file cannot modify through /tmp/test-exec-bindreadonlypaths
-ExecStart=/bin/sh -x -c '! touch /tmp/test-exec-bindreadonlypaths/thisisasimpletest'
+ExecStart=sh -x -c '! touch /tmp/test-exec-bindreadonlypaths/thisisasimpletest'
# Cleanup
ExecStart=rm /tmp/thisisasimpletest
BindPaths=/tmp:/tmp/test-exec-bindpaths
[Service]
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep "^Bounding set .*cap_chown"'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep "^Bounding set .*cap_chown"'
Type=oneshot
CapabilityBoundingSet=~CAP_CHOWN
Description=Test for CapabilityBoundingSet
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_chown,cap_fowner,cap_kill"'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_chown,cap_fowner,cap_kill"'
Type=oneshot
CapabilityBoundingSet=CAP_FOWNER
CapabilityBoundingSet=CAP_KILL CAP_CHOWN
Description=Test for CapabilityBoundingSet
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set ="'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set ="'
Type=oneshot
CapabilityBoundingSet=CAP_FOWNER CAP_KILL
CapabilityBoundingSet=
Description=Test for CapabilityBoundingSet
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_fowner,cap_kill"'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_fowner,cap_kill"'
Type=oneshot
CapabilityBoundingSet=CAP_FOWNER CAP_KILL
ExecCondition=/bin/sh -c 'exit 255'
# This should not get run
-ExecStart=/bin/sh -c 'true'
+ExecStart=sh -c 'true'
ExecCondition=/bin/sh -c 'exit 255'
# This should not get run
-ExecStart=/bin/sh -c 'true'
+ExecStart=sh -c 'true'
Description=Test for CPUAffinity (simple)
[Service]
-ExecStart=/bin/sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 1'
+ExecStart=sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 1'
CPUAffinity=0
Description=Test for CPUAffinity (reset)
[Service]
-ExecStart=/bin/sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 1'
+ExecStart=sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 1'
CPUAffinity=0-1 3
CPUAffinity=
CPUAffinity=0
Description=Test for CPUAffinity (merge)
[Service]
-ExecStart=/bin/sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 7'
+ExecStart=sh -c 'test $$(cat /proc/self/status | grep Cpus_allowed: | rev | cut -c 1) = 7'
CPUAffinity=0,1
CPUAffinity=1-2
[Service]
Type=oneshot
-ExecStart=/bin/sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
+ExecStart=sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
# Multiple ExecStart= lines causes the issue #9702.
-ExecStart=/bin/sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
+ExecStart=sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
DynamicUser=yes
User=adm
[Service]
Type=oneshot
-ExecStart=/bin/sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
+ExecStart=sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
# Multiple ExecStart= lines causes the issue #9702.
-ExecStart=/bin/sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
+ExecStart=sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
DynamicUser=yes
User=games
Description=Test DynamicUser with User= and SupplementaryGroups=
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
Type=oneshot
User=1
DynamicUser=yes
Description=Test DynamicUser with User=
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
Type=oneshot
User=1
DynamicUser=yes
Description=Test for RuntimeDirectory with RuntimeDirectoryPreserve=yes and DynamicUser=yes
[Service]
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
-ExecStart=/bin/sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
+ExecStart=sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectorypreserve
RuntimeDirectoryPreserve=yes
Description=Test for RuntimeDirectory with RuntimeDirectoryPreserve=yes and DynamicUser=yes 2nd trial
[Service]
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
-ExecStart=/bin/sh -x -c 'test -f $$RUNTIME_DIRECTORY/test'
-ExecStart=/bin/sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
+ExecStart=sh -x -c 'test -f $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectorypreserve
RuntimeDirectoryPreserve=yes
Description=Test for RuntimeDirectory with DynamicUser=yes migrated from RuntimeDirectoryPreserve=yes
[Service]
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
-ExecStart=/bin/sh -x -c 'test -f $$RUNTIME_DIRECTORY/test'
-ExecStart=/bin/sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectorypreserve'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectorypreserve"'
+ExecStart=sh -x -c 'test -f $$RUNTIME_DIRECTORY/test'
+ExecStart=sh -x -c 'touch $$RUNTIME_DIRECTORY/test'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectorypreserve
DynamicUser=yes
ExecStart=test -d %S/test-dynamicuser-migrate2/hoge
ExecStart=touch %S/test-dynamicuser-migrate/yay
ExecStart=touch %S/test-dynamicuser-migrate2/hoge/yayyay
-ExecStart=/bin/sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
+ExecStart=sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
Type=oneshot
DynamicUser=no
ExecStart=touch %S/test-dynamicuser-migrate2/hoge/yayyay
ExecStart=touch %S/private/test-dynamicuser-migrate/yay
ExecStart=touch %S/private/test-dynamicuser-migrate2/hoge/yayyay
-ExecStart=/bin/sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
+ExecStart=sh -x -c 'test "$$STATE_DIRECTORY" = "%S/test-dynamicuser-migrate:%S/test-dynamicuser-migrate2/hoge"'
Type=oneshot
DynamicUser=yes
Description=Test DynamicUser with SupplementaryGroups=
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
Type=oneshot
DynamicUser=yes
SupplementaryGroups=1 2
Description=Test for Environment
[Service]
-ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset"'
+ExecStart=sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset"'
Type=oneshot
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
Environment=
Description=Test for Environment
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = foobar'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = foobar'
Type=oneshot
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
Environment="VAR3=foobar"
Description=Test for No Environment Variable Substitution
[Service]
-ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2}" = "word3" && test "$${VAR3-unset}" = \'$word 5 6\''
+ExecStart=sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2}" = "word3" && test "$${VAR3-unset}" = \'$word 5 6\''
ExecStart=:/bin/sh -x -c 'test "$${VAR1-unset}" != "unset" && test "$${VAR2}" != "word3" && test "$${VAR3-unset}" != \'$word 5 6\''
Type=oneshot
Environment="VAR2=word3" "VAR3=$word 5 6"
Description=Test for Environment
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6"'
Type=oneshot
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
Description=Test for EnvironmentFile
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
Type=oneshot
EnvironmentFile=/tmp/test-exec_environmentfile.conf
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/sh -x -c 'test "$$PATH" = "/usr" && test "$$VAR1" = word3 && test "$$VAR2" = "\\$$word 5 6"'
+ExecStart=sh -x -c 'test "$$PATH" = "/usr" && test "$$VAR1" = word3 && test "$$VAR2" = "\\$$word 5 6"'
Type=oneshot
ExecSearchPath=/tmp:/bin
Environment="PATH=/usr" VAR1=word3 "VAR2=$word 5 6"
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$PATH" = "/tmp:/bin"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$PATH" = "/tmp:/bin"'
Type=oneshot
ExecSearchPath=/tmp:/bin
Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
Description=Test for ExecSearchPath with EnvironmentFile where EnvironmentFile sets PATH
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = /usr'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = /usr'
Type=oneshot
EnvironmentFile=/tmp/test-exec_execsearchpath_environmentfile-set.conf
ExecSearchPath=/tmp:/bin
Description=Test for ExecSearchPath with EnvironmentFile where EnvironmentFile does not set PATH
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
Type=oneshot
ExecSearchPath=/tmp:/bin
EnvironmentFile=/tmp/test-exec_execsearchpath_environmentfile.conf
Description=Test for PassEnvironment with ExecSearchPath with PATH set by user
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/usr"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/usr"'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5 PATH
ExecSearchPath=/tmp:/bin
Description=Test for PassEnvironment with ExecSearchPath with PATH not set by user
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
ExecSearchPath=/tmp:/bin
[Service]
Type=oneshot
ExecSearchPath=/tmp:/bin:/usr/bin:%V
-ExecStart=/bin/sh -x -c 'test %V = /var/tmp && test "$$PATH" = "/tmp:/bin:/usr/bin:/var/tmp"'
+ExecStart=sh -x -c 'test %V = /var/tmp && test "$$PATH" = "/tmp:/bin:/usr/bin:/var/tmp"'
Description=Test for Group
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "nfsnobody"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "nfsnobody"'
Type=oneshot
Group=nfsnobody
Description=Test for Group
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "nobody"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "nobody"'
Type=oneshot
Group=nobody
Description=Test for Group
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "nogroup"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "nogroup"'
Type=oneshot
Group=nogroup
Description=Test for Group (daemon)
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "daemon"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "daemon"'
Type=oneshot
Group=daemon
Description=Test for IgnoreSIGPIPE=no
[Service]
-ExecStart=/bin/sh -x -c 'kill -PIPE 0'
+ExecStart=sh -x -c 'kill -PIPE 0'
Type=oneshot
IgnoreSIGPIPE=no
Description=Test for IgnoreSIGPIPE=yes
[Service]
-ExecStart=/bin/sh -x -c 'kill -PIPE 0'
+ExecStart=sh -x -c 'kill -PIPE 0'
Type=oneshot
IgnoreSIGPIPE=yes
[Service]
InaccessiblePaths=-/i-dont-exist
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
Type=oneshot
[Service]
InaccessiblePaths=/sys
-ExecStart=/bin/sh -x -c 'test "$$(stat -c %%a /sys)" = "0"'
+ExecStart=sh -x -c 'test "$$(stat -c %%a /sys)" = "0"'
Type=oneshot
Description=Test for IOSchedulingClass=best-effort
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "best-effort"'
+ExecStart=sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "best-effort"'
Type=oneshot
IOSchedulingClass=best-effort
Description=Test for IOSchedulingClass=idle
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "idle"'
+ExecStart=sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "idle"'
Type=oneshot
IOSchedulingClass=idle
[Service]
# Old kernels might report "none" here, new kernels "best-effort".
-ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "none" -o "$${c%%:*}" = "best-effort"'
+ExecStart=sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "none" -o "$${c%%:*}" = "best-effort"'
Type=oneshot
IOSchedulingClass=none
Description=Test for IOSchedulingClass=realtime
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "realtime"'
+ExecStart=sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "realtime"'
Type=oneshot
IOSchedulingClass=realtime
Description=Test for LoadCredential=
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
-ExecStartPost=/bin/sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
-ExecStop=/bin/sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
-ExecStopPost=/bin/sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
+ExecStart=sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
+ExecStartPost=sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
+ExecStop=sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
+ExecStopPost=sh -x -c 'test "$$(cat %d/test-execute.load-credential)" = "foo"'
Type=oneshot
LoadCredential=test-execute.load-credential
Description=Test for NetworkNamespacePath= without mount namespacing
[Service]
-ExecStart=/bin/sh -x -c '! ip link show dummy-test-exec'
-ExecStart=/bin/sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
+ExecStart=sh -x -c '! ip link show dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
# Without mount namespacing, we can access the dummy-test-exec interface through sysfs.
-ExecStart=/bin/sh -x -c 'test -e /sys/class/net/dummy-test-exec'
-ExecStart=/bin/sh -x -c 'ip link show dummy-test-ns'
-ExecStart=/bin/sh -x -c 'test -e /proc/sys/net/ipv4/conf/dummy-test-ns'
+ExecStart=sh -x -c 'test -e /sys/class/net/dummy-test-exec'
+ExecStart=sh -x -c 'ip link show dummy-test-ns'
+ExecStart=sh -x -c 'test -e /proc/sys/net/ipv4/conf/dummy-test-ns'
# Without mount namespacing, we cannot access the dummy-test-ns interface through sysfs.
-ExecStart=/bin/sh -x -c 'test ! -e /sys/class/net/dummy-test-ns'
+ExecStart=sh -x -c 'test ! -e /sys/class/net/dummy-test-ns'
Type=oneshot
NetworkNamespacePath=/run/netns/test-execute-netns
PrivateMounts=no
Description=Test for NetworkNamespacePath= with mount namespacing
[Service]
-ExecStart=/bin/sh -x -c '! ip link show dummy-test-exec'
-ExecStart=/bin/sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
+ExecStart=sh -x -c '! ip link show dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
# With mount namespacing, we cannot access the dummy-test-exec interface through sysfs.
-ExecStart=/bin/sh -x -c 'test ! -e /sys/class/net/dummy-test-exec'
-ExecStart=/bin/sh -x -c 'ip link show dummy-test-ns'
-ExecStart=/bin/sh -x -c 'test -e /proc/sys/net/ipv4/conf/dummy-test-ns'
+ExecStart=sh -x -c 'test ! -e /sys/class/net/dummy-test-exec'
+ExecStart=sh -x -c 'ip link show dummy-test-ns'
+ExecStart=sh -x -c 'test -e /proc/sys/net/ipv4/conf/dummy-test-ns'
# With mount namespacing, we can access the dummy-test-ns interface through sysfs.
-ExecStart=/bin/sh -x -c 'test -e /sys/class/net/dummy-test-ns'
+ExecStart=sh -x -c 'test -e /sys/class/net/dummy-test-ns'
Type=oneshot
NetworkNamespacePath=/run/netns/test-execute-netns
# NetworkNamespacePath= implies PrivateMounts=yes
# This should work, as we explicitly disable the effect of NoExecPaths=
ExecStart=+/bin/sh -c '/bin/cat /dev/null'
# This should also work, as we do not disable the effect of NoExecPaths= but invert the exit code
-ExecStart=/bin/sh -x -c '! /bin/cat /dev/null'
+ExecStart=sh -x -c '! /bin/cat /dev/null'
NoExecPaths=/bin/cat
Description=Test for OOMScoreAdjust
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq -100'
+ExecStart=sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq -100'
Type=oneshot
OOMScoreAdjust=-100
Description=Test for OOMScoreAdjust
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq 100'
+ExecStart=sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq 100'
Type=oneshot
OOMScoreAdjust=100
Description=Test for PassEnvironment with variables absent from the execution environment
[Service]
-ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset" && test "$${VAR4-unset}" = "unset" && test "$${VAR5-unset}" = "unset"'
+ExecStart=sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset" && test "$${VAR4-unset}" = "unset" && test "$${VAR5-unset}" = "unset"'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
Description=Test for PassEnvironment and erasing the variable list
[Service]
-ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset" && test "$${VAR4-unset}" = "unset" && test "$${VAR5-unset}" = "unset"'
+ExecStart=sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset" && test "$${VAR4-unset}" = "unset" && test "$${VAR5-unset}" = "unset"'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
PassEnvironment=
Description=Test for PassEnvironment with a variable name repeated
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
Type=oneshot
PassEnvironment=VAR1 VAR2
PassEnvironment=VAR1 VAR3
Description=Test for PassEnvironment
[Service]
-ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
+ExecStart=sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes'
Type=oneshot
PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
Description=Test for Personality=aarch64
[Service]
-ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "aarch64")'
+ExecStart=sh -c 'echo $(uname -m); exit $(test $(uname -m) = "aarch64")'
Type=oneshot
Personality=aarch64
Description=Test for Personality=loongarch64
[Service]
-ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "loongarch64")'
+ExecStart=sh -c 'echo $(uname -m); exit $(test $(uname -m) = "loongarch64")'
Type=oneshot
Personality=loongarch64
Description=Test for Personality=ppc64
[Service]
-ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64")'
+ExecStart=sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64")'
Type=oneshot
Personality=ppc64
Description=Test for Personality=ppc64le
[Service]
-ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64le")'
+ExecStart=sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64le")'
Type=oneshot
Personality=ppc64le
Description=Test for Personality=s390
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "s390"'
+ExecStart=sh -x -c 'c=$$(uname -m); test "$$c" = "s390"'
Type=oneshot
Personality=s390
Description=Test for Personality=x86-64
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "x86_64"'
+ExecStart=sh -x -c 'c=$$(uname -m); test "$$c" = "x86_64"'
Type=oneshot
Personality=x86-64
Description=Test for Personality=x86
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "i686" -o "$$c" = "x86_64"'
+ExecStart=sh -x -c 'c=$$(uname -m); test "$$c" = "i686" -o "$$c" = "x86_64"'
Type=oneshot
Personality=x86
Description=Test for PrivateDevices=yes with a bind mounted device
[Service]
-ExecStart=/bin/sh -c 'test -c /dev/kmsg'
-ExecStart=/bin/sh -c 'test ! -w /dev/'
+ExecStart=sh -c 'test -c /dev/kmsg'
+ExecStart=sh -c 'test ! -w /dev/'
Type=oneshot
PrivateDevices=yes
BindPaths=/dev/kmsg
Description=Test for PrivateDevices=yes with prefix
[Service]
-ExecStart=/bin/sh -x -c '! test -c /dev/kmsg'
+ExecStart=sh -x -c '! test -c /dev/kmsg'
ExecStart=+/bin/sh -x -c 'test -c /dev/kmsg'
Type=oneshot
PrivateDevices=yes
[Service]
PrivateDevices=no
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_mknod'
+ExecStart=sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_mknod'
Type=oneshot
[Service]
PrivateDevices=no
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_rawio'
+ExecStart=sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_rawio'
Type=oneshot
Description=Test for PrivateDevices=no
[Service]
-ExecStart=/bin/sh -x -c 'test -c /dev/kmsg'
+ExecStart=sh -x -c 'test -c /dev/kmsg'
Type=oneshot
PrivateDevices=no
[Service]
PrivateDevices=yes
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_mknod'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_mknod'
Type=oneshot
[Service]
PrivateDevices=yes
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_rawio'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_rawio'
Type=oneshot
Type=oneshot
# Check the group applied
-ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "daemon"'
+ExecStart=sh -x -c 'test "$$(id -n -g)" = "daemon"'
# Check that the namespace applied
-ExecStart=/bin/sh -c 'test ! -c /dev/kmsg'
+ExecStart=sh -c 'test ! -c /dev/kmsg'
# Check that the owning group of a node is not daemon (should be the host root)
-ExecStart=/bin/sh -x -c 'test ! "$$(stat -c %%G /dev/stderr)" = "daemon"'
+ExecStart=sh -x -c 'test ! "$$(stat -c %%G /dev/stderr)" = "daemon"'
Description=Test for PrivateDevices=yes
[Service]
-ExecStart=/bin/sh -c 'test ! -c /dev/kmsg'
+ExecStart=sh -c 'test ! -c /dev/kmsg'
Type=oneshot
PrivateDevices=yes
Description=Test for PrivateNetwork= without mount namespacing
[Service]
-ExecStart=/bin/sh -x -c '! ip link show dummy-test-exec'
-ExecStart=/bin/sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
+ExecStart=sh -x -c '! ip link show dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
# Without mount namespacing, we can access the dummy-test-exec interface through sysfs
-ExecStart=/bin/sh -x -c 'test -d /sys/class/net/dummy-test-exec'
+ExecStart=sh -x -c 'test -d /sys/class/net/dummy-test-exec'
Type=oneshot
PrivateNetwork=yes
PrivateMounts=no
Description=Test for PrivateNetwork= with mount namespacing
[Service]
-ExecStart=/bin/sh -x -c '! ip link show dummy-test-exec'
-ExecStart=/bin/sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
+ExecStart=sh -x -c '! ip link show dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /proc/sys/net/ipv4/conf/dummy-test-exec'
# With mount namespacing, we cannot access the dummy-test-exec interface through sysfs.
-ExecStart=/bin/sh -x -c 'test ! -e /sys/class/net/dummy-test-exec'
+ExecStart=sh -x -c 'test ! -e /sys/class/net/dummy-test-exec'
Type=oneshot
PrivateNetwork=yes
# PrivateNetwork=yes implies PrivateMounts=yes
Description=Test for PrivateTmp=yes with prefix
[Service]
-ExecStart=/bin/sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
ExecStart=+/bin/sh -x -c 'test -f /tmp/test-exec_privatetmp'
Type=oneshot
PrivateTmp=yes
Description=Test for PrivateTmp=no
[Service]
-ExecStart=/bin/sh -x -c 'test -f /tmp/test-exec_privatetmp'
+ExecStart=sh -x -c 'test -f /tmp/test-exec_privatetmp'
Type=oneshot
PrivateTmp=no
Description=Test for PrivateTmp=yes
[Service]
-ExecStart=/bin/sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
Type=oneshot
PrivateTmp=yes
ProtectHome=tmpfs
ProtectSystem=strict
Type=oneshot
-ExecStart=/bin/sh -x -c 'test "$$(stat -fc %%T /home)" = "tmpfs"'
+ExecStart=sh -x -c 'test "$$(stat -fc %%T /home)" = "tmpfs"'
[Service]
ProtectKernelLogs=no
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_syslog'
+ExecStart=sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_syslog'
Type=oneshot
[Service]
ProtectKernelLogs=yes
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_syslog'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_syslog'
Type=oneshot
[Service]
ProtectKernelModules=no
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_module'
+ExecStart=sh -x -c 'capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_module'
Type=oneshot
[Service]
ProtectKernelModules=yes
# sed: remove dropped (cap_xxx-[epi]) and IAB capabilities from the output
-ExecStart=/bin/sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_module'
+ExecStart=sh -x -c '! capsh --print | sed -re "s/[^ ]+?\-[epi]+//g" -e '/IAB/d' | grep cap_sys_module'
Type=oneshot
[Service]
ProtectKernelModules=yes
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
Type=oneshot
[Service]
ReadOnlyPaths=-/i-dont-exist
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
Type=oneshot
# This should work, as we explicitly disable the effect of ReadOnlyPaths=
ExecStart=+/bin/sh -c 'touch /tmp/thisisasimpletest'
# This should also work, as we do not disable the effect of ReadOnlyPaths= but invert the exit code
-ExecStart=/bin/sh -x -c '! touch /tmp/thisisasimpletest'
+ExecStart=sh -x -c '! touch /tmp/thisisasimpletest'
ExecStart=+/bin/sh -c 'rm /tmp/thisisasimpletest'
ReadOnlyPaths=/tmp
[Service]
ReadOnlyPaths=/etc -/i-dont-exist /usr
BindPaths=/etc:/tmp/etc2
-ExecStart=/bin/sh -x -c 'test ! -w /etc && test ! -w /usr && test ! -e /i-dont-exist && test -w /var'
+ExecStart=sh -x -c 'test ! -w /etc && test ! -w /usr && test ! -e /i-dont-exist && test -w /var'
Type=oneshot
[Service]
ReadOnlyPaths=/usr /etc /sys /dev -/i-dont-exist
PrivateDevices=yes
-ExecStart=/bin/sh -x -c 'test ! -w /usr && test ! -w /etc && test ! -w /sys && test ! -w /sys/fs/cgroup'
-ExecStart=/bin/sh -x -c 'test ! -w /dev && test ! -w /dev/shm && test ! -e /i-dont-exist && test -w /var'
+ExecStart=sh -x -c 'test ! -w /usr && test ! -w /etc && test ! -w /sys && test ! -w /sys/fs/cgroup'
+ExecStart=sh -x -c 'test ! -w /dev && test ! -w /dev/shm && test ! -e /i-dont-exist && test -w /var'
Type=oneshot
[Service]
ReadWritePaths=-/i-dont-exist
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); trap "umount \'$$d\' && rmdir \'$$d\'" EXIT; mount -t tmpfs tmpfs "$$d"; grep "$$d" /proc/self/mountinfo && ! grep "$$d" /proc/$${PPID}/mountinfo && ! grep "$$d" /proc/1/mountinfo'
Type=oneshot
Description=Test for RuntimeDirectoryMode
[Service]
-ExecStart=/bin/sh -x -c 'mode=$$(stat -c %%a %t/test-exec_runtimedirectory-mode); test "$$mode" = "750"'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectory-mode"'
+ExecStart=sh -x -c 'mode=$$(stat -c %%a %t/test-exec_runtimedirectory-mode); test "$$mode" = "750"'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectory-mode"'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectory-mode
RuntimeDirectoryMode=0750
Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set)
[Service]
-ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nfsnobody"'
+ExecStart=sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nfsnobody"'
Type=oneshot
Group=nfsnobody
User=root
Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set)
[Service]
-ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nobody"'
+ExecStart=sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nobody"'
Type=oneshot
Group=nobody
User=root
Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set)
[Service]
-ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nogroup"'
+ExecStart=sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner); test "$$group" = "nogroup"'
Type=oneshot
Group=nogroup
User=root
Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set)
[Service]
-ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner-daemon); test "$$group" = "daemon"'
+ExecStart=sh -x -c 'group=$$(stat -c %%G %t/test-exec_runtimedirectory-owner-daemon); test "$$group" = "daemon"'
Type=oneshot
Group=daemon
User=root
Description=Test for RuntimeDirectory
[Service]
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectory'
-ExecStart=/bin/sh -x -c 'test -d %t/test-exec_runtimedirectory2/hogehoge'
-ExecStart=/bin/sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectory:%t/test-exec_runtimedirectory2/hogehoge"'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectory'
+ExecStart=sh -x -c 'test -d %t/test-exec_runtimedirectory2/hogehoge'
+ExecStart=sh -x -c 'test "$$RUNTIME_DIRECTORY" = "%t/test-exec_runtimedirectory:%t/test-exec_runtimedirectory2/hogehoge"'
Type=oneshot
RuntimeDirectory=test-exec_runtimedirectory
RuntimeDirectory=./test-exec_runtimedirectory2///./hogehoge/.
Description=Test for SetCredential=
[Service]
-ExecStart=/bin/sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
-ExecStartPost=/bin/sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
-ExecStop=/bin/sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
-ExecStopPost=/bin/sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
+ExecStart=sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
+ExecStartPost=sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
+ExecStop=sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
+ExecStopPost=sh -x -c 'test "$$(cat %d/test-execute.set-credential)" = "hoge"'
Type=oneshot
SetCredential=test-execute.set-credential:hoge
[Service]
Type=oneshot
-ExecStart=/bin/bash -x -c "[[ %%U == ?U ]]"
+ExecStart=bash -x -c "[[ %%U == ?U ]]"
Description=Test for StandardInputText= and StandardInputData=
[Service]
-ExecStart=/bin/sh -x -c 'd=$$(mktemp -d -p /tmp); echo -e "this is a test\nand this is more\nsomething encoded!\nsomething in multiple lines\nand some more\nand a more bas64 data\nsomething with strange\nembedded\tcharacters\nand something with a exec-stdin-data.service specifier" >$d/text ; cmp $d/text ; rm -rf $d'
+ExecStart=sh -x -c 'd=$$(mktemp -d -p /tmp); echo -e "this is a test\nand this is more\nsomething encoded!\nsomething in multiple lines\nand some more\nand a more bas64 data\nsomething with strange\nembedded\tcharacters\nand something with a exec-stdin-data.service specifier" >$d/text ; cmp $d/text ; rm -rf $d'
Type=oneshot
StandardInput=data
StandardInputText=this is a test
Description=Test for Supplementary Group with multiple groups without Group and User
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "%G" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "%G" && test "$$(id -u)" = "%U"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "%G" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "%G" && test "$$(id -u)" = "%U"'
Type=oneshot
SupplementaryGroups=1 2
Description=Test for Supplementary Group with multiple groups and Group=1
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "%U"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "%U"'
Type=oneshot
Group=1
SupplementaryGroups=1 2
Description=Test for Supplementary Group with multiple groups and Uid=1
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "2" && exit 0; done; exit 1'
Type=oneshot
User=1
SupplementaryGroups=1 2
Description=Test for Supplementary Group with only one group and uid 1
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
Type=oneshot
User=1
Group=1
Description=Test for Supplementary Group with only one group
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "0"'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "0"'
Type=oneshot
Group=1
SupplementaryGroups=1
Description=Test for Supplementary Group
[Service]
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "%G" && exit 0; done; exit 1'
-ExecStart=/bin/sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "%G" && exit 0; done; exit 1'
+ExecStart=sh -x -c 'for g in $$(id -G); do test "$$g" = "1" && exit 0; done; exit 1'
Type=oneshot
SupplementaryGroups=1
Description=Test for SystemCallErrorNumber
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname
SystemCallErrorNumber=EACCES
Description=Test for SystemCallErrorNumber
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname
SystemCallErrorNumber=255
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c '/bin/echo "This should not be seen"'
+ExecStart=sh -c '/bin/echo "This should not be seen"'
Type=oneshot
LimitCORE=0
SystemCallFilter=ioperm
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c '/bin/echo "This should not be seen"'
+ExecStart=sh -c '/bin/echo "This should not be seen"'
Type=oneshot
LimitCORE=0
SystemCallFilter=~write open execve fexecve execveat exit_group close mmap munmap fstat DONOTEXIST
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c '/bin/echo "This should not be seen"'
+ExecStart=sh -c '/bin/echo "This should not be seen"'
Type=oneshot
LimitCORE=0
SystemCallArchitectures=native
Description=Test bounding set is right with SystemCallFilter and non-root user
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_net_bind_service"'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_net_bind_service"'
Type=oneshot
User=1
SystemCallFilter=@system-service
Description=Test bounding set is right with SystemCallFilter and non-root user
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_setpcap,cap_net_bind_service,cap_sys_admin"'
+ExecStart=sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_setpcap,cap_net_bind_service,cap_sys_admin"'
Type=oneshot
User=1
SystemCallFilter=@system-service
Description=Test no_new_privs is unset for ProtectClock and non-root user
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/status | grep "NoNewPrivs: "); test "$$c" = "NoNewPrivs: 0"'
+ExecStart=sh -x -c 'c=$$(cat /proc/self/status | grep "NoNewPrivs: "); test "$$c" = "NoNewPrivs: 0"'
Type=oneshot
User=1
ProtectClock=yes
Description=Test no_new_privs is unset for SystemCallFilter and non-root user
[Service]
-ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/status | grep "NoNewPrivs: "); test "$$c" = "NoNewPrivs: 0"'
+ExecStart=sh -x -c 'c=$$(cat /proc/self/status | grep "NoNewPrivs: "); test "$$c" = "NoNewPrivs: 0"'
Type=oneshot
User=1
SystemCallFilter=@system-service
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
SystemCallFilter=~read write open execve ioperm
SystemCallFilter=ioctl
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
SystemCallFilter=
Description=Test for SystemCallFilter
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
SystemCallArchitectures=native
SystemCallFilter=
Description=Test for SystemCallFilter with specific kill action overriding default errno action
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:kill
SystemCallErrorNumber=EILSEQ
Description=Test for SystemCallFilter with specific errno action overriding default kill action
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:EILSEQ
SystemCallErrorNumber=kill
Description=Test for SystemCallFilter in system mode with User set
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
User=nfsnobody
SystemCallFilter=~read write open execve ioperm
Description=Test for SystemCallFilter in system mode with User set
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
User=nobody
SystemCallFilter=~read write open execve ioperm
Description=Test for SystemCallFilter in system mode with User set (daemon)
[Service]
-ExecStart=/bin/sh -c 'echo "Foo bar"'
+ExecStart=sh -c 'echo "Foo bar"'
Type=oneshot
User=daemon
SystemCallFilter=~read write open execve ioperm
Description=Test for SystemCallFilter with errno name (for issue #18916)
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=@system-service
SystemCallFilter=~uname:EILSEQ
# test for issue #9939 which is fixed by a5404992cc7724ebf7572a0aa89d9fdb26ce0b62 (#9942)
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:ENOENT uname:EILSEQ
SystemCallErrorNumber=EACCES
Description=Test for SystemCallFilter with errno name
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:EILSEQ
SystemCallErrorNumber=EACCES
Description=Test for SystemCallFilter with errno number
[Service]
-ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
+ExecStart=python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)'
Type=oneshot
SystemCallFilter=~uname:255
SystemCallErrorNumber=EACCES
TemporaryFileSystem=/var:ro,mode=0700,nostrictatime
# Check /proc/self/mountinfo
-ExecStart=/bin/sh -x -c 'test "$$(awk \'$$5 == "/var" && $$11 !~ /(^|,)mode=700(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
+ExecStart=sh -x -c 'test "$$(awk \'$$5 == "/var" && $$11 !~ /(^|,)mode=700(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
-ExecStart=/bin/sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 !~ /(^|,)ro(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
-ExecStart=/bin/sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 !~ /(^|,)nodev(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
-ExecStart=/bin/sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 ~ /(^|,)strictatime(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
+ExecStart=sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 !~ /(^|,)ro(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
+ExecStart=sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 !~ /(^|,)nodev(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
+ExecStart=sh -x -c 'test "$$(awk \'$$5 == "/var" && $$6 ~ /(^|,)strictatime(,|$$)/ { print $$6 }\' /proc/self/mountinfo)" = ""'
Type=oneshot
# Check directories exist
-ExecStart=/bin/sh -c 'test -d /var/test-exec-temporaryfilesystem/rw && test -d /var/test-exec-temporaryfilesystem/ro'
+ExecStart=sh -c 'test -d /var/test-exec-temporaryfilesystem/rw && test -d /var/test-exec-temporaryfilesystem/ro'
# Check TemporaryFileSystem= are empty
-ExecStart=/bin/sh -c 'for i in $$(ls -A /var); do test $$i = test-exec-temporaryfilesystem || false; done'
+ExecStart=sh -c 'for i in $$(ls -A /var); do test $$i = test-exec-temporaryfilesystem || false; done'
# Check default mode
ExecStart=sh -x -c 'test "$$(stat -c %%a /var)" = "755"'
# Cannot create a file in /var
-ExecStart=/bin/sh -c '! touch /var/hoge'
+ExecStart=sh -c '! touch /var/hoge'
# Create a file in /var/test-exec-temporaryfilesystem/rw
-ExecStart=/bin/sh -c 'touch /var/test-exec-temporaryfilesystem/rw/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c 'touch /var/test-exec-temporaryfilesystem/rw/thisisasimpletest-temporaryfilesystem'
# Then, the file can be access through /tmp
-ExecStart=/bin/sh -c 'test -f /tmp/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c 'test -f /tmp/thisisasimpletest-temporaryfilesystem'
# Also, through /var/test-exec-temporaryfilesystem/ro
-ExecStart=/bin/sh -c 'test -f /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c 'test -f /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
# The file cannot modify through /var/test-exec-temporaryfilesystem/ro
-ExecStart=/bin/sh -c '! touch /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c '! touch /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
# Cleanup
-ExecStart=/bin/sh -c 'rm /tmp/thisisasimpletest-temporaryfilesystem'
+ExecStart=sh -c 'rm /tmp/thisisasimpletest-temporaryfilesystem'
TemporaryFileSystem=/var:ro
BindPaths=/tmp:/var/test-exec-temporaryfilesystem/rw
Type=oneshot
# Check TemporaryFileSystem= are empty
-ExecStart=/bin/sh -c 'for i in $$(ls -A /usr); do test $$i = lib -o $$i = lib64 -o $$i = bin -o $$i = sbin || false; done'
+ExecStart=sh -c 'for i in $$(ls -A /usr); do test $$i = lib -o $$i = lib64 -o $$i = bin -o $$i = sbin || false; done'
# Cannot create files under /usr
-ExecStart=/bin/sh -c '! touch /usr/hoge'
-ExecStart=/bin/sh -c '! touch /usr/bin/hoge'
+ExecStart=sh -c '! touch /usr/hoge'
+ExecStart=sh -c '! touch /usr/bin/hoge'
TemporaryFileSystem=/usr:ro
BindReadOnlyPaths=-/usr/lib -/usr/lib64 /usr/bin /usr/sbin
Description=Test for UMask
[Service]
-ExecStart=/bin/sh -x -c 'rm /tmp/test-exec-umask; touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "600"'
+ExecStart=sh -x -c 'rm /tmp/test-exec-umask; touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "600"'
Type=oneshot
UMask=0177
PrivateTmp=yes
Description=Test for UMask default
[Service]
-ExecStart=/bin/sh -x -c 'rm /tmp/test-exec-umask; touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "644"'
+ExecStart=sh -x -c 'rm /tmp/test-exec-umask; touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "644"'
Type=oneshot
PrivateTmp=yes
Description=Test for UMask= + namespacing
[Service]
-ExecStart=/bin/ls -lahd /tmp/subdir
+ExecStart=ls -lahd /tmp/subdir
Type=oneshot
User=65534
Group=65534
Description=Test for UnsetEnvironment
[Service]
-ExecStart=/bin/sh -x -c 'test "$$FOO" = "bar" && test "$${QUUX-X}" = "X" && test "$$VAR3" = "value3" && test "$${VAR4-X}" = "X" && test "$$VAR5" = "value5" && test "$${X%b-X}" = "X"'
+ExecStart=sh -x -c 'test "$$FOO" = "bar" && test "$${QUUX-X}" = "X" && test "$$VAR3" = "value3" && test "$${VAR4-X}" = "X" && test "$$VAR5" = "value5" && test "$${X%b-X}" = "X"'
Type=oneshot
Environment=FOO=bar QUUX=waldo VAR3=value3 VAR4=value4 VAR5=value5 X%b=%U
UnsetEnvironment=QUUX=waldo VAR3=somethingelse VAR4 X%b=%U
Description=Test for User
[Service]
-ExecStart=/bin/sh -x -c 'test "$$USER" = "nfsnobody"'
+ExecStart=sh -x -c 'test "$$USER" = "nfsnobody"'
Type=oneshot
User=nfsnobody
Description=Test for User
[Service]
-ExecStart=/bin/sh -x -c 'test "$$USER" = "nobody"'
+ExecStart=sh -x -c 'test "$$USER" = "nobody"'
Type=oneshot
User=nobody
Description=Test for User (daemon)
[Service]
-ExecStart=/bin/sh -x -c 'test "$$USER" = "daemon"'
+ExecStart=sh -x -c 'test "$$USER" = "daemon"'
Type=oneshot
User=daemon
Description=Test for WorkingDirectory with trailing dot
[Service]
-ExecStart=/bin/sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"'
+ExecStart=sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"'
Type=oneshot
WorkingDirectory=/tmp///./test-exec_workingdirectory/.
Description=Test for WorkingDirectory
[Service]
-ExecStart=/bin/sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"'
+ExecStart=sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"'
Type=oneshot
WorkingDirectory=/tmp/test-exec_workingdirectory
SYSTEMD_NSPAWN="${SYSTEMD_NSPAWN:-$(command -v "$BUILD_DIR/systemd-nspawn" || command -v systemd-nspawn)}"
JOURNALCTL="${JOURNALCTL:-$(command -v "$BUILD_DIR/journalctl" || command -v journalctl)}"
SYSTEMCTL="${SYSTEMCTL:-$(command -v "$BUILD_DIR/systemctl" || command -v systemctl)}"
+SYSTEMD_ID128="${SYSTEMD_ID128:-$(command -v "$BUILD_DIR/systemd-id128" || command -v systemd-id128)}"
TESTFILE="${BASH_SOURCE[1]}"
if [ -z "$TESTFILE" ]; then
[[ -z "$ASAN_RT_PATH" ]] && dfatal "ASAN_RT_PATH is empty, but it shouldn't be"
- default_asan_options="${ASAN_OPTIONS:-strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1}"
+ default_asan_options="${ASAN_OPTIONS:-strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:disable_coredump=0:use_madv_dontdump=1}"
default_ubsan_options="${UBSAN_OPTIONS:-print_stacktrace=1:print_summary=1:halt_on_error=1}"
if [[ "$ASAN_COMPILER" == "clang" ]]; then
image_install kpartx /lib/udev/kpartx_id lsmod mpathpersist multipath multipathd partx
image_install "${ROOTLIBDIR:?}"/system/multipathd.{service,socket}
if get_bool "$LOOKS_LIKE_DEBIAN"; then
- # Note: try both 60-kpartx.rules (as seen on Debian Sid with 0.9.4-7) and 90-kpartx.rules (as seen on
+ # Note: try both 60-kpartx.rules (as seen on Debian Sid with 0.9.4-7) and 95-kpartx.rules (as seen on
# Ubuntu Jammy with 0.8.8-1ubuntu1.22.04.4)
- inst_rules 56-dm-parts.rules 56-dm-mpath.rules 60-kpartx.rules 60-multipath.rules 68-del-part-nodes.rules 90-kpartx.rules
+ inst_rules 56-dm-parts.rules 56-dm-mpath.rules 60-kpartx.rules 60-multipath.rules 68-del-part-nodes.rules 95-kpartx.rules
else
inst_rules 11-dm-mpath.rules 11-dm-parts.rules 62-multipath.rules 66-kpartx.rules 68-del-part-nodes.rules
fi
sfdisk "$LOOPDEV" <<EOF
label: gpt
type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B name=esp size=${esp_size}M
-type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=root size=${root_size}M bootable
+type=$("${SYSTEMD_ID128:?}" show root -Pu) name=root size=${root_size}M bootable
type=BC13C2FF-59E6-4262-A352-B275FD6F7172 name=boot size=${boot_size}M
type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=data
EOF
# Newer Fedora versions use dbus-broker by default. Let's install it if it's available.
if [ -f "$ROOTLIBDIR/system/dbus-broker.service" ]; then
inst "$ROOTLIBDIR/system/dbus-broker.service"
- inst_symlink /etc/systemd/system/dbus.service
inst /usr/bin/dbus-broker
inst /usr/bin/dbus-broker-launch
+ image_install -o {/etc,/usr/lib}/systemd/system/dbus.service
elif [ -f "$ROOTLIBDIR/system/dbus-daemon.service" ]; then
# Fedora rawhide replaced dbus.service with dbus-daemon.service
inst "$ROOTLIBDIR/system/dbus-daemon.service"
# Alias symlink
- inst_symlink /etc/systemd/system/dbus.service
+ image_install -o {/etc,/usr/lib}/systemd/system/dbus.service
else
inst "$ROOTLIBDIR/system/dbus.service"
fi
# Newer Fedora versions use dbus-broker by default. Let's install it if it's available.
if [ -f "$userunitdir/dbus-broker.service" ]; then
inst "$userunitdir/dbus-broker.service"
- inst_symlink /etc/systemd/user/dbus.service
+ image_install -o {/etc,/usr/lib}/systemd/user/dbus.service
elif [ -f "${ROOTLIBDIR:?}/system/dbus-daemon.service" ]; then
# Fedora rawhide replaced dbus.service with dbus-daemon.service
inst "$userunitdir/dbus-daemon.service"
# Alias symlink
- inst_symlink /etc/systemd/user/dbus.service
+ image_install -o {/etc,/usr/lib}/systemd/user/dbus.service
else
inst "$userunitdir/dbus.service"
fi
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=client-peer
+
+[Network]
+Bridge=bridge-relay
+IPv6AcceptRA=no
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[NetDev]
+Name=bridge-relay
+Kind=bridge
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=bridge-relay
+
+[Network]
+Address=192.168.2.1/24
+DHCPServer=yes
+IPv6AcceptRA=no
+
+[DHCPServer]
+RelayTarget=192.168.1.1
+RelayAgentRemoteId=string:aabbccdd
# SPDX-License-Identifier: LGPL-2.1-or-later
-[Match]
-Name=dummy98
-
-[Network]
-IPv6AcceptRA=no
-
[Neighbor]
Address=192.168.10.1
LinkLayerAddress=00:00:5e:00:02:65
# SPDX-License-Identifier: LGPL-2.1-or-later
-
[Neighbor]
Address=192.168.10.1
LinkLayerAddress=00:00:5e:00:03:65
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Neighbor]
+Address=192.168.10.1
+LinkLayerAddress=00:00:5e:00:03:66
IPv6AcceptRA=no
Address=2001:1234:5:8f63::1/120
Address=192.168.5.10/24
-Gateway=192.168.5.1
[NextHop]
Id=1
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=veth99
+
+[Network]
+IPv6AcceptRA=no
+Address=2001:1234:5:8f63::1/120
+Address=192.168.5.10/24
+
+# Commented out lines are specified in 25-nexthop.network
+
+[NextHop]
+#Id=1
+Id=6
+Gateway=192.168.5.1
+
+[NextHop]
+#Id=2
+Id=7
+Gateway=2001:1234:5:8f63::2
+
+[NextHop]
+#Id=3
+Id=4
+Family=ipv6
+
+[NextHop]
+#Id=4
+Id=3
+Family=ipv4
+
+[NextHop]
+Id=5
+#Gateway=192.168.10.1
+#OnLink=yes
+Gateway=192.168.5.3
+OnLink=no
+
+[NextHop]
+#Id=6
+Id=1
+Family=ipv4
+Blackhole=yes
+
+[NextHop]
+#Id=7
+Id=2
+Family=ipv6
+Blackhole=yes
+
+[NextHop]
+Id=8
+Gateway=fe80::222:4dff:ff:ff:ff:ff
+
+[NextHop]
+Gateway=192.168.5.2
+
+[NextHop]
+Family=ipv4
+Blackhole=yes
+
+[NextHop]
+Family=ipv6
+Blackhole=yes
+
+[Route]
+#NextHop=1
+NextHop=6
+Destination=10.10.10.10
+
+[Route]
+#NextHop=2
+NextHop=7
+Destination=10.10.10.11
+
+[Route]
+#NextHop=2
+NextHop=7
+Destination=2001:1234:5:8f62::1
+
+[Route]
+NextHop=5
+Destination=10.10.10.12
+
+[Route]
+#NextHop=6
+NextHop=1
+Destination=10.10.10.13
+
+[Route]
+#NextHop=7
+NextHop=2
+Destination=2001:1234:5:8f62::2
+
+[Route]
+#NextHop=21
+NextHop=20
+Destination=10.10.10.14
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=dummy98
+
+[Network]
+Address=192.168.20.20/24
+IPv6AcceptRA=no
+
+# Commented out lines are specified in 25-nexthop-dummy.network
+
+[NextHop]
+#Id=20
+Id=21
+Gateway=192.168.20.1
+
+[NextHop]
+#Id=21
+#Group=1:3 20:1
+Id=20
+Group=5:3 21:1
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test1
+
+[Network]
+Address=192.168.20.21/24
+IPv6AcceptRA=no
+
+[Route]
+Destination=10.10.11.10
+# Nexthop 21 is configured as a group nexthop of 1 and 20
+NextHop=21
--- /dev/null
+192.168.27.3:51820
--- /dev/null
+EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
Kind=wireguard
[WireGuard]
-PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
+#PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
ListenPort=51821
FwMark=1235
--- /dev/null
+6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=
[WireGuardPeer]
PublicKey=RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=
AllowedIPs=fd31:bf08:57cb::/48,192.168.26.3/24
-#Endpoint=wireguard.example.com:51820
-Endpoint=192.168.27.3:51820
+#Endpoint=192.168.27.3:51820
+Endpoint=@network.wireguard.peer0.endpoint
PresharedKey=IIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=
PersistentKeepalive=20
RouteTable=1234
[WireGuardPeer]
PublicKey=9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=
-PresharedKey=6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=
+PresharedKey=@network.wireguard.peer2.psk
AllowedIPs=192.168.124.3
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=bridge99
+
+[Network]
+IPv6AcceptRA=false
[Network]
IPv6AcceptRA=no
-
-[Neighbor]
-Address=192.168.10.1
-LinkLayerAddress=00:00:5e:00:02:66
+Bridge=bridge99
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+OriginalName=bridge99
+
+[Link]
+MACAddressPolicy=none
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[NetDev]
+Name=bridge99
+Kind=bridge
+MACAddress=none
import json
import os
import pathlib
+import random
import re
import shutil
import signal
networkd_conf_dropin_dir = '/run/systemd/networkd.conf.d'
networkd_ci_temp_dir = '/run/networkd-ci'
udev_rules_dir = '/run/udev/rules.d'
+credstore_dir = '/run/credstore'
dnsmasq_pid_file = '/run/networkd-ci/test-dnsmasq.pid'
dnsmasq_log_file = '/run/networkd-ci/test-dnsmasq.log'
if has_link:
udev_reload()
+def copy_credential(src, target):
+ mkdir_p(credstore_dir)
+ cp(os.path.join(networkd_ci_temp_dir, src),
+ os.path.join(credstore_dir, target))
+
def remove_network_unit(*units):
"""
Remove previously copied unit files from the testbed.
invocation_id = networkd_invocation_id()
command = [
'journalctl',
+ '--no-hostname',
+ '--output=short-monotonic',
f'_SYSTEMD_INVOCATION_ID={invocation_id}',
]
if since:
command.append(f'--since={since}')
+ check_output('journalctl --sync')
return check_output(*command)
+def networkd_is_failed():
+ return call_quiet('systemctl is-failed -q systemd-networkd.service') != 1
+
def stop_networkd(show_logs=True):
if show_logs:
invocation_id = networkd_invocation_id()
if show_logs:
print(read_networkd_log(invocation_id))
# Check if networkd exits cleanly.
- assert call_quiet('systemctl is-failed -q systemd-networkd.service') == 1
+ assert not networkd_is_failed()
def start_networkd():
check_output('systemctl start systemd-networkd')
def networkd_pid():
return int(check_output('systemctl show --value -p MainPID systemd-networkd.service'))
+def networkctl(*args):
+ # Do not call networkctl if networkd is in failed state.
+ # Otherwise, networkd may be restarted and we may get wrong results.
+ assert not networkd_is_failed()
+ return check_output(*(networkctl_cmd + list(args)), env=env)
+
+def networkctl_status(*args):
+ return networkctl('-n', '0', 'status', *args)
+
+def networkctl_json(*args):
+ return networkctl('--json=short', 'status', *args)
+
def networkctl_reconfigure(*links):
- check_output(*networkctl_cmd, 'reconfigure', *links, env=env)
+ networkctl('reconfigure', *links)
def networkctl_reload(sleep_time=1):
- check_output(*networkctl_cmd, 'reload', env=env)
+ networkctl('reload')
# 'networkctl reload' asynchronously reconfigure links.
# Hence, we need to wait for a short time for link to be in configuring state.
if sleep_time > 0:
time.sleep(sleep_time)
+def resolvectl(*args):
+ return check_output(*(resolvectl_cmd + list(args)), env=env)
+
+def timedatectl(*args):
+ return check_output(*(timedatectl_cmd + list(args)), env=env)
+
def setup_common():
print()
def wait_activated(self, link, state='down', timeout=20, fail_assert=True):
# wait for the interface is activated.
- invocation_id = check_output('systemctl show systemd-networkd -p InvocationID --value')
needle = f'{link}: Bringing link {state}'
flag = state.upper()
for iteration in range(timeout + 1):
time.sleep(1)
if not link_exists(link):
continue
- output = check_output('journalctl _SYSTEMD_INVOCATION_ID=' + invocation_id)
+ output = read_networkd_log()
if needle in output and flag in check_output(f'ip link show {link}'):
return True
if fail_assert:
time.sleep(1)
if not link_exists(link):
continue
- output = check_output(*networkctl_cmd, '-n', '0', 'status', link, env=env)
+ output = networkctl_status(link)
if re.search(rf'(?m)^\s*State:\s+{operstate}\s+\({setup_state}\)\s*$', output):
return True
try:
check_output(*args, env=wait_online_env)
except subprocess.CalledProcessError:
- # show detailed status on failure
- for link in links_with_operstate:
- name = link.split(':')[0]
- if link_exists(name):
- call(*networkctl_cmd, '-n', '0', 'status', name, env=env)
+ if networkd_is_failed():
+ print('!!!!! systemd-networkd.service is failed !!!!!')
+ call('systemctl status systemd-networkd.service')
+ else:
+ # show detailed status on failure
+ for link in links_with_operstate:
+ name = link.split(':')[0]
+ if link_exists(name):
+ networkctl_status(name)
raise
if not bool_any and setup_state:
for link in links_with_operstate:
start_networkd()
self.wait_online(['dummy98:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
self.assertRegex(output, 'hogehogehogehogehogehoge')
@expectedFailureIfAlternativeNameIsNotAvailable()
start_networkd()
self.wait_online(['dummyalt:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummyalt', env=env)
+ output = networkctl_status('dummyalt')
self.assertIn('hogehogehogehogehogehoge', output)
self.assertNotIn('dummy98', output)
def test_renew(self):
def check():
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertIn('Gateway: 192.168.5.3', output)
copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
start_networkd()
check()
- output = check_output(*networkctl_cmd, '--lines=0', '--stats', '--all', '--full', '--json=short', 'status')
- check_json(output)
+ check_json(networkctl_json('--lines=0', '--stats', '--all', '--full'))
for verb in ['renew', 'forcerenew']:
- call_check(*networkctl_cmd, verb, 'veth99')
+ networkctl(verb, 'veth99')
check()
- call_check(*networkctl_cmd, verb, 'veth99', 'veth99', 'veth99')
+ networkctl(verb, 'veth99', 'veth99', 'veth99')
check()
def test_up_down(self):
start_networkd()
self.wait_online(['dummy98:routable'])
- call_check(*networkctl_cmd, 'down', 'dummy98')
+ networkctl('down', 'dummy98')
self.wait_online(['dummy98:off'])
- call_check(*networkctl_cmd, 'up', 'dummy98')
+ networkctl('up', 'dummy98')
self.wait_online(['dummy98:routable'])
- call_check(*networkctl_cmd, 'down', 'dummy98', 'dummy98', 'dummy98')
+ networkctl('down', 'dummy98', 'dummy98', 'dummy98')
self.wait_online(['dummy98:off'])
- call_check(*networkctl_cmd, 'up', 'dummy98', 'dummy98', 'dummy98')
+ networkctl('up', 'dummy98', 'dummy98', 'dummy98')
self.wait_online(['dummy98:routable'])
def test_reload(self):
self.wait_online(['test1:degraded'])
- output = check_output(*networkctl_cmd, 'list', env=env)
+ output = networkctl('list')
self.assertRegex(output, '1 lo ')
self.assertRegex(output, 'test1')
- output = check_output(*networkctl_cmd, 'list', 'test1', env=env)
+ output = networkctl('list', 'test1')
self.assertNotRegex(output, '1 lo ')
self.assertRegex(output, 'test1')
- output = check_output(*networkctl_cmd, 'list', 'te*', env=env)
+ output = networkctl('list', 'te*')
self.assertNotRegex(output, '1 lo ')
self.assertRegex(output, 'test1')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'te*', env=env)
+ output = networkctl_status('te*')
self.assertNotRegex(output, '1: lo ')
self.assertRegex(output, 'test1')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'tes[a-z][0-9]', env=env)
+ output = networkctl_status('tes[a-z][0-9]')
self.assertNotRegex(output, '1: lo ')
self.assertRegex(output, 'test1')
self.wait_online(['test1:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
self.assertRegex(output, 'MTU: 1600')
def test_type(self):
start_networkd()
self.wait_online(['test1:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
self.assertRegex(output, 'Type: ether')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'lo', env=env)
+ output = networkctl_status('lo')
print(output)
self.assertRegex(output, 'Type: loopback')
start_networkd()
self.wait_online(['test1:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
self.assertIn('Link File: /run/systemd/network/11-test-unit-file.link', output)
self.assertIn('/run/systemd/network/11-test-unit-file.link.d/dropin.conf', output)
# In that case, the udev DB for the loopback network interface may already have ID_NET_LINK_FILE property.
# Let's reprocess the interface and drop the property.
check_output(*udevadm_cmd, 'trigger', '--settle', '--action=add', '/sys/class/net/lo')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'lo', env=env)
+ output = networkctl_status('lo')
print(output)
self.assertIn('Link File: n/a', output)
self.assertIn('Network File: n/a', output)
self.wait_online(['test1:degraded', 'veth99:degraded', 'veth-peer:degraded'])
- check_output(*networkctl_cmd, 'delete', 'test1', 'veth99', env=env)
+ networkctl('delete', 'test1', 'veth99')
self.check_link_exists('test1', expected=False)
self.check_link_exists('veth99', expected=False)
self.check_link_exists('veth-peer', expected=False)
def test_label(self):
- call_check(*networkctl_cmd, 'label')
+ networkctl('label')
class NetworkdMatchTests(unittest.TestCase, Utilities):
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
self.assertIn('Network File: /run/systemd/network/12-dummy-match-mac-01.network', output)
output = check_output('ip -4 address show dev dummy98')
self.assertIn('10.0.0.1/16', output)
self.wait_address('dummy98', '10.0.0.2/16', ipv='-4', timeout_sec=10)
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
self.assertIn('Network File: /run/systemd/network/12-dummy-match-mac-02.network', output)
check_output('ip link set dev dummy98 down')
self.wait_address('dummy98-1', '10.0.1.2/16', ipv='-4', timeout_sec=10)
self.wait_online(['dummy98-1:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98-1', env=env)
+ output = networkctl_status('dummy98-1')
self.assertIn('Network File: /run/systemd/network/12-dummy-match-renamed.network', output)
check_output('ip link set dev dummy98-1 down')
self.wait_address('dummy98-2', '10.0.2.2/16', ipv='-4', timeout_sec=10)
self.wait_online(['dummy98-2:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98-2', env=env)
+ output = networkctl_status('dummy98-2')
self.assertIn('Network File: /run/systemd/network/12-dummy-match-altname.network', output)
def test_match_udev_property(self):
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
print(output)
self.assertRegex(output, 'Network File: /run/systemd/network/14-match-udev-property')
self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state')))
self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version')))
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bridge99', env=env)
+ output = networkctl_status('bridge99')
print(output)
self.assertRegex(output, 'Priority: 9')
self.assertRegex(output, 'STP: yes')
self.check_link_attr('bond98', 'bonding', 'mode', 'balance-tlb 5')
self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb', '1')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bond99', env=env)
+ output = networkctl_status('bond99')
print(output)
self.assertIn('Mode: 802.3ad', output)
self.assertIn('Miimon: 1s', output)
self.assertIn('Updelay: 2s', output)
self.assertIn('Downdelay: 2s', output)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bond98', env=env)
+ output = networkctl_status('bond98')
print(output)
self.assertIn('Mode: balance-tlb', output)
start_networkd()
self.wait_online(['vcan99:carrier', 'vcan98:carrier'])
+ # For can devices, 'carrier' is the default required operational state.
+ self.wait_online(['vcan99', 'vcan98'])
# https://github.com/systemd/systemd/issues/30140
output = check_output('ip -d link show vcan99')
start_networkd()
self.wait_online(['vxcan99:carrier', 'vxcan-peer:carrier'])
+ # For can devices, 'carrier' is the default required operational state.
+ self.wait_online(['vxcan99', 'vxcan-peer'])
@expectedFailureIfModuleIsNotAvailable('wireguard')
def test_wireguard(self):
+ copy_credential('25-wireguard-endpoint-peer0-cred.txt', 'network.wireguard.peer0.endpoint')
+ copy_credential('25-wireguard-preshared-key-peer2-cred.txt', 'network.wireguard.peer2.psk')
+ copy_credential('25-wireguard-no-peer-private-key-cred.txt', 'network.wireguard.private.25-wireguard-no-peer')
+
copy_network_unit('25-wireguard.netdev', '25-wireguard.network',
'25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network',
'25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt',
output = check_output('ip -4 route show dev wg99 table 1234')
print(output)
- self.assertIn('192.168.26.0/24 proto static metric 123', output)
+ self.assertIn('192.168.26.0/24 proto static scope link metric 123', output)
output = check_output('ip -6 route show dev wg99 table 1234')
print(output)
self.assertIn('00:11:22:33:44:66 dst 10.0.0.6 self permanent', output)
self.assertIn('00:11:22:33:44:77 dst 10.0.0.7 via test1 self permanent', output)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'vxlan99', env=env)
+ output = networkctl_status('vxlan99')
print(output)
self.assertIn('VNI: 999', output)
self.assertIn('Destination Port: 5555', output)
# netlabel
self.check_netlabel('dummy98', r'10\.10\.1\.0/24')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
def test_address_static(self):
copy_network_unit('25-address-static.network', '12-dummy.netdev', copy_dropins=False)
check_output(f'ip link set dev test1 carrier {carrier}')
self.wait_online([f'test1:{routable_map[carrier]}:{routable_map[carrier]}'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
self.assertRegex(output, '192.168.0.15')
self.assertRegex(output, '192.168.0.1')
check_output(f'ip link set dev test1 carrier {carrier}')
self.wait_online([f'test1:{routable_map[carrier]}:{routable_map[carrier]}'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
if have_config:
self.assertRegex(output, '192.168.0.15')
self.assertRegex(output, 'iif test1')
self.assertRegex(output, 'lookup 10')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
def test_routing_policy_rule_issue_11280(self):
copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev',
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
print(output)
print('### ip -6 route show dev dummy98')
self.assertIn('via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98', output)
self.assertIn('via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
copy_network_unit('25-address-static.network', copy_dropins=False)
networkctl_reload()
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
print(output)
print('### ip -6 route show dev dummy98')
for i in range(1, 5):
self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy')
- def test_neighbor_section(self):
- copy_network_unit('25-neighbor-section.network', '12-dummy.netdev', copy_dropins=False)
+ def test_neighbor(self):
+ copy_network_unit('12-dummy.netdev', '25-neighbor-dummy.network', '25-neighbor-dummy.network.d/10-step1.conf',
+ '25-gre-tunnel-remote-any.netdev', '25-neighbor-ip.network',
+ '25-ip6gre-tunnel-remote-any.netdev', '25-neighbor-ipv6.network',
+ copy_dropins=False)
start_networkd()
- self.wait_online(['dummy98:degraded'])
+ self.wait_online(['dummy98:degraded', 'gretun97:routable', 'ip6gretun97:routable'])
+
+ print('### ip neigh list dev gretun97')
+ output = check_output('ip neigh list dev gretun97')
+ print(output)
+ self.assertIn('10.0.0.22 lladdr 10.65.223.239 PERMANENT', output)
+ self.assertNotIn('10.0.0.23', output)
+
+ print('### ip neigh list dev ip6gretun97')
+ output = check_output('ip neigh list dev ip6gretun97')
+ print(output)
+ self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT')
+ self.assertNotIn('2001:db8:0:f102::18', output)
print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
self.assertNotIn('192.168.10.2', output)
self.assertNotIn('00:00:5e:00:02:67', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
- copy_network_unit('25-neighbor-section.network.d/override.conf')
+ # Here, 10-step1.conf is intendedly kept, to verify that 10-step2.conf overrides
+ # the valid configurations in 10-step1.conf.
+ copy_network_unit('25-neighbor-dummy.network.d/10-step2.conf')
networkctl_reload()
self.wait_online(['dummy98:degraded'])
- print('### ip neigh list dev dummy98 (after reloading)')
+ print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
print(output)
self.assertIn('192.168.10.1 lladdr 00:00:5e:00:03:65 PERMANENT', output)
self.assertIn('2004:da8:1::1 lladdr 00:00:5e:00:03:66 PERMANENT', output)
self.assertNotIn('2004:da8:1:0::2', output)
self.assertNotIn('192.168.10.2', output)
- self.assertNotIn('00:00:5e:00:02', output)
-
- def test_neighbor_reconfigure(self):
- copy_network_unit('25-neighbor-section.network', '12-dummy.netdev', copy_dropins=False)
- start_networkd()
- self.wait_online(['dummy98:degraded'])
+ self.assertNotIn('00:00:5e:00:02:67', output)
- print('### ip neigh list dev dummy98')
- output = check_output('ip neigh list dev dummy98')
- print(output)
- self.assertIn('192.168.10.1 lladdr 00:00:5e:00:02:65 PERMANENT', output)
- self.assertIn('2004:da8:1::1 lladdr 00:00:5e:00:02:66 PERMANENT', output)
+ check_json(networkctl_json())
- remove_network_unit('25-neighbor-section.network')
- copy_network_unit('25-neighbor-next.network')
+ remove_network_unit('25-neighbor-dummy.network.d/10-step1.conf',
+ '25-neighbor-dummy.network.d/10-step2.conf')
+ copy_network_unit('25-neighbor-dummy.network.d/10-step3.conf')
networkctl_reload()
self.wait_online(['dummy98:degraded'])
+
print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
print(output)
+ self.assertIn('192.168.10.1 lladdr 00:00:5e:00:03:66 PERMANENT', output)
self.assertNotIn('00:00:5e:00:02:65', output)
- self.assertIn('192.168.10.1 lladdr 00:00:5e:00:02:66 PERMANENT', output)
+ self.assertNotIn('00:00:5e:00:02:66', output)
+ self.assertNotIn('00:00:5e:00:03:65', output)
self.assertNotIn('2004:da8:1::1', output)
- def test_neighbor_gre(self):
- copy_network_unit('25-neighbor-ip.network', '25-neighbor-ipv6.network', '25-neighbor-ip-dummy.network',
- '12-dummy.netdev', '25-gre-tunnel-remote-any.netdev', '25-ip6gre-tunnel-remote-any.netdev')
- start_networkd()
- self.wait_online(['dummy98:degraded', 'gretun97:routable', 'ip6gretun97:routable'], timeout='40s')
-
- output = check_output('ip neigh list dev gretun97')
- print(output)
- self.assertIn('10.0.0.22 lladdr 10.65.223.239 PERMANENT', output)
- self.assertNotIn('10.0.0.23', output)
-
- output = check_output('ip neigh list dev ip6gretun97')
- print(output)
- self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT')
- self.assertNotIn('2001:db8:0:f102::18', output)
-
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
-
def test_link_local_addressing(self):
copy_network_unit('25-link-local-addressing-yes.network', '11-dummy.netdev',
'25-link-local-addressing-no.network', '12-dummy.netdev')
# default is true, if neither are specified
expected = True
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'test1', env=env)
+ output = networkctl_status('test1')
print(output)
yesno = 'yes' if expected else 'no'
start_networkd()
self.wait_online(['dummy98:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env)
+ output = networkctl_status('dummy98')
print(output)
self.assertRegex(output, 'Address: 192.168.42.100')
self.assertRegex(output, 'DNS: 192.168.42.1')
self.assertRegex(output, 'inet 10.1.2.3/16 scope global dummy98')
self.assertNotRegex(output, 'inet 10.2.3.4/16 scope global dynamic dummy98')
- def check_nexthop(self, manage_foreign_nexthops):
+ def check_nexthop(self, manage_foreign_nexthops, first):
self.wait_online(['veth99:routable', 'veth-peer:routable', 'dummy98:routable'])
output = check_output('ip nexthop list dev veth99')
print(output)
- self.assertIn('id 1 via 192.168.5.1 dev veth99', output)
- self.assertIn('id 2 via 2001:1234:5:8f63::2 dev veth99', output)
+ if first:
+ self.assertIn('id 1 via 192.168.5.1 dev veth99', output)
+ self.assertIn('id 2 via 2001:1234:5:8f63::2 dev veth99', output)
+ else:
+ self.assertIn('id 6 via 192.168.5.1 dev veth99', output)
+ self.assertIn('id 7 via 2001:1234:5:8f63::2 dev veth99', output)
self.assertIn('id 3 dev veth99', output)
self.assertIn('id 4 dev veth99', output)
- self.assertRegex(output, 'id 5 via 192.168.10.1 dev veth99 .*onlink')
+ if first:
+ self.assertRegex(output, 'id 5 via 192.168.10.1 dev veth99 .*onlink')
+ else:
+ self.assertIn('id 5 via 192.168.5.3 dev veth99', output)
+ self.assertNotRegex(output, 'id 5 via 192.168.5.3 dev veth99 .*onlink')
self.assertIn('id 8 via fe80:0:222:4dff:ff:ff:ff:ff dev veth99', output)
if manage_foreign_nexthops:
self.assertRegex(output, r'id [0-9]* via 192.168.5.2 dev veth99')
output = check_output('ip nexthop list dev dummy98')
print(output)
- self.assertIn('id 20 via 192.168.20.1 dev dummy98', output)
+ if first:
+ self.assertIn('id 20 via 192.168.20.1 dev dummy98', output)
+ else:
+ self.assertIn('id 21 via 192.168.20.1 dev dummy98', output)
if manage_foreign_nexthops:
self.assertNotIn('id 42 via 192.168.20.2 dev dummy98', output)
else:
# kernel manages blackhole nexthops on lo
output = check_output('ip nexthop list dev lo')
print(output)
- self.assertIn('id 6 blackhole', output)
- self.assertIn('id 7 blackhole', output)
+ if first:
+ self.assertIn('id 6 blackhole', output)
+ self.assertIn('id 7 blackhole', output)
+ else:
+ self.assertIn('id 1 blackhole', output)
+ self.assertIn('id 2 blackhole', output)
# group nexthops are shown with -0 option
- output = check_output('ip -0 nexthop list id 21')
- print(output)
- self.assertRegex(output, r'id 21 group (1,3/20|20/1,3)')
+ if first:
+ output = check_output('ip -0 nexthop list id 21')
+ print(output)
+ self.assertRegex(output, r'id 21 group (1,3/20|20/1,3)')
+ else:
+ output = check_output('ip -0 nexthop list id 20')
+ print(output)
+ self.assertRegex(output, r'id 20 group (5,3/21|21/5,3)')
output = check_output('ip route show dev veth99 10.10.10.10')
print(output)
- self.assertEqual('10.10.10.10 nhid 1 via 192.168.5.1 proto static', output)
+ if first:
+ self.assertEqual('10.10.10.10 nhid 1 via 192.168.5.1 proto static', output)
+ else:
+ self.assertEqual('10.10.10.10 nhid 6 via 192.168.5.1 proto static', output)
output = check_output('ip route show dev veth99 10.10.10.11')
print(output)
- self.assertEqual('10.10.10.11 nhid 2 via inet6 2001:1234:5:8f63::2 proto static', output)
+ if first:
+ self.assertEqual('10.10.10.11 nhid 2 via inet6 2001:1234:5:8f63::2 proto static', output)
+ else:
+ self.assertEqual('10.10.10.11 nhid 7 via inet6 2001:1234:5:8f63::2 proto static', output)
output = check_output('ip route show dev veth99 10.10.10.12')
print(output)
- self.assertEqual('10.10.10.12 nhid 5 via 192.168.10.1 proto static onlink', output)
+ if first:
+ self.assertEqual('10.10.10.12 nhid 5 via 192.168.10.1 proto static onlink', output)
+ else:
+ self.assertEqual('10.10.10.12 nhid 5 via 192.168.5.3 proto static', output)
output = check_output('ip -6 route show dev veth99 2001:1234:5:8f62::1')
print(output)
- self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
+ if first:
+ self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
+ else:
+ self.assertEqual('2001:1234:5:8f62::1 nhid 7 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
output = check_output('ip route show 10.10.10.13')
print(output)
- self.assertEqual('blackhole 10.10.10.13 nhid 6 dev lo proto static', output)
+ if first:
+ self.assertEqual('blackhole 10.10.10.13 nhid 6 dev lo proto static', output)
+ else:
+ self.assertEqual('blackhole 10.10.10.13 nhid 1 dev lo proto static', output)
output = check_output('ip -6 route show 2001:1234:5:8f62::2')
print(output)
- self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output)
+ if first:
+ self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output)
+ else:
+ self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 2 dev lo proto static metric 1024 pref medium', output)
output = check_output('ip route show 10.10.10.14')
print(output)
- self.assertIn('10.10.10.14 nhid 21 proto static', output)
+ if first:
+ self.assertIn('10.10.10.14 nhid 21 proto static', output)
+ self.assertIn('nexthop via 192.168.5.1 dev veth99 weight 3', output)
+ else:
+ self.assertIn('10.10.10.14 nhid 20 proto static', output)
+ self.assertIn('nexthop via 192.168.5.3 dev veth99 weight 3', output)
self.assertIn('nexthop via 192.168.20.1 dev dummy98 weight 1', output)
- self.assertIn('nexthop via 192.168.5.1 dev veth99 weight 3', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
def _test_nexthop(self, manage_foreign_nexthops):
if not manage_foreign_nexthops:
check_output('ip address add 192.168.20.20/24 dev dummy98')
check_output('ip nexthop add id 42 via 192.168.20.2 dev dummy98')
- copy_network_unit('25-nexthop.network', '25-veth.netdev', '25-veth-peer.network',
- '12-dummy.netdev', '25-nexthop-dummy.network')
+ copy_network_unit('25-nexthop-1.network', '25-veth.netdev', '25-veth-peer.network',
+ '12-dummy.netdev', '25-nexthop-dummy-1.network')
start_networkd()
- self.check_nexthop(manage_foreign_nexthops)
+ self.check_nexthop(manage_foreign_nexthops, first=True)
+
+ remove_network_unit('25-nexthop-1.network', '25-nexthop-dummy-1.network')
+ copy_network_unit('25-nexthop-2.network', '25-nexthop-dummy-2.network')
+ networkctl_reload()
+ self.check_nexthop(manage_foreign_nexthops, first=False)
- remove_network_unit('25-nexthop.network')
+ remove_network_unit('25-nexthop-2.network')
copy_network_unit('25-nexthop-nothing.network')
networkctl_reload()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
print(output)
self.assertEqual(output, '')
- remove_network_unit('25-nexthop-nothing.network')
- copy_network_unit('25-nexthop.network')
- networkctl_reconfigure('dummy98')
+ remove_network_unit('25-nexthop-nothing.network', '25-nexthop-dummy-2.network')
+ copy_network_unit('25-nexthop-1.network', '25-nexthop-dummy-1.network')
+ # Of course, networkctl_reconfigure() below is unnecessary in normal operation, but it is intentional
+ # here to test reconfiguring with different .network files does not trigger race.
+ # See also comments in link_drop_requests().
+ networkctl_reconfigure('dummy98') # reconfigured with 25-nexthop-dummy-2.network
+ networkctl_reload() # reconfigured with 25-nexthop-dummy-1.network
+
+ self.check_nexthop(manage_foreign_nexthops, first=True)
+
+ # Remove nexthop with ID 20
+ check_output('ip nexthop del id 20')
+ copy_network_unit('11-dummy.netdev', '25-nexthop-test1.network')
networkctl_reload()
- self.check_nexthop(manage_foreign_nexthops)
+ # 25-nexthop-test1.network requests a route with nexthop ID 21,
+ # which is silently removed by the kernel when nexthop with ID 20 is removed in the above,
+ # hence test1 should be stuck in the configuring state.
+ self.wait_operstate('test1', operstate='routable', setup_state='configuring')
+
+ # Wait for a while, and check if the interface is still in the configuring state.
+ time.sleep(1)
+ output = networkctl_status('test1')
+ self.assertIn('State: routable (configuring)', output)
+
+ # Reconfigure the interface that has nexthop with ID 20 and 21,
+ # then the route requested by test1 can be configured.
+ networkctl_reconfigure('dummy98')
+ self.wait_online(['test1:routable'])
+
+ # Check if the requested route actually configured.
+ output = check_output('ip route show 10.10.11.10')
+ print(output)
+ self.assertIn('10.10.11.10 nhid 21 proto static', output)
+ self.assertIn('nexthop via 192.168.5.1 dev veth99 weight 3', output)
+ self.assertIn('nexthop via 192.168.20.1 dev dummy98 weight 1', output)
remove_link('veth99')
time.sleep(2)
self.wait_online(['dummy98:routable'])
# make link state file updated
- check_output(*resolvectl_cmd, 'revert', 'dummy98', env=env)
+ resolvectl('revert', 'dummy98')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
output = read_link_state_file('dummy98')
print(output)
self.assertIn('MDNS=yes', output)
self.assertIn('DNSSEC=no', output)
- check_output(*resolvectl_cmd, 'dns', 'dummy98', '10.10.10.12#ccc.com', '10.10.10.13', '1111:2222::3333', env=env)
- check_output(*resolvectl_cmd, 'domain', 'dummy98', 'hogehogehoge', '~foofoofoo', env=env)
- check_output(*resolvectl_cmd, 'llmnr', 'dummy98', 'yes', env=env)
- check_output(*resolvectl_cmd, 'mdns', 'dummy98', 'no', env=env)
- check_output(*resolvectl_cmd, 'dnssec', 'dummy98', 'yes', env=env)
- check_output(*timedatectl_cmd, 'ntp-servers', 'dummy98', '2.fedora.pool.ntp.org', '3.fedora.pool.ntp.org', env=env)
+ resolvectl('dns', 'dummy98', '10.10.10.12#ccc.com', '10.10.10.13', '1111:2222::3333')
+ resolvectl('domain', 'dummy98', 'hogehogehoge', '~foofoofoo')
+ resolvectl('llmnr', 'dummy98', 'yes')
+ resolvectl('mdns', 'dummy98', 'no')
+ resolvectl('dnssec', 'dummy98', 'yes')
+ timedatectl('ntp-servers', 'dummy98', '2.fedora.pool.ntp.org', '3.fedora.pool.ntp.org')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
output = read_link_state_file('dummy98')
print(output)
self.assertIn('MDNS=no', output)
self.assertIn('DNSSEC=yes', output)
- check_output(*timedatectl_cmd, 'revert', 'dummy98', env=env)
+ timedatectl('revert', 'dummy98')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
output = read_link_state_file('dummy98')
print(output)
self.assertIn('MDNS=no', output)
self.assertIn('DNSSEC=yes', output)
- check_output(*resolvectl_cmd, 'revert', 'dummy98', env=env)
+ resolvectl('revert', 'dummy98')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
output = read_link_state_file('dummy98')
print(output)
def tearDown(self):
tear_down_common()
+ def test_bridge_mac_none(self):
+ copy_network_unit('12-dummy-mac.netdev', '26-bridge-mac-slave.network',
+ '26-bridge-mac.netdev', '26-bridge-mac-master.network', '26-bridge-mac.link')
+ start_networkd()
+ self.wait_online(['dummy98:enslaved', 'bridge99:degraded'])
+
+ output = check_output('ip link show dev dummy98')
+ print(output)
+ self.assertIn('link/ether 12:34:56:78:9a:01', output)
+
+ output = check_output('ip link show dev bridge99')
+ print(output)
+ self.assertIn('link/ether 12:34:56:78:9a:01', output)
+
def test_bridge_vlan(self):
copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave.network',
'26-bridge.netdev', '26-bridge-vlan-master.network',
self.wait_online(['bridge99:no-carrier:no-carrier'])
self.check_link_attr('bridge99', 'carrier', '0')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bridge99', env=env)
+ output = networkctl_status('bridge99')
self.assertRegex(output, '10.1.2.3')
self.assertRegex(output, '10.1.2.1')
if trial > 0:
time.sleep(1)
- output = check_output(*networkctl_cmd, 'lldp', env=env)
+ output = networkctl('lldp')
print(output)
if re.search(r'veth99 .* veth-peer', output):
break
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
- output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env)
+ output = resolvectl('dns', 'veth99')
print(output)
self.assertRegex(output, 'fe80::')
self.assertRegex(output, '2002:da8:1::1')
- output = check_output(*resolvectl_cmd, 'domain', 'veth99', env=env)
+ output = resolvectl('domain', 'veth99')
print(output)
self.assertIn('hogehoge.test', output)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, '2002:da8:1:0')
self.teardown_nftset('addr6', 'network6', 'ifindex')
- def test_ipv6_token_static(self):
- copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
- start_networkd()
+ def check_ipv6_token_static(self):
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, '2002:da8:1:0:1a:2b:3c:4d')
self.assertRegex(output, '2002:da8:1:0:fa:de:ca:fe')
self.assertRegex(output, '2002:da8:2:0:1a:2b:3c:4d')
self.assertRegex(output, '2002:da8:2:0:fa:de:ca:fe')
+ def test_ipv6_token_static(self):
+ copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
+ start_networkd()
+
+ self.check_ipv6_token_static()
+
+ for _ in range(20):
+ check_output('ip link set veth99 down')
+ check_output('ip link set veth99 up')
+
+ self.check_ipv6_token_static()
+
+ for _ in range(20):
+ check_output('ip link set veth99 down')
+ time.sleep(random.uniform(0, 0.1))
+ check_output('ip link set veth99 up')
+ time.sleep(random.uniform(0, 0.1))
+
+ self.check_ipv6_token_static()
+
def test_ipv6_token_prefixstable(self):
copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network')
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e', output)
self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc', output) # EUI64
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e', output)
self.assertIn('2002:da8:2:0:f689:561a:8eda:7443', output)
self.wait_online(['client:routable'])
self.wait_address('client', '2002:da8:1:99:1034:56ff:fe78:9a00/64', ipv='-6', timeout_sec=10)
- output = check_output(*networkctl_cmd, 'status', 'client', env=env)
+ output = networkctl_status('client')
print(output)
self.assertIn('Captive Portal: http://systemd.io', output)
self.wait_online(['client:routable'])
self.wait_address('client', '2002:da8:1:99:1034:56ff:fe78:9a00/64', ipv='-6', timeout_sec=10)
- output = check_output(*networkctl_cmd, 'status', 'client', env=env)
+ output = networkctl_status('client')
print(output)
self.assertNotIn('Captive Portal:', output)
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertIn('Gateway: 192.168.5.3', output)
self.assertRegex(output, 'DNS: 192.168.5.1\n *192.168.5.10')
self.assertRegex(output, 'NTP: 192.168.5.1\n *192.168.5.11')
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env)
+ output = networkctl_status('veth-peer')
self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*")
def test_dhcp_server_null_server_address(self):
client_address = json.loads(output)[0]['addr_info'][0]['local']
print(client_address)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, rf'Address: {client_address} \(DHCP4 via {server_address}\)')
self.assertIn(f'Gateway: {server_address}', output)
self.assertIn(f'DNS: {server_address}', output)
self.assertIn(f'NTP: {server_address}', output)
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env)
+ output = networkctl_status('veth-peer')
self.assertIn(f'Offered DHCP leases: {client_address}', output)
def test_dhcp_server_with_uplink(self):
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertIn('Gateway: 192.168.5.3', output)
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCP4 via 192.168.5.1\)')
self.assertIn('Gateway: 192.168.5.1', output)
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertIn('Address: 10.1.1.200 (DHCP4 via 10.1.1.1)', output)
self.assertIn('DHCP4 Client ID: 12:34:56:78:9a:bc', output)
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertIn('Address: 10.1.1.200 (DHCP4 via 10.1.1.1)', output)
self.assertRegex(output, 'DHCP4 Client ID: IAID:[0-9a-z]*/DUID')
self.wait_online(['client:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'client', env=env)
+ output = networkctl_status('client')
print(output)
self.assertRegex(output, r'Address: 192.168.5.150 \(DHCP4 via 192.168.5.1\)')
+ def test_replay_agent_on_bridge(self):
+ copy_network_unit('25-agent-bridge.netdev',
+ '25-agent-veth-client.netdev',
+ '25-agent-bridge.network',
+ '25-agent-bridge-port.network',
+ '25-agent-client.network')
+ start_networkd()
+ self.wait_online(['bridge-relay:routable', 'client-peer:enslaved'])
+
+ # For issue #30763.
+ expect = 'bridge-relay: DHCPv4 server: STARTED'
+ for _ in range(20):
+ if expect in read_networkd_log():
+ break
+ time.sleep(0.5)
+ else:
+ self.fail()
+
class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
def setUp(self):
self.assertNotIn('DHCPREPLY(veth-peer)', output)
# Check json format
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- check_json(output)
+ check_json(networkctl_json('veth99'))
# solicit mode
stop_dnsmasq()
self.assertRegex(output, 'token :: dev veth99')
# Make manager and link state file updated
- check_output(*resolvectl_cmd, 'revert', 'veth99', env=env)
+ resolvectl('revert', 'veth99')
# Check link state file
print('## link state file')
self.assertIn('sent size: 0 option: 14 rapid-commit', output)
# Check json format
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- check_json(output)
+ check_json(networkctl_json('veth99'))
# Testing without rapid commit support
with open(os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8') as f:
self.assertRegex(output, 'via fe80::1034:56ff:fe78:9abd')
# Make manager and link state file updated
- check_output(*resolvectl_cmd, 'revert', 'veth99', env=env)
+ resolvectl('revert', 'veth99')
# Check link state file
print('## link state file')
self.assertNotIn('rapid-commit', output)
# Check json format
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- check_json(output)
+ check_json(networkctl_json('veth99'))
def test_dhcp_client_ipv6_dbus_status(self):
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network')
# Test renew command
# See https://github.com/systemd/systemd/pull/29472#issuecomment-1759092138
- check_output(*networkctl_cmd, 'renew', 'veth99', env=env)
+ networkctl('renew', 'veth99')
for _ in range(100):
state = get_dhcp4_client_state('veth99')
self.assertIn('DOMAINS=example.com', output)
print('## json')
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- j = json.loads(output)
+ j = json.loads(networkctl_json('veth99'))
self.assertEqual(len(j['DNS']), 2)
for i in j['DNS']:
self.assertIn('DOMAINS=foo.example.com', output)
print('## json')
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth99', env=env)
- j = json.loads(output)
+ j = json.loads(networkctl_json('veth99'))
self.assertEqual(len(j['DNS']), 3)
for i in j['DNS']:
self.assertNotRegex(output, r'8.8.8.8 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024')
self.assertNotRegex(output, r'9.9.9.9 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024')
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
def test_dhcp_client_settings_anonymize(self):
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-anonymize.network')
start_dnsmasq()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
- output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertRegex(output, '192.168.5')
self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
# make resolved re-read the link state file
- check_output(*resolvectl_cmd, 'revert', 'veth99', env=env)
+ resolvectl('revert', 'veth99')
- output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env)
+ output = resolvectl('dns', 'veth99')
print(output)
if ipv4:
self.assertIn('192.168.5.1', output)
else:
self.assertNotIn('2600::1', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4')
self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
- output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
if ipv4 or ipv6:
self.assertIn('Captive Portal: http://systemd.io', output)
else:
self.assertNotIn('Captive Portal: http://systemd.io', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4')
self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
- output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
+ output = networkctl_status('veth99')
print(output)
self.assertNotIn('Captive Portal: ', output)
self.assertNotIn('invalid/url', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
self.assertIn('inet6 2001:db8:0:2:fa:de:ca:fe', output)
self.assertNotIn('inet6 2001:db8:0:3:', output)
- output = check_output(*resolvectl_cmd, 'dns', 'veth-peer', env=env)
+ output = resolvectl('dns', 'veth-peer')
print(output)
self.assertRegex(output, '2001:db8:1:1::2')
- output = check_output(*resolvectl_cmd, 'domain', 'veth-peer', env=env)
+ output = resolvectl('domain', 'veth-peer')
print(output)
self.assertIn('example.com', output)
- output = check_output(*networkctl_cmd, '--json=short', 'status', env=env)
- check_json(output)
+ check_json(networkctl_json())
- output = check_output(*networkctl_cmd, '--json=short', 'status', 'veth-peer', env=env)
+ output = networkctl_json('veth-peer')
check_json(output)
# PREF64 or NAT64
self.assertNotIn('inet6 2001:db8:0:1:', output)
self.assertIn('inet6 2001:db8:0:2:', output)
- output = check_output(*resolvectl_cmd, 'dns', 'veth-peer', env=env)
+ output = resolvectl('dns', 'veth-peer')
print(output)
self.assertRegex(output, '2001:db8:1:1::2')
- output = check_output(*resolvectl_cmd, 'domain', 'veth-peer', env=env)
+ output = resolvectl('domain', 'veth-peer')
print(output)
self.assertIn('example.com', output)
test "$(readlink "$1")" = "$2" || return 2
}
-: '------enable nonexistent------------------------------------'
+: '-------enable nonexistent--------------------------------------'
( ! "$systemctl" --root="$root" enable test1.service )
-: '------basic enablement--------------------------------------'
+: '-------basic enablement----------------------------------------'
mkdir -p "$root/etc/systemd/system"
cat >"$root/etc/systemd/system/test1.service" <<EOF
[Install]
test ! -h "$root/etc/systemd/system/default.target.wants/test1.service"
test ! -h "$root/etc/systemd/system/special.target.requires/test1.service"
-: '------enable when link already exists-----------------------'
+: '-------enable when link already exists-------------------------'
# We don't read the symlink target, so it's OK for the symlink to point
# to something else. We should just silently accept this.
test ! -h "$root/etc/systemd/system/default.target.wants/test1.service"
test ! -h "$root/etc/systemd/system/special.target.requires/test1.service"
-: '------suffix guessing---------------------------------------'
+: '-------suffix guessing-----------------------------------------'
"$systemctl" --root="$root" enable test1
test -h "$root/etc/systemd/system/default.target.wants/test1.service"
test -h "$root/etc/systemd/system/special.target.requires/test1.service"
test ! -e "$root/etc/systemd/system/default.target.wants/test1.service"
test ! -e "$root/etc/systemd/system/special.target.requires/test1.service"
-: '-------aliases----------------------------------------------'
+: '-------aliases-------------------------------------------------'
cat >>"$root/etc/systemd/system/test1.service" <<EOF
Alias=test1-goodalias.service
Alias=test1@badalias.service
test ! -e "$root/etc/systemd/system/test1-badalias.socket"
test -h "$root/etc/systemd/system/test1-goodalias2.service"
-: '-------aliases in reenable----------------------------------'
+: '-------aliases in reenable-------------------------------------'
( ! "$systemctl" --root="$root" reenable test1 )
test -h "$root/etc/systemd/system/default.target.wants/test1.service"
test ! -e "$root/etc/systemd/system/test1-goodalias.service"
test ! -e "$root/etc/systemd/system/special.target.requires/test1.service"
test ! -e "$root/etc/systemd/system/test1-goodalias.service"
-: '-------aliases when link already exists---------------------'
+: '-------aliases when link already exists------------------------'
cat >"$root/etc/systemd/system/test1a.service" <<EOF
[Install]
Alias=test1a-alias.service
"$systemctl" --root="$root" disable test1a.service
test ! -h "$root/etc/systemd/system/test1a-alias.service"
-: '-------also units-------------------------------------------'
+: '-------also units----------------------------------------------'
cat >"$root/etc/systemd/system/test2.socket" <<EOF
[Install]
WantedBy=sockets.target
test ! -e "$root/etc/systemd/system/sockets.target.wants/test2.socket"
-: '-------link-------------------------------------------------'
+: '-------link----------------------------------------------------'
# File doesn't exist yet
test ! -e "$root/link1.path"
( ! "$systemctl" --root="$root" link '/link1.path' )
"$systemctl" --root="$root" link '/link1.path'
islink "$root/etc/systemd/system/link1.path" "/link1.path"
-: '-------link already linked same path------------------------'
+: '-------link already linked same path---------------------------'
SYSTEMD_LOG_LEVEL=debug "$systemctl" --root="$root" link '/link1.path' # this passes
islink "$root/etc/systemd/system/link1.path" "/link1.path"
-: '-------link already linked different path-------------------'
+: '-------link already linked different path----------------------'
mkdir "$root/subdir"
cp "$root/link1.path" "$root/subdir/"
( ! "$systemctl" --root="$root" link '/subdir/link1.path' )
islink "$root/etc/systemd/system/link1.path" "/link1.path"
-: '-------link bad suffix--------------------------------------'
+: '-------link bad suffix-----------------------------------------'
cp "$root/link1.path" "$root/subdir/link1.suffix"
( ! "$systemctl" --root="$root" link '/subdir/link1.suffix' )
test ! -e "$root/etc/systemd/system/link1.suffix"
-: '-------unlink by unit name----------------------------------'
+: '-------unlink by unit name-------------------------------------'
"$systemctl" --root="$root" disable 'link1.path'
test ! -e "$root/etc/systemd/system/link1.path"
-: '-------unlink by path---------------------------------------'
+: '-------unlink by path------------------------------------------'
"$systemctl" --root="$root" link '/link1.path'
test -h "$root/etc/systemd/system/link1.path"
"$systemctl" --root="$root" disable '/link1.path'
test ! -e "$root/etc/systemd/system/link1.path"
-: '-------unlink by wrong path---------------------------------'
+: '-------unlink by wrong path------------------------------------'
"$systemctl" --root="$root" link '/link1.path'
test -h "$root/etc/systemd/system/link1.path"
"$systemctl" --root="$root" disable '/subdir/link1.path' # we only care about the name
test ! -e "$root/etc/systemd/system/link1.path"
-: '-------link and enable--------------------------------------'
+: '-------link and enable-----------------------------------------'
"$systemctl" --root="$root" enable '/link1.path'
islink "$root/etc/systemd/system/link1.path" "/link1.path"
islink "$root/etc/systemd/system/paths.target.wants/link1.path" "/link1.path"
-: '-------enable already linked same path----------------------'
+: '-------enable already linked same path-------------------------'
"$systemctl" --root="$root" enable '/link1.path'
islink "$root/etc/systemd/system/link1.path" "/link1.path"
islink "$root/etc/systemd/system/paths.target.wants/link1.path" "/link1.path"
-: '-------enable already linked different path-----------------'
+: '-------enable already linked different path--------------------'
( ! "$systemctl" --root="$root" enable '/subdir/link1.path' )
islink "$root/etc/systemd/system/link1.path" "/link1.path"
islink "$root/etc/systemd/system/paths.target.wants/link1.path" "/link1.path"
-: '-------enable bad suffix------------------------------------'
+: '-------enable bad suffix---------------------------------------'
cp "$root/link1.path" "$root/subdir/link1.suffix"
( ! "$systemctl" --root="$root" enable '/subdir/link1.suffix' )
test ! -e "$root/etc/systemd/system/link1.suffix"
test ! -e "$root/etc/systemd/system/paths.target.wants/link1.suffix"
-: '-------disable by unit name---------------------------------'
+: '-------disable by unit name------------------------------------'
"$systemctl" --root="$root" disable 'link1.path'
test ! -e "$root/etc/systemd/system/link1.path"
test ! -e "$root/etc/systemd/system/paths.target.wants/link1.path"
-: '-------disable by path--------------------------------------'
+: '-------disable by path-----------------------------------------'
"$systemctl" --root="$root" enable '/link1.path'
test -h "$root/etc/systemd/system/link1.path"
test -h "$root/etc/systemd/system/paths.target.wants/link1.path"
test ! -e "$root/etc/systemd/system/paths.target.wants/link1.path"
-: '-------link and enable-------------------------------------'
+: '-------link and enable-----------------------------------------'
"$systemctl" --root="$root" link '/link1.path'
islink "$root/etc/systemd/system/link1.path" "/link1.path"
test ! -h "$root/etc/systemd/system/paths.target.wants/link1.path"
islink "$root/etc/systemd/system/link1.path" "/link1.path"
islink "$root/etc/systemd/system/paths.target.wants/link1.path" "/link1.path"
-: '-------link instance and enable-------------------------------------'
+: '-------link instance and enable--------------------------------'
cat >"$root/link-instance@.service" <<EOF
[Service]
ExecStart=true
test ! -h "$root/etc/systemd/system/link-instance@first.service"
test ! -h "$root/etc/systemd/system/services.target.wants/link-instance@first.service"
-: '-------manual link------------------------------------------'
+: '-------manual link---------------------------------------------'
cat >"$root/link3.suffix" <<EOF
[Install]
WantedBy=services.target
test ! -h "$root/etc/systemd/system/link3.service"
test ! -h "$root/etc/systemd/system/services.target.wants/link3.service"
-: '-------enable on masked-------------------------------------'
+: '-------enable on masked----------------------------------------'
ln -s "/dev/null" "$root/etc/systemd/system/masked.service"
( ! "$systemctl" --root="$root" enable 'masked.service' )
( ! "$systemctl" --root="$root" enable '/etc/systemd/system/masked.service' )
-: '-------enable on masked alias-------------------------------'
+: '-------enable on masked alias----------------------------------'
test -h "$root/etc/systemd/system/masked.service"
ln -s "masked.service" "$root/etc/systemd/system/masked-alias.service"
( ! "$systemctl" --root="$root" enable 'masked-alias.service' )
( ! "$systemctl" --root="$root" enable '/etc/systemd/system/masked-alias.service' )
-: '-------issue 22000: link in subdirectory--------------------'
+: '-------issue 22000: link in subdirectory-----------------------'
mkdir -p "$root/etc/systemd/system/myown.d"
cat >"$root/etc/systemd/system/link5-also.service" <<EOF
[Install]
test ! -h "$root/etc/systemd/system/services.target.wants/link5.service"
islink "$root/etc/systemd/system/services.target.wants/link5-also.service" "/etc/systemd/system/link5-also.service"
-: '-------template enablement----------------------------------'
+: '-------template enablement-------------------------------------'
cat >"$root/etc/systemd/system/templ1@.service" <<EOF
[Install]
WantedBy=services.target
test ! -h "$root/etc/systemd/system/services.target.wants/templ1@one.service"
test ! -h "$root/etc/systemd/system/services.target.wants/templ1@two.service"
-: '-------template enablement w/ default instance--------------'
+: '-------template enablement w/ default instance-----------------'
cat >"$root/etc/systemd/system/templ1@.service" <<EOF
[Install]
# check enablement with
test ! -h "$root/etc/systemd/system/services.target.wants/templ1@two.service"
test ! -h "$root/etc/systemd/system/other@templ1.target.requires/templ1@two.service"
-: '-------removal of relative enablement symlinks--------------'
+: '-------removal of relative enablement symlinks-----------------'
test ! -h "$root/etc/systemd/system/services.target.wants/templ1@.service"
ln -s '../templ1@one.service' "$root/etc/systemd/system/services.target.wants/templ1@one.service"
ln -s 'templ1@two.service' "$root/etc/systemd/system/services.target.wants/templ1@two.service"
test ! -h "$root/etc/systemd/system/services.target.wants/templ1@six.service"
test ! -h "$root/etc/systemd/system/services.target.wants/templ1@seven.service"
-: '-------template enablement for another template-------------'
+: '-------template enablement for another template----------------'
cat >"$root/etc/systemd/system/templ2@.service" <<EOF
[Install]
RequiredBy=another-template@.target
test ! -h "$root/etc/systemd/system/another-template@.target.requires/templ2@.service"
test ! -h "$root/etc/systemd/system/another-template@.target.requires/templ2@two.service"
-: '-------aliases w/ and w/o instance--------------------------'
+: '-------aliases w/ and w/o instance-----------------------------'
test ! -e "$root/etc/systemd/system/link4.service"
cat >"$root/etc/systemd/system/link4.service" <<EOF
[Install]
test ! -h "$root/etc/systemd/system/link4alias.service"
test ! -h "$root/etc/systemd/system/link4alias2.service"
-: '-------systemctl enable on path to unit file----------------'
+: '-------systemctl enable on path to unit file-------------------'
cat >"$root/etc/systemd/system/link4.service" <<EOF
[Install]
Alias=link4alias.service
test ! -h "$root/etc/systemd/system/link4alias.service"
test ! -h "$root/etc/systemd/system/link4alias2.service"
-: '-------issue 661: enable on unit file--------------'
+: '-------issue 661: enable on unit file--------------------------'
test ! -e "$root/etc/systemd/system/link5.service"
cat >"$root/etc/systemd/system/link5.service" <<EOF
[Install]
test ! -h "$root/etc/systemd/system/link5alias.service"
test ! -h "$root/etc/systemd/system/link5alias2.service"
-: '-------issue 661: link and enable on unit file--------------'
+: '-------issue 661: link and enable on unit file-----------------'
test ! -e "$root/etc/systemd/system/link5copy.service"
cat >"$root/link5copy.service" <<EOF
[Install]
test ! -h "$root/etc/systemd/system/link5alias.service"
test ! -h "$root/etc/systemd/system/link5alias2.service"
-: '----issue 19437: plain templates in .wants/ or .requires/---'
+: '-------issue 19437: plain templates in .wants/ or .requires/---'
test ! -e "$root/etc/systemd/system/link5@.path"
cat >"$root/etc/systemd/system/link5@.path" <<EOF
[Install]
test ! -h "$root/etc/systemd/system/target5@inst.target.wants/link5@.path"
test ! -h "$root/etc/systemd/system/target5@inst.target.requires/link5@.path"
-: '-------removal of symlinks not listed in [Install]----------'
+: '-------removal of symlinks not listed in [Install]-------------'
# c.f. 66a19d85a533b15ed32f4066ec880b5a8c06babd
test ! -e "$root/etc/systemd/system/multilink.mount"
cat >"$root/etc/systemd/system/multilink.mount" <<EOF
test ! -h "$root/etc/systemd/system/multilink-alias.mount"
test ! -h "$root/etc/systemd/system/multilink-badalias.service"
-: '-------merge 20017: specifiers in the unit file-------------'
+: '-------merge 20017: specifiers in the unit file----------------'
test ! -e "$root/etc/systemd/system/some-some-link6@.socket"
# c.f. de61a04b188f81a85cdb5c64ddb4987dcd9d30d3
# %z is not defined
( ! check_alias z 'z' )
-: '-------specifiers in WantedBy-------------------------------'
+: '-------specifiers in WantedBy----------------------------------'
# We don't need to repeat all the tests. Let's do a basic check that specifier
# expansion is performed.
# TODO: repeat the tests above for presets
-: '-------SYSTEMD_OS_RELEASE relative to root-------------------'
+: '-------SYSTEMD_OS_RELEASE relative to root---------------------'
# check that os-release overwriting works as expected with root
test -e "$root/etc/os-release"
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Verbose successful service
+
+[Service]
+ExecStart=/bin/echo success
After=testsuite-23-bound-by.service
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
# --kill-who= (no 'm') to check that the short form is accepted
ExecStopPost=systemctl kill --kill-whom=main -sRTMIN+1 testsuite-23.service
Description=Unit with BoundBy=
[Service]
-ExecStart=/bin/sleep 0.7
+ExecStart=sleep 0.7
OnFailure=testsuite-23-uphold.service
[Service]
-ExecStart=/bin/false
+ExecStart=false
NotifyAccess=all
MountAPIVFS=yes
PrivateTmp=yes
-ExecStart=/bin/bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity'
+ExecStart=bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity'
NotifyAccess=all
MountAPIVFS=yes
PrivateTmp=yes
-ExecStart=/bin/bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity'
+ExecStart=bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity'
NotifyAccess=all
MountAPIVFS=yes
PrivateTmp=yes
-ExecStart=/bin/bash -c 'touch /tmp/shared-private-file-x && systemd-notify --ready && sleep infinity'
+ExecStart=bash -c 'touch /tmp/shared-private-file-x && systemd-notify --ready && sleep infinity'
PrivateTmp=yes
ExecStartPre=test -e /tmp/shared-private-file-x
ExecStartPre=test -e /tmp/hoge
-ExecStart=/bin/bash -c 'touch /tmp/shared-private-file-y && systemd-notify --ready && sleep infinity'
+ExecStart=bash -c 'touch /tmp/shared-private-file-y && systemd-notify --ready && sleep infinity'
BindPaths=/run/testsuite-23-marker-fixed:/tmp/testfile-marker-fixed
InaccessiblePaths=/run/inaccessible
ExecStartPre=grep -q -F MARKER_FIXED /tmp/testfile-marker-fixed
-ExecStart=/bin/sh -c 'systemd-notify --ready; until grep -q -F MARKER_RUNTIME /tmp/testfile-marker-runtime; do sleep 0.1; done; test ! -f /run/inaccessible/testfile-marker-fixed'
+ExecStart=sh -c 'systemd-notify --ready; until grep -q -F MARKER_RUNTIME /tmp/testfile-marker-runtime; do sleep 0.1; done; test ! -f /run/inaccessible/testfile-marker-fixed'
RuntimeMaxSec=5
Type=notify
RemainAfterExit=yes
-ExecStart=/bin/sh -c 'systemd-notify --ready; until grep -q -F MARKER_RUNTIME /tmp/testfile-marker-runtime; do sleep 0.1; done; exit 0'
+ExecStart=sh -c 'systemd-notify --ready; until grep -q -F MARKER_RUNTIME /tmp/testfile-marker-runtime; do sleep 0.1; done; exit 0'
StopPropagatedFrom=testsuite-23-prop-stop-two.service
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
ExecStopPost=systemctl kill --kill-whom=main -sUSR2 testsuite-23.service
Description=Stop Propagation Sender
[Service]
-ExecStart=/bin/sleep 1.5
+ExecStart=sleep 1.5
[Service]
Type=oneshot
RemainAfterExit=yes
-ExecStart=/bin/sh -c "if [ -f /tmp/testsuite-23-retry-fail ]; then exit 0; else exit 1; fi"
+ExecStart=sh -c "if [ -f /tmp/testsuite-23-retry-fail ]; then exit 0; else exit 1; fi"
Restart=no
[Service]
Type=oneshot
RemainAfterExit=yes
-ExecStart=/bin/echo ok
+ExecStart=echo ok
Upholds=testsuite-23-retry-upheld.service
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
OnSuccess=testsuite-23-fail.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Description=Unit that sets UpheldBy= through [Install]
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
[Install]
UpheldBy=testsuite-23-retry-uphold.service
Upholds=testsuite-23-short-lived.service
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
Before=a.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Before=b.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Wants=f.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Requires=a.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Requires=a.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=parent.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
CPUAccounting=true
[Service]
Slice=dml-discard.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=dml-discard.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
MemoryLow=15
[Service]
Slice=dml-override.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=dml-passthrough.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=dml-passthrough.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
DefaultMemoryLow=15
[Service]
Slice=dml-passthrough.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
MemoryLow=0
Wants=a.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
Description=F
[Service]
-ExecStart=/bin/true
+ExecStart=true
Conflicts=e.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
[Service]
Slice=parent-deep.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
Wants=g.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
After=b.service
[Service]
-ExecStart=/bin/true
+ExecStart=true
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/true
+ExecStart=true
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/true
+ExecStart=true
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/true
+ExecStart=true
[Unit]
Conflicts=loopy4.service
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/true
+ExecStart=true
[Unit]
Conflicts=loopy4.service
[Service]
Slice=nomem.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
IOWeight=200
MemoryAccounting=true
Description=Bad sched priority for Idle
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPriority=1
Description=Sched idle with prio 0
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPriority=0
Description=Bad sched priority for RR
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPriority=-1
CPUSchedulingPriority=100
CPUSchedulingPolicy=rr
Description=Change prio
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPriority=1
CPUSchedulingPriority=2
CPUSchedulingPriority=99
Description=Default prio for RR
[Service]
-ExecStart=/bin/true
+ExecStart=true
CPUSchedulingPolicy=rr
[Service]
Slice=parent.slice
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
CPUShares=100
[Unit]
Description=Wait for 2 seconds
[Service]
-ExecStart=/bin/sh -ec 'sleep 2'
+ExecStart=sh -ec 'sleep 2'
EOF
cat <<EOF >/run/systemd/system/wait5fail.service
[Unit]
Description=Wait for 5 seconds and fail
[Service]
-ExecStart=/bin/sh -ec 'sleep 5; false'
+ExecStart=sh -ec 'sleep 5; false'
EOF
# wait2 succeeds
journalctl --sync
[[ -z "$(journalctl -b -q -u silent-success.service)" ]]
+# Test syslog identifiers exclusion
+systemctl start verbose-success.service
+timeout 30 bash -xec 'while systemctl -q is-active verbose-success.service; do sleep 1; done'
+journalctl --sync
+[[ -n "$(journalctl -b -q -u verbose-success.service -t systemd)" ]]
+[[ -n "$(journalctl -b -q -u verbose-success.service -t echo)" ]]
+[[ -n "$(journalctl -b -q -u verbose-success.service -T systemd)" ]]
+[[ -n "$(journalctl -b -q -u verbose-success.service -T echo)" ]]
+[[ -z "$(journalctl -b -q -u verbose-success.service -T echo -T '(echo)' -T systemd -T '(systemd)' -T systemd-executor)" ]]
+
# Exercise the matching machinery
SYSTEMD_LOG_LEVEL=debug journalctl -b -n 1 /dev/null /dev/zero /dev/null /dev/null /dev/null
journalctl -b -n 1 /bin/true /bin/false
bash -xec "mountpoint /etc; test -d /etc/systemd; mountpoint /mnt; ! mountpoint /usr"
systemd-run --wait --pipe -p BindReadOnlyPaths="/etc /home:/mnt:norbind -/foo/bar/baz:/usr:rbind" \
bash -xec "test ! -w /etc; test ! -w /mnt; ! mountpoint /usr"
+# Make sure we properly serialize/deserialize paths with spaces
+# See: https://github.com/systemd/systemd/issues/30747
+touch "/tmp/test file with spaces"
+systemd-run --wait --pipe -p TemporaryFileSystem="/tmp" -p BindPaths="/etc /home:/mnt:norbind /tmp/test\ file\ with\ spaces" \
+ bash -xec "mountpoint /etc; test -d /etc/systemd; mountpoint /mnt; stat '/tmp/test file with spaces'"
+systemd-run --wait --pipe -p TemporaryFileSystem="/tmp" -p BindPaths="/etc /home:/mnt:norbind /tmp/test\ file\ with\ spaces:/tmp/destination\ wi\:th\ spaces" \
+ bash -xec "mountpoint /etc; test -d /etc/systemd; mountpoint /mnt; stat '/tmp/destination wi:th spaces'"
# Check if we correctly serialize, deserialize, and set directives that
# have more complex internal handling
# {Cache,Configuration,Logs,Runtime,State}Directory=
ARGUMENTS=(
- -p CacheDirectory="foo/bar/baz"
+ -p CacheDirectory="foo/bar/baz also\ with\ spaces"
-p CacheDirectory="foo"
-p CacheDirectory="context"
-p CacheDirectoryMode="0123"
-p CacheDirectoryMode="0666"
- -p ConfigurationDirectory="context/foo also_context/bar context/nested/baz"
+ -p ConfigurationDirectory="context/foo also_context/bar context/nested/baz context/semi\:colon"
-p ConfigurationDirectoryMode="0400"
-p LogsDirectory="context/foo"
-p LogsDirectory=""
-p LogsDirectory="context/a/very/nested/logs/dir"
- -p RuntimeDirectory="context"
- -p RuntimeDirectory="also_context"
+ -p RuntimeDirectory="context/with\ spaces"
+ # Note: {Runtime,State,Cache,Logs}Directory= directives support the directory:symlink syntax, which
+ # requires an additional level of escaping for the colon character
+ -p RuntimeDirectory="also_context:a\ symlink\ with\ \\\:\ col\\\:ons\ and\ \ spaces"
-p RuntimeDirectoryPreserve=yes
-p StateDirectory="context"
-p StateDirectory="./././././././context context context"
rm -rf /run/context
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
- bash -xec '[[ $CACHE_DIRECTORY == /var/cache/context:/var/cache/foo:/var/cache/foo/bar/baz ]];
- [[ $(stat -c "%a" ${CACHE_DIRECTORY##*:}) == 666 ]]'
+ bash -xec '[[ $CACHE_DIRECTORY == "/var/cache/also with spaces:/var/cache/context:/var/cache/foo:/var/cache/foo/bar/baz" ]];
+ [[ $(stat -c "%a" "${CACHE_DIRECTORY##*:}") == 666 ]]'
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
- bash -xec '[[ $CONFIGURATION_DIRECTORY == /etc/also_context/bar:/etc/context/foo:/etc/context/nested/baz ]];
- [[ $(stat -c "%a" ${CONFIGURATION_DIRECTORY##*:}) == 400 ]]'
+ bash -xec '[[ $CONFIGURATION_DIRECTORY == /etc/also_context/bar:/etc/context/foo:/etc/context/nested/baz:/etc/context/semi:colon ]];
+ [[ $(stat -c "%a" "${CONFIGURATION_DIRECTORY%%:*}") == 400 ]]'
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
bash -xec '[[ $LOGS_DIRECTORY == /var/log/context/a/very/nested/logs/dir:/var/log/context/foo ]];
- [[ $(stat -c "%a" ${LOGS_DIRECTORY##*:}) == 755 ]]'
+ [[ $(stat -c "%a" "${LOGS_DIRECTORY##*:}") == 755 ]]'
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
- bash -xec '[[ $RUNTIME_DIRECTORY == /run/also_context:/run/context ]];
- [[ $(stat -c "%a" ${RUNTIME_DIRECTORY##*:}) == 755 ]];
- [[ $(stat -c "%a" ${RUNTIME_DIRECTORY%%:*}) == 755 ]]'
+ bash -xec '[[ $RUNTIME_DIRECTORY == "/run/also_context:/run/context/with spaces" ]];
+ [[ $(stat -c "%a" "${RUNTIME_DIRECTORY##*:}") == 755 ]];
+ [[ $(stat -c "%a" "${RUNTIME_DIRECTORY%%:*}") == 755 ]]'
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
bash -xec '[[ $STATE_DIRECTORY == /var/lib/context ]]; [[ $(stat -c "%a" $STATE_DIRECTORY) == 0 ]]'
-test -d /run/context
+test -d "/run/context/with spaces"
+test -s "/run/a symlink with : col:ons and spaces"
rm -rf /var/{cache,lib,log}/context /etc/{also_,}context
# Limit*=
# Add one new ExecStart= before the existing ones.
#
# Since, after reload, we should continue running from the "sleep 3" statement, the newly added "echo
- # bar" one will have no efect and we should end up with the same output as in the previous case.
+ # bar" one will have no effect and we should end up with the same output as in the previous case.
cat >"$unit_path" <<EOF
[Service]
Type=oneshot
# Remove the currently executed ExecStart= line.
#
- # In this case we completely drop the currently excuted "sleep 3" statement, so after reload systemd
+ # In this case we completely drop the currently executed "sleep 3" statement, so after reload systemd
# should complain that the currently executed command vanished and simply finish executing the unit,
# resulting in an empty log.
cat >"$unit_path" <<EOF
ExecStartPre=sh -c 'test "$TRIGGER_UNIT" = my.timer'
ExecStartPre=sh -c 'test -n "$TRIGGER_TIMER_REALTIME_USEC"'
ExecStartPre=sh -c 'test -n "$TRIGGER_TIMER_MONOTONIC_USEC"'
-ExecStart=/bin/echo Timer runs me
+ExecStart=echo Timer runs me
EOF
cat >/run/systemd/system/my.timer <<EOF
Description=Test service
[Service]
StandardInput=socket
-ExecStart=/bin/sh -x -c cat
+ExecStart=sh -x -c cat
EOF
systemctl start issue-3171.socket
systemd-analyze log-level debug
-cat > /run/systemd/system/floodme@.service <<EOF
+cat >/run/systemd/system/floodme@.service <<EOF
[Service]
-ExecStart=/bin/true
+ExecStart=true
EOF
-cat > /run/systemd/system/floodme.socket <<EOF
+cat >/run/systemd/system/floodme.socket <<EOF
[Socket]
ListenStream=/tmp/floodme
PollLimitIntervalSec=10s
START=$(date +%s%N)
# Trigger this 100 times in a flood
-for (( i=0 ; i < 100; i++ )) ; do
+for _ in {1..100}; do
logger -u /tmp/floodme foo &
done
TMPDIR=
TEST_RULE="/run/udev/rules.d/49-test.rules"
+TEST_CONF="/run/udev/udev.conf.d/test-17.conf"
KILL_PID=
setup() {
mkdir -p "${TEST_RULE%/*}"
- [[ -e /etc/udev/udev.conf ]] && cp -f /etc/udev/udev.conf /etc/udev/udev.conf.bak
+ mkdir -p /run/udev/udev.conf.d
cat >"${TEST_RULE}" <<EOF
ACTION!="add", GOTO="test_end"
LABEL="test_end"
EOF
- cat >/etc/udev/udev.conf <<EOF
+ cat >"$TEST_CONF" <<EOF
event_timeout=10
timeout_signal=SIGABRT
EOF
fi
rm -rf "$TMPDIR"
- rm -f "$TEST_RULE"
- [[ -e /etc/udev/udev.conf.bak ]] && mv -f /etc/udev/udev.conf.bak /etc/udev/udev.conf
+ rm -f "$TEST_RULE" "$TEST_CONF"
systemctl restart systemd-udevd.service
}
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
+udevadm control --log-level=debug
+
+mkdir -p /run/systemd/network/
+cat >/run/systemd/network/10-test.link <<EOF
+[Match]
+Kind=dummy
+MACAddress=00:50:56:c0:00:19
+
+[Link]
+Name=test1
+EOF
+
+mkdir /run/systemd/network/10-test.link.d
+cat >/run/systemd/network/10-test.link.d/10-override.conf <<EOF
+[Link]
+Property=HOGE=foo BAR=baz SHOULD_BE_UNSET=unset
+UnsetProperty=SHOULD_BE_UNSET
+EOF
+
+udevadm control --reload
+
+ip link add address 00:50:56:c0:00:19 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/test1
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_in "HOGE=foo" "$output"
+assert_in "BAR=baz" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+cat >/run/systemd/network/10-test.link.d/11-override.conf <<EOF
+[Link]
+Property=
+Property=HOGE2=foo2 BAR2=baz2 SHOULD_BE_UNSET=unset
+ImportProperty=HOGE
+EOF
+
+udevadm control --reload
+
+udevadm trigger --settle --action add /sys/class/net/test1
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_in "HOGE=foo" "$output"
+assert_in "HOGE2=foo2" "$output"
+assert_not_in "BAR=" "$output"
+assert_in "BAR2=baz2" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+# On change event, .link file will not be applied.
+udevadm trigger --settle --action change /sys/class/net/test1
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+### testing with udevadm test-builtin
+output=$(udevadm test-builtin --action add net_setup_link /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_in "HOGE2=foo2" "$output"
+assert_not_in "BAR=" "$output"
+assert_in "BAR2=baz2" "$output"
+assert_in "SHOULD_BE_UNSET=" "$output" # this is expected, as an empty assignment is also logged.
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+# check that test-builtin command does not update udev database.
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+output=$(udevadm test-builtin --action change net_setup_link /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+### testing with udevadm test
+output=$(udevadm test --action add /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_in "HOGE2=foo2" "$output"
+assert_not_in "BAR=" "$output"
+assert_in "BAR2=baz2" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+# check that test command _does_ update udev database.
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_in "HOGE2=foo2" "$output"
+assert_not_in "BAR=" "$output"
+assert_in "BAR2=baz2" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+output=$(udevadm test --action change /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "HOGE=" "$output"
+assert_not_in "HOGE2=" "$output"
+assert_not_in "BAR=" "$output"
+assert_not_in "BAR2=" "$output"
+assert_not_in "SHOULD_BE_UNSET=" "$output"
+assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test.link" "$output"
+assert_in "ID_NET_LINK_FILE_DROPINS=/run/systemd/network/10-test.link.d/10-override.conf:/run/systemd/network/10-test.link.d/11-override.conf" "$output"
+assert_in "ID_NET_NAME=test1" "$output"
+
+# test for specifiers
+cat >/run/systemd/network/10-test.link.d/12-override.conf <<EOF
+[Link]
+Property=
+Property=LINK_VERSION=%v
+EOF
+
+udevadm control --reload
+
+output=$(udevadm test --action add /sys/class/net/test1)
+assert_in "LINK_VERSION=$(uname -r)" "$output"
+
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_in "LINK_VERSION=$(uname -r)" "$output"
+
+# test for constant properties
+cat >/run/systemd/network/10-test.link.d/13-override.conf <<EOF
+[Link]
+Property=
+Property=ACTION=foo IFINDEX=bar
+UnsetProperty=DEVPATH
+EOF
+
+udevadm control --reload
+
+output=$(udevadm test --action add /sys/class/net/test1)
+assert_in "ACTION=add" "$output"
+assert_not_in "ACTION=foo" "$output"
+assert_in "IFINDEX=" "$output"
+assert_not_in "IFINDEX=bar" "$output"
+assert_in "DEVPATH=" "$output"
+
+output=$(udevadm info --query property /sys/class/net/test1)
+assert_not_in "ACTION=foo" "$output"
+assert_in "IFINDEX=" "$output"
+assert_not_in "IFINDEX=bar" "$output"
+assert_in "DEVPATH=" "$output"
+
+# cleanup
+ip link del dev test1
+
+rm -f /run/systemd/network/10-test.link
+rm -rf /run/systemd/network/10-test.link.d
+udevadm control --reload --log-level=info
+
+exit 0
echo "[#1] Failing ExecReload= should not kill the service"
cat >"$SERVICE_PATH" <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
ExecReload=/bin/false
EOF
echo "[#2] Failing ExecReload= should not kill the service (multiple ExecReload=)"
cat >"$SERVICE_PATH" <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
ExecReload=/bin/true
ExecReload=/bin/false
ExecReload=/bin/true
echo "[#3] Failing ExecReload=- should not affect reload's exit code"
cat >"$SERVICE_PATH" <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
ExecReload=-/bin/false
EOF
CacheDirectory=test-service
LogsDirectory=test-service
RuntimeDirectoryPreserve=yes
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
Type=exec
EOF
CacheDirectory=test-service
LogsDirectory=test-service
RuntimeDirectoryPreserve=yes
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
Type=exec
EOF
cat >/run/systemd/system/testsuite-23-no-reload.service <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
EOF
systemctl start testsuite-23-no-reload.service
cat >/run/systemd/system/testsuite-23-no-reload.service <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
EOF
# Start a non-existing unit first, so that the cache is reloaded for an unrelated
cat >/run/systemd/system/testsuite-23-no-reload.service <<EOF
[Service]
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
EOF
systemctl restart testsuite-23-no-reload.target
if [[ -r /etc/softhsm2.conf ]]; then
# Test unlocking with a PKCS#11 token
export SOFTHSM2_CONF="/etc/softhsm2.conf"
+
PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=RSATestKey" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
cryptsetup_start_and_check empty_pkcs11_auto
cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
+
+ PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=RSATestKey;type=cert" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
+ cryptsetup_start_and_check empty_pkcs11_auto
+ cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
+ cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
+
PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=ECTestKey" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
cryptsetup_start_and_check empty_pkcs11_auto
cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
+
+ PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=ECTestKey;type=cert" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
+ cryptsetup_start_and_check empty_pkcs11_auto
+ cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
+ cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
fi
cryptsetup_start_and_check detached
systemctl stop getty@tty2.service
- for s in $(loginctl --no-legend list-sessions | awk '$3 == "logind-test-user" { print $1 }'); do
+ for s in $(loginctl --no-legend list-sessions | grep -v manager | awk '$3 == "logind-test-user" { print $1 }'); do
echo "INFO: stopping session $s"
loginctl terminate-session "$s"
done
local seat session leader_pid
- if [[ $(loginctl --no-legend | grep -c "logind-test-user") != 1 ]]; then
+ if [[ $(loginctl --no-legend | grep -v manager | grep -c "logind-test-user") != 1 ]]; then
echo "no session or multiple sessions for logind-test-user." >&2
return 1
fi
- seat=$(loginctl --no-legend | grep 'logind-test-user *seat' | awk '{ print $4 }')
+ seat=$(loginctl --no-legend | grep -v manager | grep 'logind-test-user *seat' | awk '{ print $4 }')
if [[ -z "$seat" ]]; then
echo "no seat found for user logind-test-user" >&2
return 1
fi
- session=$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')
+ session=$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')
if [[ -z "$session" ]]; then
echo "no session found for user logind-test-user" >&2
return 1
check_session && break
done
check_session
- assert_eq "$(loginctl --no-legend | awk '$3=="logind-test-user" { print $5 }')" "tty2"
+ assert_eq "$(loginctl --no-legend | grep -v manager | awk '$3=="logind-test-user" { print $7 }')" "tty2"
}
testcase_sanity_check() {
# the seat/session autodetection work-ish
systemd-run --user --pipe --wait -M "logind-test-user@.host" bash -eux <<\EOF
loginctl list-sessions
+ loginctl list-sessions -j
+ loginctl list-sessions --json=short
loginctl session-status
loginctl show-session
loginctl show-session -P DelayInhibited
udevadm info "$dev"
# trigger logind and activate session
- loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')"
+ loginctl activate "$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')"
# check ACL
sleep 1
return
fi
- if loginctl --no-legend | grep -q logind-test-user; then
+ if loginctl --no-legend | grep -v manager | grep -q logind-test-user; then
echo >&2 "Session of the 'logind-test-user' is already present."
exit 1
fi
trap cleanup_session RETURN
create_session
- s=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }')
+ s=$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')
/usr/lib/systemd/tests/unit-tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}" /dev/tty2
}
create_session
# Activate the session
- loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')"
+ loginctl activate "$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')"
- session=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }')
+ session=$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')
: check that we got a valid session id
busctl get-property org.freedesktop.login1 "/org/freedesktop/login1/session/_3${session?}" org.freedesktop.login1.Session Id
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)"
- seat=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $4 }')
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $5 }')" tty2
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $6 }')" active
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $7 }')" no
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $8 }')" '-'
+ assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)"
+ seat=$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $4 }')
+ assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $6 }')" user
+ assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $7 }')" tty2
+ assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $8 }')" no
+ assert_eq "$(loginctl list-sessions --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $9 }')" '-'
loginctl list-seats --no-legend | grep -Fwq "${seat?}"
loginctl enable-linger logind-test-user
assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $3 }')" yes
- for s in $(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }'); do
+ for s in $(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }'); do
loginctl terminate-session "$s"
done
- if ! timeout 30 bash -c "while loginctl --no-legend | grep -q logind-test-user; do sleep 1; done"; then
+ if ! timeout 30 bash -c "while loginctl --no-legend | grep tty | grep -q logind-test-user; do sleep 1; done"; then
echo "WARNING: session for logind-test-user still active, ignoring."
return
fi
create_session
trap teardown_stop_idle_session RETURN
- id="$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1; }')"
+ id="$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1; }')"
ts="$(date '+%H:%M:%S')"
mkdir -p /run/systemd/logind.conf.d
sleep 5
assert_eq "$(journalctl -b -u systemd-logind.service --since="$ts" --grep "Session \"$id\" of user \"logind-test-user\" is idle, stopping." | wc -l)" 1
- assert_eq "$(loginctl --no-legend | grep -c "logind-test-user")" 0
+ assert_eq "$(loginctl --no-legend | grep -v manager | grep -c "logind-test-user")" 0
}
testcase_ambient_caps() {
rm -f "$SCRIPT" "$PAMSERVICE"
}
+background_at_return() {
+ rm -f /etc/pam.d/"$PAMSERVICE"
+ unset PAMSERVICE
+}
+
+testcase_background() {
+
+ local uid TRANSIENTUNIT1 TRANSIENTUNIT2
+
+ uid=$(id -u logind-test-user)
+
+ systemctl stop user@"$uid".service
+
+ PAMSERVICE="pamserv$RANDOM"
+ TRANSIENTUNIT1="bg$RANDOM.service"
+ TRANSIENTUNIT2="bgg$RANDOM.service"
+
+ trap background_at_return RETURN
+
+ cat > /etc/pam.d/"$PAMSERVICE" <<EOF
+auth sufficient pam_unix.so
+auth required pam_deny.so
+account sufficient pam_unix.so
+account required pam_permit.so
+session optional pam_systemd.so debug
+session required pam_unix.so
+EOF
+
+ systemd-run -u "$TRANSIENTUNIT1" -p PAMName="$PAMSERVICE" -p "Environment=XDG_SESSION_CLASS=background-light" -p Type=exec -p User=logind-test-user sleep infinity
+
+ # This was a 'light' background service, hence the service manager should not be running
+ (! systemctl is-active user@"$uid".service )
+
+ systemctl stop "$TRANSIENTUNIT1"
+
+ systemd-run -u "$TRANSIENTUNIT2" -p PAMName="$PAMSERVICE" -p "Environment=XDG_SESSION_CLASS=background" -p Type=exec -p User=logind-test-user sleep infinity
+
+ # This was a regular background service, hence the service manager should be running
+ systemctl is-active user@"$uid".service
+
+ systemctl stop "$TRANSIENTUNIT2"
+
+ systemctl stop user@"$uid".service
+}
+
setup_test_user
test_write_dropin
run_testcases
writeTestUnit() {
mkdir -p "$testUnitFile.d/"
- printf "[Service]\nExecStart=/bin/sleep 3600\n" >"$testUnitFile"
+ printf "[Service]\nExecStart=sleep 3600\n" >"$testUnitFile"
}
writeTestUnitNUMAPolicy() {
# SPDX-License-Identifier: LGPL-2.1-or-later
[Service]
-ExecStart=/bin/sleep 3600
+ExecStart=sleep 3600
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
+if [[ "$(sysctl -ne kernel.apparmor_restrict_unprivileged_userns)" -eq 1 ]]; then
+ echo "Cannot create unprivileged user namespaces" >/skipped
+ exit 0
+fi
+
systemd-analyze log-level debug
runas testuser systemd-run --wait --user --unit=test-private-users \
systemd-analyze log-level debug
+journalctl --list-namespaces -o json | jq .
+
systemd-run --wait -p LogNamespace=foobar echo "hello world"
+systemd-run --wait -p LogNamespace=foobaz echo "hello world"
journalctl --namespace=foobar --sync
+journalctl --namespace=foobaz --sync
+ls -l /var/log/journal/
+journalctl --list-namespaces
+
journalctl -o cat --namespace=foobar >/tmp/hello-world
journalctl -o cat >/tmp/no-hello-world
+journalctl --list-namespaces | grep foobar
+journalctl --list-namespaces | grep foobaz
+journalctl --list-namespaces -o json | jq .
+[[ "$(journalctl --root=/tmp --list-namespaces --quiet)" == "" ]]
+
grep "^hello world$" /tmp/hello-world
(! grep "^hello world$" /tmp/no-hello-world)
Type=simple
AmbientCapabilities=
ExecStart=
-ExecStart=/bin/sleep infinity
+ExecStart=sleep infinity
EOF
systemctl daemon-reload
fi
RemainAfterExit=yes
MountAPIVFS=yes
PrivateTmp=yes
-ExecStart=/bin/sh -c ' \\
+ExecStart=sh -c ' \\
systemd-notify --ready; \\
while [ ! -f /tmp/img/usr/lib/os-release ] || ! grep -q -F MARKER /tmp/img/usr/lib/os-release; do \\
sleep 0.1; \\
ExtensionImages=/usr/share/app0.raw /usr/share/app1.raw:nosuid
# Relevant only for sanitizer runs
UnsetEnvironment=LD_PRELOAD
-ExecStart=/bin/bash -c '/opt/script0.sh | grep ID'
-ExecStart=/bin/bash -c '/opt/script1.sh | grep ID'
+ExecStart=bash -c '/opt/script0.sh | grep ID'
+ExecStart=bash -c '/opt/script1.sh | grep ID'
Type=oneshot
RemainAfterExit=yes
EOF
ExtensionDirectories=${image_dir}/app0 ${image_dir}/app1
# Relevant only for sanitizer runs
UnsetEnvironment=LD_PRELOAD
-ExecStart=/bin/bash -c '/opt/script0.sh | grep ID'
-ExecStart=/bin/bash -c '/opt/script1.sh | grep ID'
+ExecStart=bash -c '/opt/script0.sh | grep ID'
+ExecStart=bash -c '/opt/script1.sh | grep ID'
Type=oneshot
RemainAfterExit=yes
EOF
assert_in "${loop}p3 : start= *${start}, size= *${size}, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=DB081670-07AE-48CA-9F5E-813D5E40B976, name=\"linux-generic-2\"" "$output"
}
+testcase_dropped_partitions() {
+ local workdir image defs
+
+ workdir="$(mktemp --directory "/tmp/test-repart.dropped-partitions.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '${workdir:?}'" RETURN
+
+ image="$workdir/image.img"
+ truncate -s 32M "$image"
+
+ defs="$workdir/defs"
+ mkdir "$defs"
+ echo -ne "[Partition]\nType=root\n" >"$defs/10-part1.conf"
+ echo -ne "[Partition]\nType=root\nSizeMinBytes=1T\nPriority=1\n" >"$defs/11-dropped-first.conf"
+ echo -ne "[Partition]\nType=root\n" >"$defs/12-part2.conf"
+ echo -ne "[Partition]\nType=root\nSizeMinBytes=1T\nPriority=2\n" >"$defs/13-dropped-second.conf"
+
+ systemd-repart --empty=allow --pretty=yes --dry-run=no --definitions="$defs" "$image"
+
+ sfdisk -q -l "$image"
+ [[ "$(sfdisk -q -l "$image" | grep -c "$image")" -eq 2 ]]
+}
+
OFFLINE="yes"
run_testcases
[Service]
Type=notify
-ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
+ExecStart=bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
EOF
cat >/run/systemd/system/testservice-fail-restart-59.service <<EOF
[Service]
Type=notify
-ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
+ExecStart=bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
Restart=on-failure
StartLimitBurst=1
EOF
[Service]
Type=notify
-ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 5; exit 1"
+ExecStart=bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 5; exit 1"
Restart=on-abort
EOF
[Unit]
Description=TEST-62-RESTRICT-IFACES-all-pings-work
[Service]
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=
Type=oneshot
[Unit]
Description=TEST-62-RESTRICT-IFACES-allow-list
[Service]
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=veth0
RestrictNetworkInterfaces=veth1
Type=oneshot
[Unit]
Description=TEST-62-RESTRICT-IFACES-deny-list
[Service]
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=~veth0
RestrictNetworkInterfaces=~veth1
Type=oneshot
[Unit]
Description=TEST-62-RESTRICT-IFACES-empty-assignment
[Service]
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=veth0
RestrictNetworkInterfaces=
Type=oneshot
[Unit]
Description=TEST-62-RESTRICT-IFACES-invert-assignment
[Service]
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.1'
-ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
-ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.9'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=sh -c '! ping -c 1 -W 0.2 192.168.113.9'
RestrictNetworkInterfaces=veth0
RestrictNetworkInterfaces=veth0 veth1
RestrictNetworkInterfaces=~veth0
ConditionPathExists=/etc/os-release
[Service]
-ExecStart=/bin/true
+ExecStart=true
EOF
systemctl daemon-reload
systemd-analyze condition --unit="$UNIT_NAME"
DeviceAllow=/dev/null r
StandardOutput=file:/tmp/testsuite66serviceresults
ExecStartPre=rm -f /tmp/testsuite66serviceresults
-ExecStart=/bin/bash -c "while true; do sleep 0.01 && echo meow >/dev/null && echo thisshouldnotbehere; done"
+ExecStart=bash -c "while true; do sleep 0.01 && echo meow >/dev/null && echo thisshouldnotbehere; done"
[Service]
Type=oneshot
-ExecStart=/bin/true
+ExecStart=true
echo "$CHASSIS"
)
+stop_hostnamed() {
+ systemctl stop systemd-hostnamed.service
+ systemctl reset-failed systemd-hostnamed # reset trigger limit
+}
+
testcase_chassis() {
local i
assert_eq "$(get_chassis)" "$i"
done
- systemctl stop systemd-hostnamed.service
+ stop_hostnamed
rm -f /etc/machine-info
# fallback chassis type
umount /sys/class/dmi/id
rm -rf /run/systemd/system/systemd-hostnamed.service.d
systemctl daemon-reload
- systemctl stop systemd-hostnamed
+ stop_hostnamed
}
testcase_firmware_date() {
echo '1' >/sys/class/dmi/id/uevent
echo '09/08/2000' >/sys/class/dmi/id/bios_date
- systemctl stop systemd-hostnamed
+ stop_hostnamed
assert_in '2000-09-08' "$(hostnamectl)"
echo '2022' >/sys/class/dmi/id/bios_date
- systemctl stop systemd-hostnamed
+ stop_hostnamed
assert_not_in 'Firmware Date' "$(hostnamectl)"
echo 'garbage' >/sys/class/dmi/id/bios_date
- systemctl stop systemd-hostnamed
+ stop_hostnamed
assert_not_in 'Firmware Date' "$(hostnamectl)"
}
(! getent hosts -s myhostname fd00:dead:beef:cafe::1)
}
+test_varlink() {
+ A="$(mktemp -u)"
+ B="$(mktemp -u)"
+ varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}' --json=short > "$A"
+ hostnamectl --json=short > "$B"
+ cmp "$A" "$B"
+}
+
run_testcases
touch /testok
# Extended unit
cat >"/run/systemd/system/delta-test-unit-extended.service" <<EOF
[Service]
-ExecStart=/bin/true
+ExecStart=true
EOF
mkdir -p "/run/systemd/system/delta-test-unit-extended.service.d"
cat >"/run/systemd/system/delta-test-unit-extended.service.d/override.conf" <<EOF
[Unit]
Description=Foo Bar
[Service]
-ExecStartPre=/bin/true
+ExecStartPre=true
EOF
# Masked unit
cp -fv /run/systemd/system/delta-test-unit-extended.service /run/systemd/system/delta-test-unit-masked.service
systemd-id128 show --pretty root-x86-64 --app-specific=4f68bce3e8cd4db196e7fbcaf984b709
[[ "$(systemd-id128 show root-x86-64 --app-specific=4f68bce3e8cd4db196e7fbcaf984b709 -P)" = "8ee5535e7cb14c249e1d28b8dfbb939c" ]]
+systemd-id128 show -j
+systemd-id128 show --no-pager
+systemd-id128 show --json=short
+systemd-id128 show --no-legend
+systemd-id128 show --no-pager --no-legend
+systemd-id128 show root -P -u
+
[[ "$(systemd-id128 new | wc -c)" -eq 33 ]]
systemd-id128 new -p
systemd-id128 new -u
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+at_exit() {
+ rm -f /run/credstore/network.network.50-testme
+ rm -f /run/systemd/system/systemd-network-generator.service.d/50-testme.conf
+}
+
+trap at_exit EXIT
+
+mkdir -p /run/credstore
+cat > /run/credstore/network.network.50-testme <<EOF
+[Match]
+Property=IDONTEXIST
+EOF
+
+systemctl edit systemd-network-generator.service --stdin --drop-in=50-testme.conf <<EOF
+[Service]
+LoadCredential=network.network.50-testme
+EOF
+
+systemctl restart systemd-network-generator
+
+test -f /run/systemd/network/50-testme.network
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+if ! command -v ssh &> /dev/null || ! command -v sshd &> /dev/null ; then
+ echo "ssh/sshd not found, skipping test." >&2
+ exit 0
+fi
+
+systemctl -q is-active sshd-unix-local.socket
+
+if test -e /dev/vsock ; then
+ systemctl -q is-active sshd-vsock.socket
+fi
+
+if test -d /run/host/unix-export ; then
+ systemctl -q is-active sshd-unix-export.socket
+fi
+
+# FIXME: sshd seems to crash inside asan currently, skip the actual ssh test hence
+if [[ -v ASAN_OPTIONS ]] ; then
+ exit 0
+fi
+
+ROOTID=$(mktemp -u)
+# Needed on Ubuntu/Debian as we copy binaries manually
+mkdir -p /run/sshd
+
+removesshid() {
+ rm -f "$ROOTID" "$ROOTID".pub
+}
+
+ssh-keygen -N '' -C '' -t rsa -f "$ROOTID"
+
+mkdir -p 0700 /root/.ssh
+cat "$ROOTID".pub >> /root/.ssh/authorized_keys
+
+# set root pw to "foo", just to set it to something valid
+# shellcheck disable=SC2016
+usermod -p '$5$AAy6BYJ6rzz.QELv$6LpVEU3/RQmVz.svHu/33qoJWWWzZuJ3DM2fo9JgcUD' root
+usermod -U root
+
+mkdir -p /etc/ssh
+test -f /etc/ssh/ssh_host_rsa_key || ssh-keygen -t rsa -C '' -N '' -f /etc/ssh/ssh_host_rsa_key
+echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
+echo "LogLevel DEBUG3" >> /etc/ssh/sshd_config
+
+test -f /etc/ssh/ssh_config || echo 'Include /etc/ssh/ssh_config.d/*.conf' > /etc/ssh/ssh_config
+
+# ssh wants this dir around, but distros cannot agree on a common name for it, let's just create all that are aware of distros use
+mkdir -p /usr/share/empty.sshd /var/empty /var/empty/sshd
+
+ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" .host cat /etc/machine-id | cmp - /etc/machine-id
+ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" unix/run/ssh-unix-local/socket cat /etc/machine-id | cmp - /etc/machine-id
+
+modprobe vsock_loopback ||:
+if test -e /dev/vsock -a -d /sys/module/vsock_loopback ; then
+ ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" vsock/1 cat /etc/machine-id | cmp - /etc/machine-id
+fi
varlinkctl introspect /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend
fi
+# SSH transport
+SSHBINDIR="$(mktemp -d)"
+
+rm_rf_sshbindir() {
+ rm -rf "$SSHBINDIR"
+}
+
+trap rm_rf_sshbindir EXIT
+
+# Create a fake "ssh" binary that validates everything works as expected
+cat > "$SSHBINDIR"/ssh <<'EOF'
+#!/bin/sh
+
+set -xe
+
+test "$1" = "-W"
+test "$2" = "/run/systemd/journal/io.systemd.journal"
+test "$3" = "foobar"
+
+exec socat - UNIX-CONNECT:/run/systemd/journal/io.systemd.journal
+EOF
+
+chmod +x "$SSHBINDIR"/ssh
+
+SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl info ssh:foobar:/run/systemd/journal/io.systemd.journal
+
# Go through all varlink sockets we can find under /run/systemd/ for some extra coverage
find /run/systemd/ -name "io.systemd*" -type s | while read -r socket; do
varlinkctl info "$socket"
(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord </dev/null)
(! varlinkctl validate-idl "")
(! varlinkctl validate-idl </dev/null)
+
+varlinkctl info /run/systemd/io.systemd.Hostname
+varlinkctl introspect /run/systemd/io.systemd.Hostname io.systemd.Hostname
+varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}'
DNS=10.0.0.1
DNS=fd00:dead:beef:cafe::1
EOF
+cat >/etc/systemd/network/10-dns1.netdev <<EOF
+[NetDev]
+Name=dns1
+Kind=dummy
+EOF
+cat >/etc/systemd/network/10-dns1.network <<EOF
+[Match]
+Name=dns1
+
+[Network]
+Address=10.99.0.1/24
+DNSSEC=no
+EOF
+systemctl edit --stdin --full --runtime --force "resolved-dummy-server.service" <<EOF
+[Service]
+Type=notify
+Environment=SYSTEMD_LOG_LEVEL=debug
+ExecStart=/usr/lib/systemd/tests/unit-tests/manual/test-resolved-dummy-server 10.99.0.1:53
+EOF
DNS_ADDRESSES=(
"10.0.0.1"
systemctl unmask systemd-networkd
systemctl start systemd-networkd
restart_resolved
+systemctl start resolved-dummy-server
# Create knot's runtime dir, since from certain version it's provided only by
# the package and not created by tmpfiles/systemd
if [[ ! -d /run/knot ]]; then
# Wait a bit for the keys to propagate
sleep 4
+systemctl status resolved-dummy-server
networkctl status
resolvectl status
resolvectl log-level debug
systemd-run -u resolvectl-monitor.service -p Type=notify resolvectl monitor
systemd-run -u resolvectl-monitor-json.service -p Type=notify resolvectl monitor --json=short
-# Check if all the zones are valid (zone-check always returns 0, so let's check
-# if it produces any errors/warnings)
-run knotc zone-check
+# FIXME: knot, unfortunately, incorrectly complains about missing zone files for zones
+# that are forwarded using the `dnsproxy` module. Until the issue is resolved,
+# let's fall back to pre-processing the `zone-check` output a bit before checking it
+#
+# See: https://gitlab.nic.cz/knot/knot-dns/-/issues/913
+run knotc zone-check || :
+sed -i '/forwarded.test./d' "$RUN_OUT"
[[ ! -s "$RUN_OUT" ]]
# We need to manually propagate the DS records of onlinesign.test. to the parent
# zone, since they're generated online
run resolvectl openpgp mr.smith@signed.test
grep -qF "5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test" "$RUN_OUT"
grep -qF "authenticated: yes" "$RUN_OUT"
+# Check zone transfers (AXFR/IXFR)
+# Note: since resolved doesn't support zone transfers, let's just make sure it
+# simply refuses such requests without choking on them
+# See: https://github.com/systemd/systemd/pull/30809#issuecomment-1880102804
+run dig @ns1.unsigned.test AXFR signed.test
+grep -qE "SOA\s+ns1.unsigned.test. root.unsigned.test." "$RUN_OUT"
+run dig AXFR signed.test
+grep -qF "; Transfer failed" "$RUN_OUT"
+run dig @ns1.unsigned.test IXFR=43 signed.test
+grep -qE "SOA\s+ns1.unsigned.test. root.unsigned.test." "$RUN_OUT"
+run dig IXFR=43 signed.test
+grep -qF "; Transfer failed" "$RUN_OUT"
# DNSSEC validation with multiple records of the same type for the same name
# Issue: https://github.com/systemd/systemd/issues/22002
#run dig +dnssec this.does.not.exist.untrusted.test
#grep -qF "status: NXDOMAIN" "$RUN_OUT"
+: "--- ZONE: forwarded.test (queries forwarded to our dummy test server) ---"
+JOURNAL_CURSOR="$(mktemp)"
+journalctl -n0 -q --cursor-file="$JOURNAL_CURSOR"
+
+# See "test-resolved-dummy-server.c" for the server part
+(! run resolvectl query nope.forwarded.test)
+grep -qF "nope.forwarded.test" "$RUN_OUT"
+grep -qF "not found" "$RUN_OUT"
+
+# SERVFAIL + EDE code 6: DNSSEC Bogus
+(! run resolvectl query edns-bogus-dnssec.forwarded.test)
+grep -qE "^edns-bogus-dnssec.forwarded.test:.+: upstream-failure \(DNSSEC Bogus\)" "$RUN_OUT"
+# Same thing, but over Varlink
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-bogus-dnssec.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSSECValidationFailed" "$RUN_OUT"
+grep -qF '{"result":"upstream-failure","extendedDNSErrorCode":6}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(DNSSEC Bogus\). Lookup failed."
+
+# SERVFAIL + EDE code 16: Censored + extra text
+(! run resolvectl query edns-extra-text.forwarded.test)
+grep -qE "^edns-extra-text.forwarded.test.+: SERVFAIL \(Censored: Nothing to see here!\)" "$RUN_OUT"
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-extra-text.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!"}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Censored: Nothing to see here!\)"
+
+# SERVFAIL + EDE code 0: Other + extra text
+(! run resolvectl query edns-code-zero.forwarded.test)
+grep -qE "^edns-code-zero.forwarded.test:.+: SERVFAIL \(Other: 🐱\)" "$RUN_OUT"
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-code-zero.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱"}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Other: 🐱\)"
+
+# SERVFAIL + invalid EDE code
+(! run resolvectl query edns-invalid-code.forwarded.test)
+grep -qE "^edns-invalid-code.forwarded.test:.+: SERVFAIL \([0-9]+\)" "$RUN_OUT"
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+\)"
+
+# SERVFAIL + invalid EDE code + extra text
+(! run resolvectl query edns-invalid-code-with-extra-text.forwarded.test)
+grep -qE '^edns-invalid-code-with-extra-text.forwarded.test:.+: SERVFAIL \([0-9]+: Hello \[#\]\$%~ World\)' "$RUN_OUT"
+(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code-with-extra-text.forwarded.test"}')
+grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT"
+grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World"}' "$RUN_OUT"
+journalctl --sync
+journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+: Hello \[\#\]\\$%~ World\)"
+
### Test resolvectl show-cache
run resolvectl show-cache
run resolvectl show-cache --json=short
Documentation=man:test
[Service]
-ExecStart=/bin/true
+ExecStart=true
--- /dev/null
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+# See tmpfiles.d(5) for details
+
+L {{SSHCONFDIR}}/20-systemd-ssh-proxy.conf - - - - {{LIBEXECDIR}}/ssh_config.d/20-systemd-ssh-proxy.conf
['systemd.conf', ''],
['var.conf', ''],
['20-systemd-userdb.conf', 'ENABLE_USERDB'],
+ ['20-systemd-ssh-generator.conf', ''],
]
foreach pair : in_files
[Service]
Type=oneshot
-# FIXME: once dracut is patched to install the symlink, change to:
-# ExecStart={{LIBEXECDIR}}/systemd-sysroot-fstab-check
-ExecStart=@{{SYSTEM_GENERATOR_DIR}}/systemd-fstab-generator systemd-sysroot-fstab-check
+ExecStart={{LIBEXECDIR}}/systemd-sysroot-fstab-check
# We want to enqueue initrd-cleanup.service/start after we finished the part
# above. It can't be part of the initial transaction, because non-oneshot units
'conditions' : ['ENABLE_HOSTNAMED'],
'symlinks' : ['dbus-org.freedesktop.hostname1.service'],
},
+ {
+ 'file' : 'systemd-hostnamed.socket',
+ 'conditions' : ['ENABLE_HOSTNAMED'],
+ 'symlinks' : ['sockets.target.wants/'],
+ },
{
'file' : 'systemd-hwdb-update.service.in',
'conditions' : ['ENABLE_HWDB'],
# (at your option) any later version.
[Unit]
-Description=Hibernate
+Description=System Hibernate
Documentation=man:systemd-hibernate.service(8)
DefaultDependencies=no
Requires=sleep.target
Documentation=man:org.freedesktop.hostname1(5)
[Service]
+Type=notify
BusName=org.freedesktop.hostname1
CapabilityBoundingSet=CAP_SYS_ADMIN
ExecStart={{LIBEXECDIR}}/systemd-hostnamed
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
-PrivateDevices=yes
+DeviceAllow=/dev/vsock r
PrivateNetwork=yes
PrivateTmp=yes
ProtectProc=invisible
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Hostname Service Varlink Socket
+Documentation=man:systemd-hostnamed.service(8)
+Documentation=man:hostname(5)
+Documentation=man:machine-info(5)
+
+[Socket]
+ListenStream=/run/systemd/io.systemd.Hostname
+FileDescriptorName=varlink
+SocketMode=0666
# (at your option) any later version.
[Unit]
-Description=Hybrid Suspend+Hibernate
+Description=System Hybrid Suspend+Hibernate
Documentation=man:systemd-hybrid-sleep.service(8)
DefaultDependencies=no
Requires=sleep.target
Documentation=man:org.freedesktop.import1(5)
[Service]
+Type=notify
ExecStart={{LIBEXECDIR}}/systemd-importd
BusName=org.freedesktop.import1
KillMode=mixed
Documentation=man:org.freedesktop.locale1(5)
[Service]
+Type=notify
BusName=org.freedesktop.locale1
CapabilityBoundingSet=
ExecStart={{LIBEXECDIR}}/systemd-localed
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-network-generator
+ImportCredential=network.netdev.*
+ImportCredential=network.link.*
+ImportCredential=network.network.*
[Install]
WantedBy=sysinit.target
SystemCallFilter=@system-service
Type=notify-reload
User=systemd-network
+ImportCredential=network.wireguard.*
{{SERVICE_WATCHDOG}}
[Install]
# (at your option) any later version.
[Unit]
-Description=Suspend; Hibernate if not used for a period of time
+Description=System Suspend then Hibernate
Documentation=man:systemd-suspend-then-hibernate.service(8)
DefaultDependencies=no
Requires=sleep.target
Documentation=man:org.freedesktop.timedate1(5)
[Service]
+Type=notify
BusName=org.freedesktop.timedate1
CapabilityBoundingSet=CAP_SYS_TIME
DeviceAllow=char-rtc r