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*'
python3-pefile
python3-pyelftools
python3-pyparsing
+ python3-pytest
rpm
zstd
)
make sure to update the package in question to provide proper, native systemd
unit files. Contact vendor if necessary. Compatibility support for System V
services is deprecated and will be removed soon.
+
+-- 187c62eb1e7f463bb530394f52cb090f
+Subject: A Portable Service has been attached
+Defined-By: systemd
+Support: %SUPPORT_URL%
+Documentation: https://systemd.io/PORTABLE_SERVICES/
+
+A new Portable Service @PORTABLE_ROOT@ (with extensions: @PORTABLE_EXTENSION@) has
+been attached to the system and is now available for use. The list of attached
+Portable Services can be queried with 'portablectl list'.
+
+-- 76c5c754d628490d8ecba4c9d042112b
+Subject: A Portable Service has been detached
+Defined-By: systemd
+Support: %SUPPORT_URL%
+Documentation: https://systemd.io/PORTABLE_SERVICES/
+
+A Portable Service @PORTABLE_ROOT@ (with extensions: @PORTABLE_EXTENSION@) has been
+detached from the system and is no longer available for use. The list of attached
+Portable Services can be queried with 'portablectl list'.
* `$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.
object are honored. If both `matchHostname` and `matchMachineId` are used
within the same array entry, the object is honored when either match succeeds,
i.e. the two match types are combined in OR, not in AND. (As a special case, if
-only a single machine ID is listed this field may be a single string rather
+only a single hostname is listed this field may be a single string rather
than an array of strings.)
These two are the only two fields specific to this section. All other fields
account required pam_permit.so
-password sufficient pam_systemd_home.so
-password sufficient pam_unix.so sha512 shadow try_first_pass use_authtok
+password sufficient pam_unix.so sha512 shadow try_first_pass
password required pam_deny.so
-session optional pam_keyinit.so revoke
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
#########################################
<variablelist>
<varlistentry>
- <term><cmdsynopsis><command>list</command></cmdsynopsis></term>
+ <term><command>list</command></term>
<listitem><para>Show all peers on the bus, by their service
names. By default, shows both unique and well-known names, but
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>status</command> <arg choice="opt"><replaceable>SERVICE</replaceable></arg></cmdsynopsis></term>
+ <term><command>status</command> <arg choice="opt"><replaceable>SERVICE</replaceable></arg></term>
<listitem><para>Show process information and credentials of a
bus service (if one is specified by its unique or well-known
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>monitor</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></cmdsynopsis></term>
+ <term><command>monitor</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
<listitem><para>Dump messages being exchanged. If
<replaceable>SERVICE</replaceable> is specified, show messages
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>capture</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></cmdsynopsis></term>
+ <term><command>capture</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
<listitem><para>Similar to <command>monitor</command> but
writes the output in pcapng format (for details, see
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>tree</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></cmdsynopsis></term>
+ <term><command>tree</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
<listitem><para>Shows an object tree of one or more
services. If <replaceable>SERVICE</replaceable> is specified,
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>introspect</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="opt"><replaceable>INTERFACE</replaceable></arg></cmdsynopsis></term>
+ <term><command>introspect</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="opt"><replaceable>INTERFACE</replaceable></arg></term>
<listitem><para>Show interfaces, methods, properties and
signals of the specified object (identified by its path) on
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>call</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>METHOD</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></cmdsynopsis></term>
+ <term><command>call</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>METHOD</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></term>
<listitem><para>Invoke a method and show the response. Takes a
service name, object path, interface name and method name. If
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>emit</command> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>SIGNAL</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></cmdsynopsis></term>
+ <term><command>emit</command> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>SIGNAL</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></term>
<listitem><para>Emit a signal. Takes an object path, interface name and method name. If parameters
shall be passed, a signature string is required, followed by the arguments, individually formatted as
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>get-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>PROPERTY</replaceable></arg></cmdsynopsis></term>
+ <term><command>get-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>PROPERTY</replaceable></arg></term>
<listitem><para>Retrieve the current value of one or more
object properties. Takes a service name, object path,
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>set-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>PROPERTY</replaceable></arg> <arg choice="plain"><replaceable>SIGNATURE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></cmdsynopsis></term>
+ <term><command>set-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>PROPERTY</replaceable></arg> <arg choice="plain"><replaceable>SIGNATURE</replaceable></arg> <arg choice="plain" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></term>
<listitem><para>Set the current value of an object
property. Takes a service name, object path, interface name,
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>help</command></cmdsynopsis></term>
+ <term><command>help</command></term>
<listitem><para>Show command syntax help.</para>
<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>
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);
<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>
<listitem><para>mkdir</para></listitem>
</itemizedlist>
Note that an image cannot be attached if a unit that it contains is already present
- on the system.</para>
+ on the system. Note that this method returns only after all the listed operations are completed,
+ and due to the I/O involved it might take some time.</para>
<para><function>AttachImageWithExtensions()</function> attaches a portable image to the system.
This method is a superset of <function>AttachImage()</function> with the addition of
<itemizedlist>
<listitem><para>unlink</para></listitem>
</itemizedlist>
- Note that an image cannot be detached if a unit that it contains is running.</para>
+ Note that an image cannot be detached if a unit that it contains is running. Note that this method
+ returns only after all the listed operations are completed, and due to the I/O involved it might take
+ some time.</para>
<para><function>DetachImageWithExtensions()</function> detaches a portable image from the system.
This method is a superset of <function>DetachImage()</function> with the addition of
<para>The <function>AttachImageWithExtensions()</function>,
<function>DetachImageWithExtensions()</function> and
<function>ReattachImageWithExtensions()</function> methods take in options as flags instead of
- booleans to allow for extendability. <varname>SD_SYSTEMD_PORTABLE_FORCE_ATTACH</varname> will cause
- safety checks that ensure the units are not running while the new image is attached or detached
- to be skipped. <varname>SD_SYSTEMD_PORTABLE_FORCE_EXTENSION</varname> will cause the check that the
+ booleans to allow for extendability. <varname>SD_SYSTEMD_PORTABLE_FORCE_ATTACH</varname> will bypass
+ the safety checks that ensure the units are not running while the image is attached or detached.
+ <varname>SD_SYSTEMD_PORTABLE_FORCE_EXTENSION</varname> will bypass the check that ensures the
<filename>extension-release.<replaceable>NAME</replaceable></filename> file in the extension image
- matches the image name to be skipped. They are defined as follows:</para>
+ matches the image name. They are defined as follows:</para>
<programlisting>
#define SD_SYSTEMD_PORTABLE_RUNTIME (UINT64_C(1) << 0)
and terminate all units that aren't dependencies of it. If <literal>ignore-dependencies</literal>, it
will start a unit but ignore all its dependencies. If <literal>ignore-requirements</literal>, it will
start a unit but only ignore the requirement dependencies. It is not recommended to make use of the
- latter two options. On completion, this method returns the newly created job object.</para>
+ latter two options. On reply, if successful, this method returns the newly created job object
+ which has been enqueued for asynchronous activation. Callers that want to track the outcome of the
+ actual start operation need to monitor the result of this job. This can be achieved in a race-free
+ manner by first subscribing to the <function>JobRemoved()</function> signal, then calling
+ <function>StartUnit()</function> and using the returned job object to filter out unrelated
+ <function>JobRemoved()</function> signals, until the desired one is received, which will then carry
+ the result of the start operation.</para>
<para><function>StartUnitReplace()</function> is similar to <function>StartUnit()</function> but
replaces a job that is queued for one unit by a job for another unit.</para>
account required pam_permit.so
-password sufficient pam_systemd_home.so
-password sufficient pam_unix.so sha512 shadow try_first_pass use_authtok
-
+password sufficient pam_unix.so sha512 shadow try_first_pass
password required pam_deny.so
-session optional pam_keyinit.so revoke
account required pam_permit.so
<command>-password sufficient pam_systemd_home.so</command>
-password sufficient pam_unix.so sha512 shadow try_first_pass use_authtok
+password sufficient pam_unix.so sha512 shadow try_first_pass
password required pam_deny.so
-session optional pam_keyinit.so revoke
"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>
<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>
<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 allowed set of values is determined by the kernel and is shown in the file itself (use
<command>cat /sys/power/disk</command> to display). See the kernel documentation page
- <ulink url="https://www.kernel.org/doc/html/latest/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
+ <ulink url="https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
Basic sysfs Interfaces for System Suspend and Hibernation</ulink> for more details.</para>
<para>
<para>The allowed set of values is determined by the kernel and is shown in the file itself (use
<command>cat /sys/power/state</command> to display). See <ulink
- url="https://www.kernel.org/doc/html/latest/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
+ url="https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
Basic sysfs Interfaces for System Suspend and Hibernation</ulink> for more details.</para>
<para>
<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>
<para>The following options are understood:</para>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-q</option></term>
+ <term><option>--quiet</option></term>
+
+ <listitem><para>Turns off any status output by the tool
+ itself. When this switch is used, the only output from vmspawn
+ will be the console output of the Virtual Machine OS itself.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ </variablelist>
+
<refsect2>
<title>Image Options</title>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
- </variablelist>
+
+ <varlistentry>
+ <term><option>--firmware=</option><replaceable>PATH</replaceable></term>
+
+ <listitem><para>Takes an absolute path, or a relative path beginning with
+ <filename>./</filename>. Specifies a JSON firmware definition file, which allows selecting the
+ firmware to boot in the VM. If not specified a suitable firmware is automatically discovered. If the
+ special string <literal>list</literal> is specified lists all discovered firmwares.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+ </variablelist>
</refsect2><refsect2>
<title>System Identity Options</title>
<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
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>IPv6RetransmissionTimeSec=</varname></term>
+ <listitem>
+ <para>Configures IPv6 Retransmission Time. The time between retransmitted Neighbor
+ Solicitation messages. Used by address resolution and the Neighbor Unreachability
+ Detection algorithm. A value of zero is ignored and the kernel's current value
+ will be used. Defaults to unset, and the kernel's current value will be used.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>IPv4ReversePathFilter=</varname></term>
<listitem>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>L3MasterDevice=</varname></term>
+ <listitem>
+ <para>A boolean. Specifies whether the rule is to direct lookups to the tables associated with
+ level 3 master devices (also known as Virtual Routing and Forwarding or VRF devices).
+ For further details see <ulink url="https://docs.kernel.org/networking/vrf.html">
+ Virtual Routing and Forwarding (VRF)</ulink>. Defaults to false.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>SourcePort=</varname></term>
<listitem>
<command>ip route show table <replaceable>num</replaceable></command>. If unset and
<varname>Type=</varname> is <literal>local</literal>, <literal>broadcast</literal>,
<literal>anycast</literal>, or <literal>nat</literal>, then <literal>local</literal> is used.
- In other cases, defaults to <literal>main</literal>.</para>
+ In other cases, defaults to <literal>main</literal>. Ignored if <varname>L3MasterDevice=</varname> is true.</para>
<xi:include href="version-info.xml" xpointer="v230"/>
</listitem>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>UseRetransmissionTime=</varname></term>
+ <listitem>
+ <para>Takes a boolean. When true, the retransmission time received in the Router Advertisement will be set
+ on the interface receiving the advertisement. It is used as the time between retransmissions of Neighbor
+ Solicitation messages to a neighbor when resolving the address or when probing the reachability of a neighbor.
+ Defaults to true.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>UseICMP6RateLimit=</varname></term>
<listitem>
cache for swap pages. It takes pages that are in the process of being swapped out and attempts to compress them into a
dynamically allocated RAM-based memory pool. If the limit specified is hit, no entries from this unit will be
stored in the pool until existing entries are faulted back or written out to disk. See the kernel's
- <ulink url="https://www.kernel.org/doc/html/latest/admin-guide/mm/zswap.html">Zswap</ulink> documentation for more details.</para>
+ <ulink url="https://docs.kernel.org/admin-guide/mm/zswap.html">Zswap</ulink> documentation for more details.</para>
<para>Takes a size in bytes. If the value is suffixed with K, M, G or T, the specified size is
parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If assigned the
number of tasks on the system. If assigned the special value <literal>infinity</literal>, no tasks
limit is applied. This controls the <literal>pids.max</literal> control group attribute. For
details about this control group attribute, the
- <ulink url="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#pid">pids controller
+ <ulink url="https://docs.kernel.org/admin-guide/cgroup-v2.html#pid">pids controller
</ulink>.
The effective configuration is reported as <varname>EffectiveTasksMax=</varname>.</para>
basic_disabled_warnings = [
'-Wno-missing-field-initializers',
'-Wno-unused-parameter',
+ '-Wno-nonnull-compare',
]
possible_common_cc_flags = [
<comment>Configuration Extension DDI</comment>
<glob pattern="*.confext.raw"/>
</mime-type>
+ <mime-type type="application/x.systemd-journal">
+ <comment>Journal Log File</comment>
+ <magic>
+ <match type="string" value="LPKSHHRH" offset="0"/>
+ </magic>
+ </mime-type>
+ <mime-type type="application/x.systemd-catalog">
+ <comment>Journal Message Catalog</comment>
+ <magic>
+ <match type="string" value="RHHHKSLP" offset="0"/>
+ </magic>
+ </mime-type>
+ <mime-type type="application/x.systemd-hwdb">
+ <comment>Hardware Database</comment>
+ <magic>
+ <match type="string" value="KSLPHHRH" offset="0"/>
+ </magic>
+ </mime-type>
+ <mime-type type="application/x.systemd-credential">
+ <comment>Encrypted Credential</comment>
+ <generic-icon name="security-high"/>
+ <magic>
+ <match type="string" value="Whxqht+dQJax1aZeCGLxm" offset="0"/>
+ <match type="string" value="DHzAexF2RZGcSwvqCLwg/" offset="0"/>
+ <match type="string" value="+vfrk0HjQSyhpDb5Wik2L" offset="0"/>
+ <match type="string" value="k6iUCUh0RJCQyvL8k8q1U" offset="0"/>
+ <match type="string" value="r0lQqEkTTrGnOEYwT/MMB" offset="0"/>
+ <match type="string" value="BYRp2vb1QySABUnaD46i+" offset="0"/>
+ </magic>
+ </mime-type>
</mime-info>
fi
if command -v authselect >/dev/null; then
- authselect select minimal
+ # authselect 1.5.0 renamed the minimal profile to the local profile without keeping backwards compat so
+ # let's use the new name if it exists.
+ if [ -d /usr/share/authselect/default/local ]; then
+ PROFILE=local
+ else
+ PROFILE=minimal
+ fi
+
+ authselect select "$PROFILE"
- if authselect list-features minimal | grep -q "with-homed"; then
+ if authselect list-features "$PROFILE" | grep -q "with-homed"; then
authselect enable-feature with-homed
fi
fi
# Do the same for dummy0.
options dummy numdummies=0
+
+# Do the same for ifb0.
+
+options ifb numifbs=0
Kind=sit
OriginalName=6rd-*
-[Network]
+[Link]
NamePolicy=keep
MACAddressPolicy=persistent
Property=ID_NET_MANAGED_BY=io.systemd.Network
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Indonesian translation for systemd.
-# Andika Triwidada <andika@gmail.com>, 2014, 2021, 2022.
+# Andika Triwidada <andika@gmail.com>, 2014, 2021, 2022, 2024.
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-14 21:25+0000\n"
-"PO-Revision-Date: 2022-11-25 08:19+0000\n"
+"PO-Revision-Date: 2024-01-21 10:36+0000\n"
"Last-Translator: Andika Triwidada <andika@gmail.com>\n"
-"Language-Team: Indonesian <https://translate.fedoraproject.org/projects/systemd/master/id/>\n"
+"Language-Team: Indonesian <https://translate.fedoraproject.org/projects/"
+"systemd/master/id/>\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: Weblate 4.14.2\n"
+"X-Generator: Weblate 5.3.1\n"
#: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system"
#: src/core/org.freedesktop.systemd1.policy.in:74
msgid "Dump the systemd state without rate limits"
-msgstr ""
+msgstr "Curahkan keadaan systemd tanpa pembatasan laju"
#: src/core/org.freedesktop.systemd1.policy.in:75
-#, fuzzy
msgid "Authentication is required to dump the systemd state without rate limits."
-msgstr "Otentikasi diperlukan untuk memuat ulang keadaan systemd."
+msgstr ""
+"Otentikasi diperlukan untuk mencurahkan keadaan systemd tanpa pembatasan "
+"laju."
#: src/home/org.freedesktop.home1.policy:13
msgid "Create a home area"
#, c-format
msgid "Home of user %s is currently absent, please plug in the necessary storage device or backing file system."
msgstr ""
+"Home dari pengguna %s saat ini tidak ada, harap tancapkan perangkat "
+"penyimpanan yang diperlukan atau sistem berkas pendukung."
#: src/home/pam_systemd_home.c:292
#, c-format
msgid "Too frequent login attempts for user %s, try again later."
-msgstr ""
+msgstr "Percobaan log masuk terlalu sering bagi pengguna %s, coba lagi nanti."
#: src/home/pam_systemd_home.c:304
msgid "Password: "
-msgstr ""
+msgstr "Kata Sandi: "
#: src/home/pam_systemd_home.c:306
#, c-format
msgid "Password incorrect or not sufficient for authentication of user %s."
-msgstr ""
+msgstr "Kata sandi salah atau tidak memadai bagi otentikasi dari pengguna %s."
#: src/home/pam_systemd_home.c:307
msgid "Sorry, try again: "
-msgstr ""
+msgstr "Maaf, coba lagi: "
#: src/home/pam_systemd_home.c:329
msgid "Recovery key: "
-msgstr ""
+msgstr "Kunci pemulihan: "
#: src/home/pam_systemd_home.c:331
#, c-format
msgid "Password/recovery key incorrect or not sufficient for authentication of user %s."
msgstr ""
+"Kata sandi/kunci pemulihan salah atau tidak memadai untuk otentikasi "
+"pengguna %s."
#: src/home/pam_systemd_home.c:332
msgid "Sorry, reenter recovery key: "
-msgstr ""
+msgstr "Maaf, masukkan lagi kunci pemulihan: "
#: src/home/pam_systemd_home.c:352
#, c-format
msgid "Security token of user %s not inserted."
-msgstr ""
+msgstr "Token keamanan pengguna %s tidak ditancapkan."
#: src/home/pam_systemd_home.c:353 src/home/pam_systemd_home.c:356
msgid "Try again with password: "
-msgstr ""
+msgstr "Coba lagi dengan kata sandi: "
#: src/home/pam_systemd_home.c:355
#, c-format
msgid "Password incorrect or not sufficient, and configured security token of user %s not inserted."
msgstr ""
+"Kata sandi salah atau tidak memadai, dan token keamanan yang terkonfigurasi "
+"dari pengguna %s tidak ditancapkan."
#: src/home/pam_systemd_home.c:376
msgid "Security token PIN: "
-msgstr ""
+msgstr "PIN token keamanan: "
#: src/home/pam_systemd_home.c:393
#, c-format
msgid "Please authenticate physically on security token of user %s."
-msgstr ""
+msgstr "Harap otentikasikan secara fisik pada token keamanan dari pengguna %s."
#: src/home/pam_systemd_home.c:404
#, c-format
msgid "Please confirm presence on security token of user %s."
-msgstr ""
+msgstr "Harap konfirmasikan keberadaan pada token keamanan dari pengguna %s."
#: src/home/pam_systemd_home.c:415
#, c-format
msgid "Please verify user on security token of user %s."
-msgstr ""
+msgstr "Harap verifikasikan pengguna pada token keamanan dari pengguna %s."
#: src/home/pam_systemd_home.c:424
msgid "Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"
msgstr ""
+"PIN token keamanan terkunci, harap buka dulu kuncinya. (Petunjuk: Mencabut "
+"dan menancapkan ulang mungkin sudah cukup)"
#: src/home/pam_systemd_home.c:432
#, c-format
msgid "Security token PIN incorrect for user %s."
-msgstr ""
+msgstr "PIN token keamanan salah bagi pengguna %s."
#: src/home/pam_systemd_home.c:433 src/home/pam_systemd_home.c:452
#: src/home/pam_systemd_home.c:471
msgid "Sorry, retry security token PIN: "
-msgstr ""
+msgstr "Maaf, coba lagi PIN token keamanan: "
#: src/home/pam_systemd_home.c:451
#, c-format
msgid "Security token PIN of user %s incorrect (only a few tries left!)"
msgstr ""
+"PIN token keamanan dari pengguna %s salah (hanya beberapa percobaan tersisa!)"
#: src/home/pam_systemd_home.c:470
#, c-format
msgid "Security token PIN of user %s incorrect (only one try left!)"
msgstr ""
+"PIN token keamanan dari pengguna %s salah (hanya satu percobaan tersisa!)"
#: src/home/pam_systemd_home.c:616
#, c-format
msgid "Home of user %s is currently not active, please log in locally first."
msgstr ""
+"Home dari pengguna %s saat ini tidak aktif, harap log masuk secara lokal "
+"terlebih dahulu."
#: src/home/pam_systemd_home.c:618
#, c-format
msgid "Home of user %s is currently locked, please unlock locally first."
msgstr ""
+"Home dari pengguna %s saat ini terkunci, harap buka kunci secara lokal "
+"terlebih dahulu."
#: src/home/pam_systemd_home.c:645
#, c-format
msgid "Too many unsuccessful login attempts for user %s, refusing."
-msgstr ""
+msgstr "Terlalu banyak upaya log masuk yang gagal bagi pengguna %s, menolak."
#: src/home/pam_systemd_home.c:868
msgid "User record is blocked, prohibiting access."
-msgstr ""
+msgstr "Rekaman pengguna terblokir, menolak akses."
#: src/home/pam_systemd_home.c:872
msgid "User record is not valid yet, prohibiting access."
-msgstr ""
+msgstr "Rekaman pengguna belum valid, menolak akses."
#: src/home/pam_systemd_home.c:876
msgid "User record is not valid anymore, prohibiting access."
-msgstr ""
+msgstr "Rekaman pengguna tidak valid lagi, menolak akses."
#: src/home/pam_systemd_home.c:881 src/home/pam_systemd_home.c:932
msgid "User record not valid, prohibiting access."
-msgstr ""
+msgstr "Rekaman pengguna tidak valid, menolak akses."
#: src/home/pam_systemd_home.c:893
#, c-format
msgid "Too many logins, try again in %s."
-msgstr ""
+msgstr "Terlalu banyak log masuk, coba lagi dalam %s."
#: src/home/pam_systemd_home.c:904
msgid "Password change required."
-msgstr ""
+msgstr "Perubahan kata sandi diperlukan."
#: src/home/pam_systemd_home.c:908
msgid "Password expired, change required."
-msgstr ""
+msgstr "Kata sandi kedaluwarsa, perubahan diperlukan."
#: src/home/pam_systemd_home.c:914
msgid "Password is expired, but can't change, refusing login."
-msgstr ""
+msgstr "Kata sandi kedaluwarsa, tapi tidak bisa mengubah, menolak log masuk."
#: src/home/pam_systemd_home.c:918
msgid "Password will expire soon, please change."
-msgstr ""
+msgstr "Kata sandi akan segera kedaluwarsan, harap diubah."
#: src/hostname/org.freedesktop.hostname1.policy:20
msgid "Set hostname"
# 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"
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
done
}
-_systemd-cryptenroll() {
+_systemd_cryptenroll() {
local comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
local -A OPTS=(
--fido2-with-user-presence
--fido2-with-user-verification
--tpm2-device
+ --tpm2-device-key
+ --tpm2-seal-key-handle
--tpm2-pcrs
--tpm2-public-key
--tpm2-public-key-pcrs
--tpm2-signature
--tpm2-with-pin
+ --tpm2-pcrlock
--wipe-slot'
)
if __contains_word "$prev" ${OPTS[ARG]}; then
case $prev in
- --unlock-key-file|--tpm2-public-key|--tpm2-signature)
+ --unlock-key-file|--tpm2-device-key|--tpm2-public-key|--tpm2-signature|--tpm2-pcrlock)
comps=$(compgen -A file -- "$cur")
compopt -o filenames
;;
return 0
}
-complete -F _systemd-cryptenroll systemd-cryptenroll
+complete -F _systemd_cryptenroll systemd-cryptenroll
--in-memory'
[ARG]='-m --mount -M
-u --umount -U
+ --attach
+ --detach
-l --list
--mtree
--with
if __contains_word "$prev_1" ${OPTS[ARG]}; then
case $prev_1 in
- -l|--list|--mtree|-m|--mount|-M|-x|--copy-from|-a|--copy-to|--verity-data|--validate|--with)
+ -l|--list|--mtree|-m|--mount|-M|--attach|--detach|-x|--copy-from|-a|--copy-to|--verity-data|--validate|--with)
comps=$(compgen -A file -- "$cur")
compopt -o filenames
;;
* 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)))
+#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, (n), sizeof(t)))
#define malloc0(n) (calloc(1, (n) ?: 1))
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;
/* Note that we can't decorate this function with _alloc_() since the returned memory area is one byte larger
* than the product of its parameters. */
-static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) {
+static inline void *memdup_suffix0_multiply(const void *p, size_t need, size_t size) {
if (size_multiply_overflow(size, need))
return NULL;
return buf;
}
+char* decescape(const char *s, const char *bad, size_t len) {
+ char *buf, *t;
+
+ /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */
+
+ assert(s || len == 0);
+
+ t = buf = new(char, len * 4 + 1);
+ if (!buf)
+ return NULL;
+
+ for (size_t i = 0; i < len; i++) {
+ uint8_t u = (uint8_t) s[i];
+
+ if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) {
+ *(t++) = '\\';
+ *(t++) = '0' + (u / 100);
+ *(t++) = '0' + ((u / 10) % 10);
+ *(t++) = '0' + (u % 10);
+ } else
+ *(t++) = u;
+ }
+
+ *t = 0;
+ return buf;
+}
+
static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
assert(bad);
assert(t);
return xescape_full(s, bad, SIZE_MAX, 0);
}
char* octescape(const char *s, size_t len);
+char* decescape(const char *s, const char *bad, size_t len);
char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags);
char* shell_escape(const char *s, const char *bad);
safe_fclose(*f);
}
+static inline void* close_fd_ptr(void *p) {
+ safe_close(PTR_TO_FD(p));
+ return NULL;
+}
+
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, pclose, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly
* convertible. The iteration variable 'entry' must already be defined. */
#define VA_ARGS_FOREACH(entry, ...) \
- _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__)
-#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...) \
- for (typeof(entry) _entries_[] = { __VA_ARGS__ }, *_current_ = _entries_; \
- ((long)(_current_ - _entries_) < (long)ELEMENTSOF(_entries_)) && ({ entry = *_current_; true; }); \
+ _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), UNIQ_T(_va_sentinel_, UNIQ), ##__VA_ARGS__)
+#define _VA_ARGS_FOREACH(entry, _entries_, _current_, _va_sentinel_, ...) \
+ for (typeof(entry) _va_sentinel_[1] = {}, _entries_[] = { __VA_ARGS__ __VA_OPT__(,) _va_sentinel_[0] }, *_current_ = _entries_; \
+ ((long)(_current_ - _entries_) < (long)(ELEMENTSOF(_entries_) - 1)) && ({ entry = *_current_; true; }); \
_current_++)
#include "log.h"
#define pid_namespace_path(pid, type) procfs_file_alloca(pid, namespace_info[type].proc_path)
-int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) {
- _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, netnsfd = -EBADF, usernsfd = -EBADF;
- int rfd = -EBADF;
+int namespace_open(
+ pid_t pid,
+ int *ret_pidns_fd,
+ int *ret_mntns_fd,
+ int *ret_netns_fd,
+ int *ret_userns_fd,
+ int *ret_root_fd) {
+
+ _cleanup_close_ int pidns_fd = -EBADF, mntns_fd = -EBADF, netns_fd = -EBADF,
+ userns_fd = -EBADF, root_fd = -EBADF;
assert(pid >= 0);
- if (mntns_fd) {
- const char *mntns;
+ if (ret_pidns_fd) {
+ const char *pidns;
- mntns = pid_namespace_path(pid, NAMESPACE_MOUNT);
- mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
- if (mntnsfd < 0)
+ pidns = pid_namespace_path(pid, NAMESPACE_PID);
+ pidns_fd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (pidns_fd < 0)
return -errno;
}
- if (pidns_fd) {
- const char *pidns;
+ if (ret_mntns_fd) {
+ const char *mntns;
- pidns = pid_namespace_path(pid, NAMESPACE_PID);
- pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
- if (pidnsfd < 0)
+ mntns = pid_namespace_path(pid, NAMESPACE_MOUNT);
+ mntns_fd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (mntns_fd < 0)
return -errno;
}
- if (netns_fd) {
+ if (ret_netns_fd) {
const char *netns;
netns = pid_namespace_path(pid, NAMESPACE_NET);
- netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
- if (netnsfd < 0)
+ netns_fd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (netns_fd < 0)
return -errno;
}
- if (userns_fd) {
+ if (ret_userns_fd) {
const char *userns;
userns = pid_namespace_path(pid, NAMESPACE_USER);
- usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
- if (usernsfd < 0 && errno != ENOENT)
+ userns_fd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (userns_fd < 0 && errno != ENOENT)
return -errno;
}
- if (root_fd) {
+ if (ret_root_fd) {
const char *root;
root = procfs_file_alloca(pid, "root");
- rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
- if (rfd < 0)
+ root_fd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+ if (root_fd < 0)
return -errno;
}
- if (pidns_fd)
- *pidns_fd = TAKE_FD(pidnsfd);
+ if (ret_pidns_fd)
+ *ret_pidns_fd = TAKE_FD(pidns_fd);
- if (mntns_fd)
- *mntns_fd = TAKE_FD(mntnsfd);
+ if (ret_mntns_fd)
+ *ret_mntns_fd = TAKE_FD(mntns_fd);
- if (netns_fd)
- *netns_fd = TAKE_FD(netnsfd);
+ if (ret_netns_fd)
+ *ret_netns_fd = TAKE_FD(netns_fd);
- if (userns_fd)
- *userns_fd = TAKE_FD(usernsfd);
+ if (ret_userns_fd)
+ *ret_userns_fd = TAKE_FD(userns_fd);
- if (root_fd)
- *root_fd = TAKE_FD(rfd);
+ if (ret_root_fd)
+ *ret_root_fd = TAKE_FD(root_fd);
return 0;
}
r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid);
if (r < 0)
- return r;
+ return log_debug_errno(r, "Failed to fork process (sd-mkuserns): %m");
if (r == 0)
/* Child. We do nothing here, just freeze until somebody kills us. */
freeze();
xsprintf(path, "/proc/" PID_FMT "/uid_map", pid);
r = write_string_file(path, uid_map, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
- return log_error_errno(r, "Failed to write UID map: %m");
+ return log_debug_errno(r, "Failed to write UID map: %m");
xsprintf(path, "/proc/" PID_FMT "/gid_map", pid);
r = write_string_file(path, gid_map, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
- return log_error_errno(r, "Failed to write GID map: %m");
-
- r = namespace_open(pid, NULL, NULL, NULL, &userns_fd, NULL);
+ return log_debug_errno(r, "Failed to write GID map: %m");
+
+ r = namespace_open(pid,
+ /* ret_pidns_fd = */ NULL,
+ /* ret_mntns_fd = */ NULL,
+ /* ret_netns_fd = */ NULL,
+ &userns_fd,
+ /* ret_root_fd = */ NULL);
if (r < 0)
- return log_error_errno(r, "Failed to open userns fd: %m");
+ return log_debug_errno(r, "Failed to open userns fd: %m");
return TAKE_FD(userns_fd);
+}
+
+int netns_acquire(void) {
+ _cleanup_(sigkill_waitp) pid_t pid = 0;
+ _cleanup_close_ int netns_fd = -EBADF;
+ int r;
+
+ /* Forks off a process in a new network namespace, acquires a network namespace fd, and then kills
+ * the process again. This way we have a netns fd that is not bound to any process. */
+
+ r = safe_fork("(sd-mknetns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_NETNS, &pid);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to fork process (sd-mknetns): %m");
+ if (r == 0)
+ /* Child. We do nothing here, just freeze until somebody kills us. */
+ freeze();
+
+ r = namespace_open(pid,
+ /* ret_pidns_fd = */ NULL,
+ /* ret_mntns_fd = */ NULL,
+ &netns_fd,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to open netns fd: %m");
+ return TAKE_FD(netns_fd);
}
int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) {
unsigned int clone_flag;
} namespace_info[_NAMESPACE_TYPE_MAX + 1];
-int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd);
+int namespace_open(
+ pid_t pid,
+ int *ret_pidns_fd,
+ int *ret_mntns_fd,
+ int *ret_netns_fd,
+ int *ret_userns_fd,
+ int *ret_root_fd);
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
int fd_is_ns(int fd, unsigned long nsflag);
}
int userns_acquire(const char *uid_map, const char *gid_map);
+int netns_acquire(void);
int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type);
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)
}
}
- if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS)) != 0)
+ if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS|FORK_NEW_NETNS)) != 0)
pid = raw_clone(SIGCHLD|
(FLAGS_SET(flags, FORK_NEW_MOUNTNS) ? CLONE_NEWNS : 0) |
- (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0));
+ (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0) |
+ (FLAGS_SET(flags, FORK_NEW_NETNS) ? CLONE_NEWNET : 0));
else
pid = fork();
if (pid < 0)
pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata);
-/* 💣 Note that FORK_NEW_USERNS + FORK_NEW_MOUNTNS should not be called in threaded programs, because they
- * cause us to use raw_clone() which does not synchronize the glibc malloc() locks, and thus will cause
- * deadlocks if the parent uses threads and the child does memory allocations. Hence: if the parent is
- * threaded these flags may not be used. These flags cannot be used if the parent uses threads or the child
- * uses malloc(). 💣 */
+/* 💣 Note that FORK_NEW_USERNS, FORK_NEW_MOUNTNS, or FORK_NEW_NETNS should not be called in threaded
+ * programs, because they cause us to use raw_clone() which does not synchronize the glibc malloc() locks,
+ * and thus will cause deadlocks if the parent uses threads and the child does memory allocations. Hence: if
+ * the parent is threaded these flags may not be used. These flags cannot be used if the parent uses threads
+ * or the child uses malloc(). 💣 */
typedef enum ForkFlags {
FORK_RESET_SIGNALS = 1 << 0, /* Reset all signal handlers and signal mask */
FORK_CLOSE_ALL_FDS = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */
FORK_CLOEXEC_OFF = 1 << 16, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
FORK_DETACH = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */
+ FORK_NEW_NETNS = 1 << 19, /* Run child in its own network namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */
} ForkFlags;
int safe_fork_full(
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);
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) {
return sysctl_write(p, value);
}
+int sysctl_write_ip_neighbor_property(int af, const char *ifname, const char *property, const char *value) {
+ const char *p;
+
+ assert(property);
+ assert(value);
+ assert(ifname);
+
+ if (!IN_SET(af, AF_INET, AF_INET6))
+ return -EAFNOSUPPORT;
+
+ if (ifname) {
+ if (!ifname_valid_full(ifname, IFNAME_VALID_SPECIAL))
+ return -EINVAL;
+ p = strjoina("net/", af_to_ipv4_ipv6(af), "/neigh/", ifname, "/", property);
+ } else
+ p = strjoina("net/", af_to_ipv4_ipv6(af), "/neigh/default/", property);
+
+ return sysctl_write(p, value);
+}
+
int sysctl_read(const char *property, char **ret) {
char *p;
int r;
return sysctl_write_ip_property(af, ifname, property, one_zero(value));
}
+int sysctl_write_ip_neighbor_property(int af, const char *ifname, const char *property, const char *value);
+static inline int sysctl_write_ip_neighbor_property_uint32(int af, const char *ifname, const char *property, uint32_t value) {
+ char buf[DECIMAL_STR_MAX(uint32_t)];
+ xsprintf(buf, "%u", value);
+ return sysctl_write_ip_neighbor_property(af, ifname, property, buf);
+}
+
#define DEFINE_SYSCTL_WRITE_IP_PROPERTY(name, type, format) \
static inline int sysctl_write_ip_property_##name(int af, const char *ifname, const char *property, type value) { \
char buf[DECIMAL_STR_MAX(type)]; \
assert(pid > 0);
- r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+ r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
if (r < 0)
return r;
pid_t child;
int r;
- r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+ r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
if (r < 0)
return r;
/* Erase characters until the end of the line */
#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K"
+/* Erase characters until end of screen */
+#define ANSI_ERASE_TO_END_OF_SCREEN "\x1B[J"
+
/* Move cursor up one line */
#define ANSI_REVERSE_LINEFEED "\x1BM"
/* Always include UTC */
r = strv_extend(&zones, "UTC");
if (r < 0)
- return -ENOMEM;
+ return r;
strv_sort(zones);
strv_uniq(zones);
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_shell) ||
+ !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;
+
+ return r;
+ }
- if (gid) {
+ 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;
}
return strdup(NOBODY_USER_NAME);
if (uid_is_valid(uid)) {
- long bufsize;
-
- bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
- if (bufsize <= 0)
- bufsize = 4096;
-
- for (;;) {
- struct passwd pwbuf, *pw = NULL;
- _cleanup_free_ char *buf = NULL;
+ _cleanup_free_ struct passwd *pw = NULL;
- buf = malloc(bufsize);
- if (!buf)
- return NULL;
-
- r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw);
- if (r == 0 && pw)
- return strdup(pw->pw_name);
- if (r != ERANGE)
- break;
-
- if (bufsize > LONG_MAX/2) /* overflow check */
- return NULL;
-
- bufsize *= 2;
- }
+ r = getpwuid_malloc(uid, &pw);
+ if (r >= 0)
+ return strdup(pw->pw_name);
}
if (asprintf(&ret, UID_FMT, uid) < 0)
return strdup(NOBODY_GROUP_NAME);
if (gid_is_valid(gid)) {
- long bufsize;
+ _cleanup_free_ struct group *gr = NULL;
- bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
- if (bufsize <= 0)
- bufsize = 4096;
-
- for (;;) {
- struct group grbuf, *gr = NULL;
- _cleanup_free_ char *buf = NULL;
-
- buf = malloc(bufsize);
- if (!buf)
- return NULL;
-
- r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr);
- if (r == 0 && gr)
- return strdup(gr->gr_name);
- if (r != ERANGE)
- break;
-
- if (bufsize > LONG_MAX/2) /* overflow check */
- return NULL;
-
- bufsize *= 2;
- }
+ r = getgrgid_malloc(gid, &gr);
+ if (r >= 0)
+ return strdup(gr->gr_name);
}
if (asprintf(&ret, GID_FMT, gid) < 0)
}
int get_home_dir(char **ret) {
- struct passwd *p;
+ _cleanup_free_ struct passwd *p = NULL;
const char *e;
uid_t u;
+ int r;
assert(ret);
e = "/root";
goto found;
}
-
if (u == UID_NOBODY && synthesize_nobody()) {
e = "/";
goto found;
}
/* Check the database... */
- errno = 0;
- p = getpwuid(u);
- if (!p)
- return errno_or_else(ESRCH);
- e = p->pw_dir;
+ r = getpwuid_malloc(u, &p);
+ if (r < 0)
+ return r;
+ e = p->pw_dir;
if (!path_is_valid(e) || !path_is_absolute(e))
return -EINVAL;
}
int get_shell(char **ret) {
- struct passwd *p;
+ _cleanup_free_ struct passwd *p = NULL;
const char *e;
uid_t u;
+ int r;
assert(ret);
}
/* Check the database... */
- errno = 0;
- p = getpwuid(u);
- if (!p)
- return errno_or_else(ESRCH);
- e = p->pw_shell;
+ r = getpwuid_malloc(u, &p);
+ if (r < 0)
+ return r;
+ e = p->pw_shell;
if (!path_is_valid(e) || !path_is_absolute(e))
return -EINVAL;
return "/home";
}
+
+static size_t getpw_buffer_size(void) {
+ long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ return bufsize <= 0 ? 4096U : (size_t) bufsize;
+}
+
+static bool errno_is_user_doesnt_exist(int error) {
+ /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
+ * not found. */
+ return IN_SET(abs(error), ENOENT, ESRCH, EBADF, EPERM);
+}
+
+int getpwnam_malloc(const char *name, struct passwd **ret) {
+ size_t bufsize = getpw_buffer_size();
+ int r;
+
+ /* A wrapper around getpwnam_r() that allocates the necessary buffer on the heap. The caller must
+ * free() the returned sructured! */
+
+ if (isempty(name))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ void *buf = NULL;
+
+ buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize);
+ if (!buf)
+ return -ENOMEM;
+
+ struct passwd *pw = NULL;
+ r = getpwnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
+ if (r == 0) {
+ if (pw) {
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ return -ESRCH;
+ }
+
+ assert(r > 0);
+
+ /* getpwnam() may fail with ENOENT if /etc/passwd is missing. For us that is equivalent to
+ * the name not being defined. */
+ if (errno_is_user_doesnt_exist(r))
+ return -ESRCH;
+ if (r != ERANGE)
+ return -r;
+
+ if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
+ return -ENOMEM;
+ bufsize *= 2;
+ }
+}
+
+int getpwuid_malloc(uid_t uid, struct passwd **ret) {
+ size_t bufsize = getpw_buffer_size();
+ int r;
+
+ if (!uid_is_valid(uid))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ void *buf = NULL;
+
+ buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize);
+ if (!buf)
+ return -ENOMEM;
+
+ struct passwd *pw = NULL;
+ r = getpwuid_r(uid, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
+ if (r == 0) {
+ if (pw) {
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ return -ESRCH;
+ }
+
+ assert(r > 0);
+
+ if (errno_is_user_doesnt_exist(r))
+ return -ESRCH;
+ if (r != ERANGE)
+ return -r;
+
+ if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
+ return -ENOMEM;
+ bufsize *= 2;
+ }
+}
+
+static size_t getgr_buffer_size(void) {
+ long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
+ return bufsize <= 0 ? 4096U : (size_t) bufsize;
+}
+
+int getgrnam_malloc(const char *name, struct group **ret) {
+ size_t bufsize = getgr_buffer_size();
+ int r;
+
+ if (isempty(name))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ void *buf = NULL;
+
+ buf = malloc(ALIGN(sizeof(struct group)) + bufsize);
+ if (!buf)
+ return -ENOMEM;
+
+ struct group *gr = NULL;
+ r = getgrnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
+ if (r == 0) {
+ if (gr) {
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ return -ESRCH;
+ }
+
+ assert(r > 0);
+
+ if (errno_is_user_doesnt_exist(r))
+ return -ESRCH;
+ if (r != ERANGE)
+ return -r;
+
+ if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
+ return -ENOMEM;
+ bufsize *= 2;
+ }
+}
+
+int getgrgid_malloc(gid_t gid, struct group **ret) {
+ size_t bufsize = getgr_buffer_size();
+ int r;
+
+ if (!gid_is_valid(gid))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ void *buf = NULL;
+
+ buf = malloc(ALIGN(sizeof(struct group)) + bufsize);
+ if (!buf)
+ return -ENOMEM;
+
+ struct group *gr = NULL;
+ r = getgrgid_r(gid, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
+ if (r == 0) {
+ if (gr) {
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ return -ESRCH;
+ }
+
+ assert(r > 0);
+
+ if (errno_is_user_doesnt_exist(r))
+ return -ESRCH;
+ if (r != ERANGE)
+ return -r;
+
+ if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
+ return -ENOMEM;
+ bufsize *= 2;
+ }
+}
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);
* Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325.
*/
#define PASSWORD_UNPROVISIONED "!unprovisioned"
+
+int getpwuid_malloc(uid_t uid, struct passwd **ret);
+int getpwnam_malloc(const char *name, struct passwd **ret);
+
+int getgrnam_malloc(const char *name, struct group **ret);
+int getgrgid_malloc(gid_t gid, struct group **ret);
{ "ACRNACRNACRN", VIRTUALIZATION_ACRN },
/* https://www.lockheedmartin.com/en-us/products/Hardened-Security-for-Intel-Processors.html */
{ "SRESRESRESRE", VIRTUALIZATION_SRE },
+ { "Apple VZ", VIRTUALIZATION_APPLE },
};
uint32_t eax, ebx, ecx, edx;
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/";
-}
assert(state);
err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_DT_FIXUP_PROTOCOL), NULL, (void **) &fixup);
+ /* Skip fixup if we cannot locate device tree fixup protocol */
if (err != EFI_SUCCESS)
- return log_error_status(EFI_SUCCESS, "Could not locate device tree fixup protocol, skipping.");
+ return EFI_SUCCESS;
size = devicetree_allocated(state);
err = fixup->Fixup(fixup, PHYSICAL_ADDRESS_TO_POINTER(state->addr), &size,
}
_malloc_ _alloc_(1, 2) _returns_nonnull_ _warn_unused_result_
-static inline void *xmalloc_multiply(size_t size, size_t n) {
+static inline void *xmalloc_multiply(size_t n, size_t size) {
assert_se(!__builtin_mul_overflow(size, n, &size));
return xmalloc(size);
}
return memcpy(xmalloc(l), p, l);
}
-#define xnew(type, n) ((type *) xmalloc_multiply(sizeof(type), (n)))
+#define xnew(type, n) ((type *) xmalloc_multiply((n), sizeof(type)))
typedef struct {
EFI_PHYSICAL_ADDRESS addr;
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 (all_unified) {
while (!isempty(l)) {
- if (sscanf(l, "rbytes=%" SCNu64, &k))
+ if (sscanf(l, "rbytes=%" SCNu64, &k) == 1)
rd += k;
- else if (sscanf(l, "wbytes=%" SCNu64, &k))
+ else if (sscanf(l, "wbytes=%" SCNu64, &k) == 1)
wr += k;
l += strcspn(l, WHITESPACE);
return;
}
- r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_NORMAL-8);
+ r = sd_event_source_set_priority(s, EVENT_PRIORITY_CGROUP_OOM);
if (r < 0) {
log_error_errno(r, "Failed to set priority of cgroup oom event source: %m");
return;
/* Schedule cgroup empty checks early, but after having processed service notification messages or
* SIGCHLD signals, so that a cgroup running empty is always just the last safety net of
* notification, and we collected the metadata the notification and SIGCHLD stuff offers first. */
- r = sd_event_source_set_priority(m->cgroup_empty_event_source, SD_EVENT_PRIORITY_NORMAL-5);
+ r = sd_event_source_set_priority(m->cgroup_empty_event_source, EVENT_PRIORITY_CGROUP_EMPTY);
if (r < 0)
return log_error_errno(r, "Failed to set priority of cgroup empty event source: %m");
/* Process cgroup empty notifications early. Note that when this event is dispatched it'll
* just add the unit to a cgroup empty queue, hence let's run earlier than that. Also see
* handling of cgroup agent notifications, for the classic cgroup hierarchy support. */
- r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_NORMAL-9);
+ r = sd_event_source_set_priority(m->cgroup_inotify_event_source, EVENT_PRIORITY_CGROUP_INOTIFY);
if (r < 0)
return log_error_errno(r, "Failed to set priority of inotify event source: %m");
DEFINE_STRING_TABLE_LOOKUP(cgroup_memory_accounting_metric, CGroupMemoryAccountingMetric);
-static const char *const cgroup_limit_type_table[_CGROUP_LIMIT_TYPE_MAX] = {
+static const char *const cgroup_effective_limit_type_table[_CGROUP_LIMIT_TYPE_MAX] = {
[CGROUP_LIMIT_MEMORY_MAX] = "EffectiveMemoryMax",
[CGROUP_LIMIT_MEMORY_HIGH] = "EffectiveMemoryHigh",
[CGROUP_LIMIT_TASKS_MAX] = "EffectiveTasksMax",
};
-DEFINE_STRING_TABLE_LOOKUP(cgroup_limit_type, CGroupLimitType);
+DEFINE_STRING_TABLE_LOOKUP(cgroup_effective_limit_type, CGroupLimitType);
const char* cgroup_io_accounting_metric_to_string(CGroupIOAccountingMetric m) _const_;
CGroupIOAccountingMetric cgroup_io_accounting_metric_from_string(const char *s) _pure_;
-const char* cgroup_limit_type_to_string(CGroupLimitType m) _const_;
-CGroupLimitType cgroup_limit_type_from_string(const char *s) _pure_;
+const char* cgroup_effective_limit_type_to_string(CGroupLimitType m) _const_;
+CGroupLimitType cgroup_effective_limit_type_from_string(const char *s) _pure_;
const char* cgroup_memory_accounting_metric_to_string(CGroupMemoryAccountingMetric m) _const_;
CGroupMemoryAccountingMetric cgroup_memory_accounting_metric_from_string(const char *s) _pure_;
}
}
- r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL);
+ r = varlink_server_attach_event(s, m->event, EVENT_PRIORITY_IPC);
if (r < 0)
return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
if (r < 0)
return r;
- r = varlink_attach_event(link, m->event, SD_EVENT_PRIORITY_NORMAL);
+ r = varlink_attach_event(link, m->event, EVENT_PRIORITY_IPC);
if (r < 0)
return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
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();
assert(reply);
assert(property);
- assert_se((type = cgroup_limit_type_from_string(property)) >= 0);
+ assert_se((type = cgroup_effective_limit_type_from_string(property)) >= 0);
(void) unit_get_effective_limit(u, type, &value);
return sd_bus_message_append(reply, "t", value);
}
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);
log_debug("Accepting direct incoming connection from " PID_FMT " (%s) [%s]", pid, strna(comm), strna(description));
}
- r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL);
+ r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC);
if (r < 0) {
log_warning_errno(r, "Failed to attach new connection bus to event loop: %m");
return 0;
if (r < 0)
return log_error_errno(r, "Failed to connect to API bus: %m");
- r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL);
+ r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC);
if (r < 0)
return log_error_errno(r, "Failed to attach API bus to event loop: %m");
if (r < 0)
return log_error_errno(r, "Failed to connect to system bus: %m");
- r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL);
+ r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC);
if (r < 0)
return log_error_errno(r, "Failed to attach system bus to event loop: %m");
}
/* Some superficial check whether this UID/GID might already be taken by some static user */
- if (getpwuid(candidate) ||
- getgrgid((gid_t) candidate) ||
+ if (getpwuid_malloc(candidate, /* ret= */ NULL) >= 0 ||
+ getgrgid_malloc((gid_t) candidate, /* ret= */ NULL) >= 0 ||
search_ipc(candidate, (gid_t) candidate) != 0) {
(void) unlink(lock_path);
continue;
/* First, let's parse this as numeric UID */
r = parse_uid(d->name, &num);
if (r < 0) {
- struct passwd *p;
- struct group *g;
+ _cleanup_free_ struct passwd *p = NULL;
+ _cleanup_free_ struct group *g = NULL;
if (is_user) {
/* OK, this is not a numeric UID. Let's see if there's a user by this name */
- p = getpwnam(d->name);
- if (p) {
+ if (getpwnam_malloc(d->name, &p) >= 0) {
num = p->pw_uid;
gid = p->pw_gid;
} else {
/* if the user does not exist but the group with the same name exists, refuse operation */
- g = getgrnam(d->name);
- if (g)
+ if (getgrnam_malloc(d->name, /* ret= */ NULL) >= 0)
return -EILSEQ;
}
} else {
/* Let's see if there's a group by this name */
- g = getgrnam(d->name);
- if (g)
+ if (getgrnam_malloc(d->name, &g) >= 0)
num = (uid_t) g->gr_gid;
else {
/* if the group does not exist but the user with the same name exists, refuse operation */
- p = getpwnam(d->name);
- if (p)
+ if (getpwnam_malloc(d->name, /* ret= */ NULL) >= 0)
return -EILSEQ;
}
}
uid_lock_fd = new_uid_lock_fd;
}
} else if (is_user && !uid_is_dynamic(num)) {
- struct passwd *p;
+ _cleanup_free_ struct passwd *p = NULL;
/* Statically allocated user may have different uid and gid. So, let's obtain the gid. */
- errno = 0;
- p = getpwuid(num);
- if (!p)
- return errno_or_else(ESRCH);
+ r = getpwuid_malloc(num, &p);
+ if (r < 0)
+ return r;
gid = p->pw_gid;
}
}
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;
* 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;
}
(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);
return log_error_errno(r, "Failed to create time change event source: %m");
/* Schedule this slightly earlier than the .timer event sources */
- r = sd_event_source_set_priority(m->time_change_event_source, SD_EVENT_PRIORITY_NORMAL-1);
+ r = sd_event_source_set_priority(m->time_change_event_source, EVENT_PRIORITY_TIME_CHANGE);
if (r < 0)
return log_error_errno(r, "Failed to set priority of time change event sources: %m");
return log_error_errno(r, "Failed to create timezone change event source: %m");
/* Schedule this slightly earlier than the .timer event sources */
- r = sd_event_source_set_priority(new_event, SD_EVENT_PRIORITY_NORMAL-1);
+ r = sd_event_source_set_priority(new_event, EVENT_PRIORITY_TIME_ZONE);
if (r < 0)
return log_error_errno(r, "Failed to set priority of timezone change event sources: %m");
* notify processing can still figure out to which process/service a message belongs, before we reap the
* process. Also, process this before handling cgroup notifications, so that we always collect child exit
* status information before detecting that there's no process in a cgroup. */
- r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-6);
+ r = sd_event_source_set_priority(m->signal_event_source, EVENT_PRIORITY_SIGNALS);
if (r < 0)
return r;
if (r < 0)
return r;
- r = sd_event_source_set_priority(m->run_queue_event_source, SD_EVENT_PRIORITY_IDLE);
+ r = sd_event_source_set_priority(m->run_queue_event_source, EVENT_PRIORITY_RUN_QUEUE);
if (r < 0)
return r;
if (r < 0)
return r;
- r = sd_event_source_set_priority(m->sigchld_event_source, SD_EVENT_PRIORITY_NORMAL-7);
+ r = sd_event_source_set_priority(m->sigchld_event_source, EVENT_PRIORITY_SIGCHLD);
if (r < 0)
return r;
m->executor_fd = open(SYSTEMD_EXECUTOR_BINARY_PATH, O_CLOEXEC|O_PATH);
if (m->executor_fd < 0)
- return log_warning_errno(errno,
- "Failed to open executor binary '%s': %m",
- SYSTEMD_EXECUTOR_BINARY_PATH);
+ return log_emergency_errno(errno,
+ "Failed to open executor binary '%s': %m",
+ SYSTEMD_EXECUTOR_BINARY_PATH);
} else if (!FLAGS_SET(test_run_flags, MANAGER_TEST_DONT_OPEN_EXECUTOR)) {
_cleanup_free_ char *self_exe = NULL, *executor_path = NULL;
_cleanup_close_ int self_dir_fd = -EBADF;
/* Process notification messages a bit earlier than SIGCHLD, so that we can still identify to which
* service an exit message belongs. */
- r = sd_event_source_set_priority(m->notify_event_source, SD_EVENT_PRIORITY_NORMAL-8);
+ r = sd_event_source_set_priority(m->notify_event_source, EVENT_PRIORITY_NOTIFY);
if (r < 0)
return log_error_errno(r, "Failed to set priority of notify event source: %m");
/* Process cgroups notifications early. Note that when the agent notification is received
* we'll just enqueue the unit in the cgroup empty queue, hence pick a high priority than
* that. Also see handling of cgroup inotify for the unified cgroup stuff. */
- r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-9);
+ r = sd_event_source_set_priority(m->cgroups_agent_event_source, EVENT_PRIORITY_CGROUP_AGENT);
if (r < 0)
return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m");
/* Process even earlier than the notify event source, so that we always know first about valid UID/GID
* resolutions */
- r = sd_event_source_set_priority(m->user_lookup_event_source, SD_EVENT_PRIORITY_NORMAL-11);
+ r = sd_event_source_set_priority(m->user_lookup_event_source, EVENT_PRIORITY_USER_LOOKUP);
if (r < 0)
return log_error_errno(errno, "Failed to set priority of user lookup event source: %m");
void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope);
void unit_defaults_done(UnitDefaults *defaults);
+
+enum {
+ /* most important … */
+ EVENT_PRIORITY_USER_LOOKUP = SD_EVENT_PRIORITY_NORMAL-10,
+ EVENT_PRIORITY_MOUNT_TABLE = SD_EVENT_PRIORITY_NORMAL-9,
+ EVENT_PRIORITY_SWAP_TABLE = SD_EVENT_PRIORITY_NORMAL-9,
+ EVENT_PRIORITY_CGROUP_AGENT = SD_EVENT_PRIORITY_NORMAL-8, /* cgroupv1 */
+ EVENT_PRIORITY_CGROUP_INOTIFY = SD_EVENT_PRIORITY_NORMAL-8, /* cgroupv2 */
+ EVENT_PRIORITY_CGROUP_OOM = SD_EVENT_PRIORITY_NORMAL-7,
+ EVENT_PRIORITY_EXEC_FD = SD_EVENT_PRIORITY_NORMAL-6,
+ EVENT_PRIORITY_NOTIFY = SD_EVENT_PRIORITY_NORMAL-5,
+ EVENT_PRIORITY_SIGCHLD = SD_EVENT_PRIORITY_NORMAL-4,
+ EVENT_PRIORITY_SIGNALS = SD_EVENT_PRIORITY_NORMAL-3,
+ EVENT_PRIORITY_CGROUP_EMPTY = SD_EVENT_PRIORITY_NORMAL-2,
+ EVENT_PRIORITY_TIME_CHANGE = SD_EVENT_PRIORITY_NORMAL-1,
+ EVENT_PRIORITY_TIME_ZONE = SD_EVENT_PRIORITY_NORMAL-1,
+ EVENT_PRIORITY_IPC = SD_EVENT_PRIORITY_NORMAL,
+ EVENT_PRIORITY_REWATCH_PIDS = SD_EVENT_PRIORITY_IDLE,
+ EVENT_PRIORITY_SERVICE_WATCHDOG = SD_EVENT_PRIORITY_IDLE+1,
+ EVENT_PRIORITY_RUN_QUEUE = SD_EVENT_PRIORITY_IDLE+2,
+ /* … to least important */
+};
goto fail;
}
- r = sd_event_source_set_priority(m->mount_event_source, SD_EVENT_PRIORITY_NORMAL-10);
+ r = sd_event_source_set_priority(m->mount_event_source, EVENT_PRIORITY_MOUNT_TABLE);
if (r < 0) {
log_error_errno(r, "Failed to adjust mount watch priority: %m");
goto fail;
/* If the triggered unit is already running, so are we */
trigger = UNIT_TRIGGER(UNIT(p));
- if (trigger && !UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(trigger))) {
+ if (trigger && !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(trigger))) {
path_set_state(p, PATH_RUNNING);
path_unwatch(p);
return;
return;
if (p->state == PATH_RUNNING &&
- UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) {
+ UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
if (!on_defer)
log_unit_debug(u, "Got notified about unit deactivation.");
} else if (p->state == PATH_WAITING &&
- !UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) {
+ !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
if (!on_defer)
log_unit_debug(u, "Got notified about unit activation.");
} else
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;
/* Let's process everything else which might be a sign
* of living before we consider a service died. */
- r = sd_event_source_set_priority(s->watchdog_event_source, SD_EVENT_PRIORITY_IDLE);
+ r = sd_event_source_set_priority(s->watchdog_event_source, EVENT_PRIORITY_SERVICE_WATCHDOG);
}
if (r < 0)
log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m");
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Failed to allocate exec_fd event source: %m");
- /* This is a bit lower priority than SIGCHLD, as that carries a lot more interesting failure information */
+ /* This is a bit higher priority than SIGCHLD, to make sure we don't confuse the case "failed to
+ * start" from the case "succeeded to start, but failed immediately after". */
- r = sd_event_source_set_priority(source, SD_EVENT_PRIORITY_NORMAL-3);
+ r = sd_event_source_set_priority(source, EVENT_PRIORITY_EXEC_FD);
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Failed to adjust priority of exec_fd event source: %m");
case STATE_EXEC_COMMAND_ARGS:
r = strv_extend(&argv, arg);
if (r < 0)
- return -ENOMEM;
+ return r;
break;
default:
assert_not_reached();
/* Dispatch this before we dispatch SIGCHLD, so that
* we always get the events from /proc/swaps before
* the SIGCHLD of /sbin/swapon. */
- r = sd_event_source_set_priority(m->swap_event_source, SD_EVENT_PRIORITY_NORMAL-10);
+ r = sd_event_source_set_priority(m->swap_event_source, EVENT_PRIORITY_SWAP_TABLE);
if (r < 0) {
log_error_errno(r, "Failed to change /proc/swaps priority: %m");
goto fail;
if (r < 0)
return log_error_errno(r, "Failed to allocate event source for tidying watched PIDs: %m");
- r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
+ r = sd_event_source_set_priority(s, EVENT_PRIORITY_REWATCH_PIDS);
if (r < 0)
return log_error_errno(r, "Failed to adjust priority of event source for tidying watched PIDs: %m");
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;
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();
}
assert(link);
- json_variant_sensitive(parameters);
-
r = varlink_dispatch(link, parameters, dispatch_table, &p);
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;
/* 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");
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();
" -u --umount Unmount the image from the specified directory\n"
" -U Shortcut for --umount --rmdir\n"
" --attach Attach the disk image to a loopback block device\n"
- " --detach Detach a loopback block device gain\n"
+ " --detach Detach a loopback block device again\n"
" -l --list List all the files and directories of the specified\n"
" OS image\n"
" --mtree Show BSD mtree manifest of OS image\n"
static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) {
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
- _cleanup_(rmdir_and_freep) char *created_dir = NULL;
- _cleanup_free_ char *temp = NULL;
+ _cleanup_free_ char *t = NULL;
const char *root;
int r;
if (r < 0)
return log_error_errno(r, "Failed to detach mount namespace: %m");
- r = tempfn_random_child(NULL, program_invocation_short_name, &temp);
- if (r < 0)
- return log_error_errno(r, "Failed to generate temporary mount directory: %m");
-
- r = mkdir_p(temp, 0700);
+ r = get_common_dissect_directory(&t);
if (r < 0)
- return log_error_errno(r, "Failed to create mount point: %m");
-
- created_dir = TAKE_PTR(temp);
+ return log_error_errno(r, "Failed generate private mount directory: %m");
r = dissected_image_mount_and_warn(
m,
- created_dir,
+ t,
/* uid_shift= */ UID_INVALID,
/* uid_range= */ UID_INVALID,
/* userns_fd= */ -EBADF,
if (r < 0)
return r;
- mounted_dir = TAKE_PTR(created_dir);
+ mounted_dir = TAKE_PTR(t);
r = loop_device_flock(d, LOCK_UN);
if (r < 0)
if (r < 0)
return r;
- r = strv_extend_front(&dirs, c);
+ r = strv_consume_prepend(&dirs, TAKE_PTR(c));
if (r < 0)
return r;
return log_error_errno(k, "Failed to check if directory file descriptor is root: %m");
if (arg_copy_root_shell && k == 0) {
- struct passwd *p;
+ _cleanup_free_ struct passwd *p = NULL;
- errno = 0;
- p = getpwnam("root");
- if (!p)
- return log_error_errno(errno_or_else(EIO), "Failed to find passwd entry for root: %m");
+ r = getpwnam_malloc("root", &p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to find passwd entry for root: %m");
r = free_and_strdup(&arg_root_shell, p->pw_shell);
if (r < 0)
static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL;
- struct passwd *pw;
- struct group *gr;
bool signed_locally;
Home *other;
int r;
if (other)
return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", hr->user_name);
- pw = getpwnam(hr->user_name);
- if (pw)
+ r = getpwnam_malloc(hr->user_name, /* ret= */ NULL);
+ if (r >= 0)
return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", hr->user_name);
+ if (r != -ESRCH)
+ return r;
- gr = getgrnam(hr->user_name);
- if (gr)
+ r = getgrnam_malloc(hr->user_name, /* ret= */ NULL);
+ if (r >= 0)
return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", hr->user_name);
+ if (r != -ESRCH)
+ return r;
r = manager_verify_user_record(m, hr);
switch (r) {
}
if (uid_is_valid(hr->uid)) {
+ _cleanup_free_ struct passwd *pw = NULL;
+ _cleanup_free_ struct group *gr = NULL;
+
other = hashmap_get(m->homes_by_uid, UID_TO_PTR(hr->uid));
if (other)
return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by home %s, refusing.", hr->uid, other->user_name);
- pw = getpwuid(hr->uid);
- if (pw)
+ r = getpwuid_malloc(hr->uid, &pw);
+ if (r >= 0)
return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by NSS user %s, refusing.", hr->uid, pw->pw_name);
+ if (r != -ESRCH)
+ return r;
- gr = getgrgid(hr->uid);
- if (gr)
+ r = getgrgid_malloc(hr->uid, &gr);
+ if (r >= 0)
return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use as GID by NSS group %s, refusing.", hr->uid, gr->gr_name);
+ if (r != -ESRCH)
+ return r;
} else {
r = manager_augment_record_with_uid(m, hr);
if (r < 0)
assert(ret);
for (;;) {
- struct passwd *pw;
- struct group *gr;
+ _cleanup_free_ struct passwd *pw = NULL;
+ _cleanup_free_ struct group *gr = NULL;
uid_t candidate;
Home *other;
continue;
}
- pw = getpwuid(candidate);
- if (pw) {
+ r = getpwuid_malloc(candidate, &pw);
+ if (r >= 0) {
log_debug("Candidate UID " UID_FMT " already registered by another user in NSS (%s), let's try another.",
candidate, pw->pw_name);
continue;
}
+ if (r != -ESRCH) {
+ log_debug_errno(r, "Failed to check if an NSS user is already registered for candidate UID " UID_FMT ", assuming there might be: %m", candidate);
+ continue;
+ }
- gr = getgrgid((gid_t) candidate);
- if (gr) {
+ r = getgrgid_malloc((gid_t) candidate, &gr);
+ if (r >= 0) {
log_debug("Candidate UID " UID_FMT " already registered by another group in NSS (%s), let's try another.",
candidate, gr->gr_name);
continue;
}
+ if (r != -ESRCH) {
+ log_debug_errno(r, "Failed to check if an NSS group is already registered for candidate UID " UID_FMT ", assuming there might be: %m", candidate);
+ continue;
+ }
r = search_ipc(candidate, (gid_t) candidate);
if (r < 0)
}
} else {
/* Check NSS, in case there's another user or group by this name */
- if (getpwnam(user_name) || getgrnam(user_name)) {
+ if (getpwnam_malloc(user_name, /* ret= */ NULL) >= 0 || getgrnam_malloc(user_name, /* ret= */ NULL) >= 0) {
log_debug("Found an existing user or group by name '%s', ignoring image '%s'.", user_name, image_path);
return 0;
}
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 (r < 0)
return r;
- r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
if (r < 0)
return r;
/* → Shrink */
if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
- r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
if (r < 0)
return r;
}
} else { /* → Grow */
if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
- r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
if (r < 0)
return r;
}
*
* · The record we got passed from the host
* · The record included in the LUKS header (only if LUKS is used)
- * · The record in the home directory itself (~.identity)
+ * · The record in the home directory itself (~/.identity)
*
* Now we have to reconcile all three, and let the newest one win. */
return 0;
}
-int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home) {
+int home_store_embedded_identity(UserRecord *h, int root_fd, UserRecord *old_home) {
_cleanup_(user_record_unrefp) UserRecord *embedded = NULL;
int r;
assert(h);
assert(root_fd >= 0);
- assert(uid_is_valid(uid));
r = user_record_clone(h, USER_RECORD_EXTRACT_EMBEDDED|USER_RECORD_PERMISSIVE, &embedded);
if (r < 0)
if (r < 0)
return r;
- r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
if (r < 0)
return r;
if (r < 0)
return r;
- r = home_store_embedded_identity(h, dir_fd, h->uid, NULL);
+ r = home_store_embedded_identity(h, dir_fd, NULL);
if (r < 0)
return r;
if (r < 0)
return r;
- r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup.root_fd, embedded_home);
if (r < 0)
return r;
if (r < 0)
return r;
- r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup.root_fd, embedded_home);
if (r < 0)
return r;
int home_populate(UserRecord *h, int dir_fd);
int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, PasswordCache *cache, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
-int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home);
+int home_store_embedded_identity(UserRecord *h, int root_fd, UserRecord *old_home);
int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup);
int user_record_authenticate(UserRecord *h, UserRecord *secret, PasswordCache *cache, bool strict_verify);
return r;
}
-static int simple_varlink_call(const char *option, const char *method) {
- _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
- const char *error, *fn;
+static int varlink_connect_journal(Varlink **ret_link) {
+ const char *address;
int r;
- if (arg_machine)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "%s is not supported in conjunction with --machine=.", option);
-
- fn = arg_namespace ?
- strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") :
- "/run/systemd/journal/io.systemd.journal";
+ address = arg_namespace ?
+ strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") :
+ "/run/systemd/journal/io.systemd.journal";
- r = varlink_connect_address(&link, fn);
+ r = varlink_connect_address(ret_link, address);
if (r < 0)
- return log_error_errno(r, "Failed to connect to %s: %m", fn);
+ return r;
- (void) varlink_set_description(link, "journal");
- (void) varlink_set_relative_timeout(link, USEC_INFINITY);
-
- r = varlink_call(link, method, NULL, NULL, &error, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to execute varlink call: %m");
- if (error)
- return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
- "Failed to execute varlink call: %s", error);
+ (void) varlink_set_description(*ret_link, "journal");
+ (void) varlink_set_relative_timeout(*ret_link, USEC_INFINITY);
return 0;
}
static int flush_to_var(void) {
+ _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+ int r;
+
+ if (arg_machine || arg_namespace)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "--flush is not supported in conjunction with %s.",
+ arg_machine ? "--machine=" : "--namespace=");
+
if (access("/run/systemd/journal/flushed", F_OK) >= 0)
return 0; /* Already flushed, no need to contact journald */
if (errno != ENOENT)
return log_error_errno(errno, "Unable to check for existence of /run/systemd/journal/flushed: %m");
- return simple_varlink_call("--flush", "io.systemd.Journal.FlushToVar");
+ r = varlink_connect_journal(&link);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+ return varlink_call_and_log(link, "io.systemd.Journal.FlushToVar", /* parameters= */ NULL, /* ret_parameters= */ NULL);
}
static int relinquish_var(void) {
- return simple_varlink_call("--relinquish-var/--smart-relinquish-var", "io.systemd.Journal.RelinquishVar");
+ _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+ int r;
+
+ if (arg_machine || arg_namespace)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "--(smart-)relinquish-var is not supported in conjunction with %s.",
+ arg_machine ? "--machine=" : "--namespace=");
+
+ r = varlink_connect_journal(&link);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+ return varlink_call_and_log(link, "io.systemd.Journal.RelinquishVar", /* parameters= */ NULL, /* ret_parameters= */ NULL);
}
static int rotate(void) {
- return simple_varlink_call("--rotate", "io.systemd.Journal.Rotate");
+ _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+ int r;
+
+ if (arg_machine)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "--rotate is not supported in conjunction with --machine=.");
+
+ r = varlink_connect_journal(&link);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+ return varlink_call_and_log(link, "io.systemd.Journal.Rotate", /* parameters= */ NULL, /* ret_parameters= */ NULL);
}
static int sync_journal(void) {
- return simple_varlink_call("--sync", "io.systemd.Journal.Synchronize");
+ _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+ int r;
+
+ if (arg_machine)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "--sync is not supported in conjunction with --machine=.");
+
+ r = varlink_connect_journal(&link);
+ if (ERRNO_IS_NEG_DISCONNECT(r) && arg_namespace)
+ /* If the namespaced sd-journald instance was shut down due to inactivity, it should already
+ * be synchronized */
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+ return varlink_call_and_log(link, "io.systemd.Journal.Synchronize", /* parameters= */ NULL, /* ret_parameters= */ NULL);
}
static int action_list_fields(sd_journal *j) {
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();
}
}
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.");
rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */
rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false);
rt->icmp6_ratelimit_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false);
+ rt->retransmission_time_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false);
rt->preference = (rt->flags >> 3) & 3;
if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
return 0;
}
+int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->retransmission_time_usec;
+ return 0;
+}
+
int sd_ndisc_router_get_icmp6_ratelimit(sd_ndisc_router *rt, uint64_t *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
uint64_t flags;
unsigned preference;
uint64_t lifetime_usec;
+ usec_t retransmission_time_usec;
uint8_t hop_limit;
uint32_t mtu;
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. */
static void router_dump(sd_ndisc_router *rt) {
struct in6_addr addr;
uint8_t hop_limit;
- usec_t t, lifetime;
+ usec_t t, lifetime, retrans_time;
uint64_t flags;
uint32_t mtu;
unsigned preference;
assert_se(sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
log_info("Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t));
+ assert_se(sd_ndisc_router_get_retransmission_time(rt, &retrans_time) >= 0);
+ log_info("Retransmission Time: %s", FORMAT_TIMESPAN(retrans_time, USEC_PER_SEC));
+
if (sd_ndisc_router_get_mtu(rt, &mtu) < 0)
log_info("No MTU set");
else
log_debug("sd-bus: connecting bus%s%s to namespace of PID "PID_FMT"...",
b->description ? " " : "", strempty(b->description), b->nspid);
- r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+ r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
if (r < 0)
return log_debug_errno(r, "Failed to open namespace of PID "PID_FMT": %m", b->nspid);
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);
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");
int device_get_property_int(sd_device *device, const char *key, int *ret);
int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value);
int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value);
+int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value);
int device_get_sysattr_bool(sd_device *device, const char *sysattr);
int device_get_device_id(sd_device *device, const char **ret);
int device_get_devlink_priority(sd_device *device, int *ret);
return v > 0;
}
+int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value) {
+ const char *value;
+ int r;
+
+ r = sd_device_get_sysattr_value(device, sysattr, &value);
+ if (r < 0)
+ return r;
+
+ uint32_t v;
+ r = safe_atou32(value, &v);
+ if (r < 0)
+ return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr);
+
+ if (ret_value)
+ *ret_value = v;
+ /* We return "true" if the value is positive. */
+ return v > 0;
+}
+
int device_get_sysattr_bool(sd_device *device, const char *sysattr) {
const char *value;
int r;
return 0;
}
+
+int event_add_child_pidref(
+ sd_event *e,
+ sd_event_source **s,
+ const PidRef *pid,
+ int options,
+ sd_event_child_handler_t callback,
+ void *userdata) {
+
+ if (!pidref_is_set(pid))
+ return -ESRCH;
+
+ if (pid->fd >= 0)
+ return sd_event_add_child_pidfd(e, s, pid->fd, options, callback, userdata);
+
+ return sd_event_add_child(e, s, pid->pid, options, callback, userdata);
+}
#include "sd-event.h"
+#include "pidref.h"
+
int event_reset_time(
sd_event *e,
sd_event_source **s,
}
int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata);
+
+int event_add_child_pidref(sd_event *e, sd_event_source **s, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata);
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;
+}
[NL80211_ATTR_SSID] = BUILD_POLICY_WITH_SIZE(BINARY, IEEE80211_MAX_SSID_LEN),
[NL80211_ATTR_STATUS_CODE] = BUILD_POLICY(U16),
[NL80211_ATTR_4ADDR] = BUILD_POLICY(U8),
+ [NL80211_ATTR_NETNS_FD] = BUILD_POLICY(U32),
};
/***************** genl wireguard type systems *****************/
#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(name);
/* Assign the requested name. */
+
+ if (!*rtnl) {
+ r = sd_netlink_open(rtnl);
+ if (r < 0)
+ return r;
+ }
+
r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex);
if (r < 0)
return r;
return sd_netlink_call(*rtnl, message, 0, NULL);
}
+int rtnl_rename_link(sd_netlink **rtnl, const char *orig_name, const char *new_name) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL;
+ int r, ifindex;
+
+ assert(orig_name);
+ assert(new_name);
+
+ /* This does not check alternative names. Callers must check the requested name is not used as an
+ * alternative name. */
+
+ if (streq(orig_name, new_name))
+ return 0;
+
+ if (!ifname_valid(new_name))
+ return -EINVAL;
+
+ if (!rtnl)
+ rtnl = &our_rtnl;
+ if (!*rtnl) {
+ r = sd_netlink_open(rtnl);
+ if (r < 0)
+ return r;
+ }
+
+ ifindex = rtnl_resolve_ifname(rtnl, orig_name);
+ if (ifindex < 0)
+ return ifindex;
+
+ return set_link_name(rtnl, ifindex, new_name);
+}
+
int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const *alternative_names) {
_cleanup_strv_free_ char **original_altnames = NULL, **new_altnames = NULL;
bool altname_deleted = false;
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;
}
int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret);
+int rtnl_rename_link(sd_netlink **rtnl, const char *orig_name, const char *new_name);
int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const* alternative_names);
static inline int rtnl_append_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names) {
return rtnl_set_link_name(rtnl, ifindex, NULL, alternative_names);
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"));
+ resolved = mfree(resolved);
+
+ assert_se(rtnl_rename_link(&rtnl, "test-shortname", "test-shortname") >= 0);
+ assert_se(rtnl_rename_link(&rtnl, "test-shortname", "test-shortname2") >= 0);
+ assert_se(rtnl_rename_link(NULL, "test-shortname2", "test-shortname3") >= 0);
+
+ assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-additional-name", &resolved) == ifindex);
+ assert_se(streq_ptr(resolved, "test-shortname3"));
+ resolved = mfree(resolved);
+
+ assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-shortname3", &resolved) == ifindex);
+ assert_se(streq_ptr(resolved, "test-shortname3"));
+ resolved = mfree(resolved);
}
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;
+}
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(
" --kill-whom=WHOM Whom to send signal to\n"
" -s --signal=SIGNAL Which signal to send\n"
" -n --lines=INTEGER Number of journal entries to show\n"
- " -o --output=STRING Change journal output mode (short, short-precise,\n"
+ " --json=MODE Generate JSON output for list-sessions/users/seats\n"
+ " (takes one of pretty, short, or off)\n"
+ " -j Same as --json=pretty on tty, --json=short otherwise\n"
+ " -o --output=MODE Change journal output mode (short, short-precise,\n"
" short-iso, short-iso-precise, short-full,\n"
" short-monotonic, short-unix, short-delta,\n"
" json, json-pretty, json-sse, json-seq, cat,\n"
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 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 (handle_action_sleep_supported(*i))
+ (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.");
};
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,
assert(m);
assert(ret);
- if (SEAT_IS_SELF(name)) /* the caller's own session */
+ if (SESSION_IS_SELF(name)) /* the caller's own session */
return get_sender_session(m, message, false, error, ret);
- if (SEAT_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */
+ if (SESSION_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */
return get_sender_session(m, message, true, error, ret);
session = hashmap_get(m->sessions, name);
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);
static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ _cleanup_free_ struct passwd *pw = NULL;
_cleanup_free_ char *cc = NULL;
Manager *m = ASSERT_PTR(userdata);
int r, b, interactive;
- struct passwd *pw;
const char *path;
uint32_t uid, auth_uid;
if (r < 0)
return r;
- errno = 0;
- pw = getpwuid(uid);
- if (!pw)
- return errno_or_else(ENOENT);
+ r = getpwuid_malloc(uid, &pw);
+ if (r < 0)
+ return r;
r = bus_verify_polkit_async_full(
message,
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_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),
return 0;
}
- LIST_FOREACH(sessions_by_user, s, u->sessions) {
- int k;
-
- k = session_stop(s, force);
- if (k < 0)
- r = k;
- }
+ LIST_FOREACH(sessions_by_user, s, u->sessions)
+ RET_GATHER(r, session_stop(s, force));
user_stop_service(u, force);
}
int user_finalize(User *u) {
- int r = 0, k;
+ int r = 0;
assert(u);
if (u->started)
log_debug("User %s logged out.", u->user_record->user_name);
- LIST_FOREACH(sessions_by_user, s, u->sessions) {
- k = session_finalize(s);
- if (k < 0)
- r = k;
- }
+ LIST_FOREACH(sessions_by_user, s, u->sessions)
+ RET_GATHER(r, session_finalize(s));
/* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs
* are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to
* cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because
* a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up,
* and do it only for normal users. */
- if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) {
- k = clean_ipc_by_uid(u->user_record->uid);
- if (k < 0)
- r = k;
- }
+ if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid))
+ RET_GATHER(r, clean_ipc_by_uid(u->user_record->uid));
(void) unlink(u->state_file);
user_add_to_gc_queue(u);
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"/>
const char *runtime_max_sec;
} SessionContext;
-static int create_session_message(sd_bus *bus, pam_handle_t *handle, const SessionContext *context, bool avoid_pidfd, sd_bus_message **ret) {
+static int create_session_message(
+ sd_bus *bus,
+ pam_handle_t *handle,
+ const SessionContext *context,
+ bool avoid_pidfd,
+ sd_bus_message **ret) {
+
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
- int r, pidfd = -EBADFD;
+ _cleanup_close_ int pidfd = -EBADF;
+ int r;
assert(bus);
assert(handle);
assert(context);
+ assert(ret);
if (!avoid_pidfd) {
pidfd = pidfd_open(getpid_cached(), 0);
r = create_session_message(bus,
handle,
&context,
- false /* avoid_pidfd = */,
+ /* avoid_pidfd = */ false,
&m);
if (r < 0)
return pam_bus_log_create_error(handle, r);
r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+ if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+ sd_bus_error_free(&error);
+ pam_debug_syslog(handle, debug,
+ "CreateSessionWithPIDFD() API is not available, retrying with CreateSession().");
+
+ m = sd_bus_message_unref(m);
+ r = create_session_message(bus,
+ handle,
+ &context,
+ /* avoid_pidfd = */ true,
+ &m);
+ if (r < 0)
+ return pam_bus_log_create_error(handle, r);
+
+ r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+ }
if (r < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
+ /* We are already in a session, don't do anything */
pam_debug_syslog(handle, debug,
"Not creating session: %s", bus_error_message(&error, r));
- /* We are already in a session, don't do anything */
goto success;
- } else if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
- pam_debug_syslog(handle, debug,
- "CreateSessionWithPIDFD() API is not available, retrying with CreateSession().");
-
- m = sd_bus_message_unref(m);
- r = create_session_message(bus,
- handle,
- &context,
- true /* avoid_pidfd = */,
- &m);
- if (r < 0)
- return pam_bus_log_create_error(handle, r);
-
- sd_bus_error_free(&error);
- r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
- }
- if (r < 0) {
- pam_syslog(handle, LOG_ERR,
- "Failed to create session: %s", bus_error_message(&error, r));
- return PAM_SESSION_ERR;
}
+
+ pam_syslog(handle, LOG_ERR,
+ "Failed to create session: %s", bus_error_message(&error, r));
+ return PAM_SESSION_ERR;
}
r = sd_bus_message_read(reply,
if (streq(us, them))
return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name);
- r = namespace_open(m->leader.pid, NULL, NULL, &netns_fd, NULL, NULL);
+ r = namespace_open(m->leader.pid,
+ /* ret_pidns_fd = */ NULL,
+ /* ret_mntns_fd = */ NULL,
+ &netns_fd,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
if (r < 0)
return r;
_cleanup_fclose_ FILE *f = NULL;
pid_t child;
- r = namespace_open(m->leader.pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
+ r = namespace_open(m->leader.pid,
+ &pidns_fd,
+ &mntns_fd,
+ /* ret_netns_fd = */ NULL,
+ /* ret_userns_fd = */ NULL,
+ &root_fd);
if (r < 0)
return r;
_cleanup_close_pair_ int pair[2] = EBADF_PAIR;
pid_t child;
- r = namespace_open(m->leader.pid, NULL, &mntns_fd, NULL, NULL, &root_fd);
+ r = namespace_open(m->leader.pid,
+ /* ret_pidns_fd = */ NULL,
+ &mntns_fd,
+ /* ret_netns_fd = */ NULL,
+ /* ret_userns_fd = */ NULL,
+ &root_fd);
if (r < 0)
return r;
}
}
-static void *close_fd_ptr(void *p) {
- safe_close(PTR_TO_FD(p));
- return NULL;
-}
-
DEFINE_PRIVATE_HASH_OPS_FULL(named_fd_hash_ops, char, string_hash_func, string_compare_func, free, void, close_fd_ptr);
int manager_add_tuntap_fd(Manager *m, int fd, const char *name) {
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);
}
}
if (r < 0)
return log_error_errno(r, "Failed to connect to network service /run/systemd/netif/io.systemd.Network: %m");
- r = varlink_call(vl, "io.systemd.Network.GetNamespaceId", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Network.GetNamespaceId", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue GetNamespaceId() varlink call: %m");
+ return r;
static const JsonDispatch dispatch_table[] = {
{ "NamespaceId", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, 0, JSON_MANDATORY },
dest->nft_set_context.n_sets = 0;
if (src->family == AF_INET) {
- r = free_and_strdup(&dest->label, src->label);
+ r = strdup_or_null(src->label, &dest->label);
if (r < 0)
return r;
}
- r = free_and_strdup(&dest->netlabel, src->netlabel);
+ r = strdup_or_null(src->netlabel, &dest->netlabel);
if (r < 0)
return r;
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);
}
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;
route->priority = link->network->dhcp_pd_route_metric;
route->lifetime_usec = lifetime_usec;
+ r = route_adjust_nexthops(route, link);
+ if (r < 0)
+ return r;
+
if (route_get(NULL, link, route, &existing) < 0)
link->dhcp_pd_configured = false;
else
route_unmark(existing);
- r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp_pd_messages,
- dhcp_pd_route_handler, NULL);
+ r = link_request_route(link, route, &link->dhcp_pd_messages, dhcp_pd_route_handler);
if (r < 0)
return log_link_error_errno(link, r, "Failed to request DHCP-PD prefix route: %m");
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->priority = IP6_RT_PRIO_USER;
route->lifetime_usec = lifetime_usec;
+ r = route_adjust_nexthops(route, link);
+ if (r < 0)
+ return r;
+
if (route_get(link->manager, NULL, route, &existing) < 0)
*configured = false;
else
route_unmark(existing);
- r = link_request_route(link, TAKE_PTR(route), true, counter, callback, NULL);
+ r = link_request_route(link, route, counter, callback);
if (r < 0)
return log_link_error_errno(link, r, "Failed to request unreachable route for DHCP delegated prefix %s: %m",
IN6_ADDR_PREFIX_TO_STRING(addr, prefixlen));
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;
route->lifetime_usec = lifetime_usec;
+ r = route_adjust_nexthops(route, link);
+ if (r < 0)
+ return r;
+
if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */
link->dhcp_pd_configured = false;
else
route_unmark(existing);
- r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp_pd_messages,
- dhcp_pd_route_handler, NULL);
+ r = link_request_route(link, route, &link->dhcp_pd_messages, dhcp_pd_route_handler);
if (r < 0)
return log_link_debug_errno(link, r, "Failed to request default gateway for DHCP delegated prefix: %m");
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)
return 1;
}
-static int dhcp4_request_route(Route *in, Link *link) {
- _cleanup_(route_freep) Route *route = in;
+static int dhcp4_request_route(Route *route, Link *link) {
struct in_addr server;
Route *existing;
int r;
if (r < 0)
return r;
+ r = route_adjust_nexthops(route, link);
+ if (r < 0)
+ return r;
+
if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */
link->dhcp4_configured = false;
else
route_unmark(existing);
- return link_request_route(link, TAKE_PTR(route), true, &link->dhcp4_messages,
- dhcp4_route_handler, NULL);
+ return link_request_route(link, route, &link->dhcp4_messages, dhcp4_route_handler);
}
static bool link_prefixroute(Link *link) {
if (r < 0)
return r;
- return dhcp4_request_route(TAKE_PTR(route), link);
+ return dhcp4_request_route(route, link);
}
static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw) {
route->prefsrc.in = address;
route->scope = RT_SCOPE_LINK;
- return dhcp4_request_route(TAKE_PTR(route), link);
+ return dhcp4_request_route(route, link);
}
static int dhcp4_request_route_auto(
- Route *in,
+ Route *route,
Link *link,
const struct in_addr *gw) {
- _cleanup_(route_freep) Route *route = in;
struct in_addr address;
int r;
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 dhcp4_request_route(TAKE_PTR(route), link);
+ return dhcp4_request_route(route, link);
}
static int dhcp4_request_classless_static_or_static_routes(Link *link) {
if (r < 0)
return r;
- r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw);
+ r = dhcp4_request_route_auto(route, link, &gw);
if (r < 0)
return r;
}
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);
+ return dhcp4_request_route(route, link);
}
static int dhcp4_request_semi_static_routes(Link *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;
- r = route_dup(rt, &route);
+ r = route_dup(rt, NULL, &route);
if (r < 0)
return r;
- route->gw.in = gw;
+ route->nexthop.gw.in = gw;
- r = dhcp4_request_route(TAKE_PTR(route), link);
+ r = dhcp4_request_route(route, link);
if (r < 0)
return r;
}
route->dst.in = *dst;
route->dst_prefixlen = 32;
- r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw);
+ r = dhcp4_request_route_auto(route, link, &gw);
if (r < 0)
return r;
}
#include <linux/nexthop.h>
+#include "dhcp-lease-internal.h"
#include "dhcp-server-lease-internal.h"
#include "dhcp6-internal.h"
#include "dhcp6-lease-internal.h"
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),
return json_variant_set_field_non_null(v, "6rdPrefix", array);
}
+static int dhcp_client_private_options_append_json(Link *link, JsonVariant **v) {
+ _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+ int r;
+
+ assert(link);
+ assert(v);
+
+ if (!link->dhcp_lease)
+ return 0;
+
+ LIST_FOREACH(options, option, link->dhcp_lease->private_options) {
+
+ r = json_variant_append_arrayb(
+ &array,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_UNSIGNED("Option", option->tag),
+ JSON_BUILD_PAIR_HEX("PrivateOptionData", option->data, option->length)));
+ if (r < 0)
+ return 0;
+ }
+ return json_variant_set_field_non_null(v, "PrivateOptions", array);
+}
+
static int dhcp_client_append_json(Link *link, JsonVariant **v) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
int r;
if (r < 0)
return r;
+ r = dhcp_client_private_options_append_json(link, &w);
+ if (r < 0)
+ return r;
+
return json_variant_set_field_non_null(v, "DHCPv4Client", w);
}
#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__);
;
}
- request_detach(link->manager, req);
+ request_detach(req);
}
return ret;
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)
.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);
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;
}
}
-static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) {
- _cleanup_(route_freep) Route *route = in;
+static int ndisc_request_route(Route *route, Link *link, sd_ndisc_router *rt) {
struct in6_addr router;
uint8_t hop_limit = 0;
uint32_t mtu = 0;
if (r < 0)
return r;
+ r = route_adjust_nexthops(route, link);
+ if (r < 0)
+ return r;
+
is_new = route_get(NULL, link, route, NULL) < 0;
- r = link_request_route(link, TAKE_PTR(route), true, &link->ndisc_messages,
- ndisc_route_handler, NULL);
+ r = link_request_route(link, route, &link->ndisc_messages, ndisc_route_handler);
if (r < 0)
return r;
if (r > 0 && is_new)
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);
+ r = ndisc_request_route(route, link, rt);
if (r < 0)
return log_link_warning_errno(link, r, "Could not request default route: %m");
}
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);
+ r = route_dup(route_gw, NULL, &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;
- r = ndisc_request_route(TAKE_PTR(route), link, rt);
+ r = ndisc_request_route(route, link, rt);
if (r < 0)
return log_link_warning_errno(link, r, "Could not request gateway: %m");
}
return 0;
}
+static int ndisc_router_process_retransmission_time(Link *link, sd_ndisc_router *rt) {
+ usec_t retrans_time, msec;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(rt);
+
+ if (!link->network->ipv6_accept_ra_use_retransmission_time)
+ return 0;
+
+ r = sd_ndisc_router_get_retransmission_time(rt, &retrans_time);
+ if (r < 0) {
+ log_link_debug_errno(link, r, "Failed to get retransmission time from RA, ignoring: %m");
+ return 0;
+ }
+
+ /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */
+ if (!timestamp_is_set(retrans_time))
+ return 0;
+
+ msec = DIV_ROUND_UP(retrans_time, USEC_PER_MSEC);
+ if (msec <= 0 || msec > UINT32_MAX) {
+ log_link_debug(link, "Failed to get retransmission time from RA - out of range (%"PRIu64"), ignoring", msec);
+ return 0;
+ }
+
+ /* Set the retransmission time for Neigbor Solicitations. */
+ r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", (uint32_t) msec);
+ if (r < 0)
+ log_link_warning_errno(
+ link, r, "Failed to apply neighbor retransmission time (%"PRIu64"), ignoring: %m", msec);
+
+ return 0;
+}
+
static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
usec_t lifetime_valid_usec, lifetime_preferred_usec;
_cleanup_set_free_ Set *addresses = NULL;
route->pref = preference;
route->lifetime_usec = lifetime_usec;
- r = ndisc_request_route(TAKE_PTR(route), link, rt);
+ r = ndisc_request_route(route, link, rt);
if (r < 0)
return log_link_warning_errno(link, r, "Could not request prefix route: %m");
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;
- r = ndisc_request_route(TAKE_PTR(route), link, rt);
+ r = ndisc_request_route(route, link, rt);
if (r < 0)
return log_link_warning_errno(link, r, "Could not request additional route: %m");
if (r < 0)
return r;
+ r = ndisc_router_process_retransmission_time(link, rt);
+ if (r < 0)
+ return r;
+
r = ndisc_router_process_options(link, rt);
if (r < 0)
return r;
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;
}
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);
return 0;
Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits)
Network.IPv6HopLimit, config_parse_uint8, 0, offsetof(Network, ipv6_hop_limit)
+Network.IPv6RetransmissionTimeSec, config_parse_sec, 0, offsetof(Network, ipv6_retransmission_time)
Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp)
Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu)
Network.IPv4AcceptLocal, config_parse_tristate, 0, offsetof(Network, ipv4_accept_local)
RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_range, 0, 0
RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0
RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0
+RoutingPolicyRule.L3MasterDevice, config_parse_routing_policy_rule_l3mdev, 0, 0
RoutingPolicyRule.Family, config_parse_routing_policy_rule_family, 0, 0
RoutingPolicyRule.User, config_parse_routing_policy_rule_uid_range, 0, 0
RoutingPolicyRule.SuppressInterfaceGroup, config_parse_routing_policy_rule_suppress_ifgroup, 0, 0
IPv6AcceptRA.UseDomains, config_parse_ipv6_accept_ra_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains)
IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_mtu)
IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_hop_limit)
+IPv6AcceptRA.UseRetransmissionTime, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_retransmission_time)
IPv6AcceptRA.UseICMP6RateLimit, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_icmp6_ratelimit)
IPv6AcceptRA.DHCPv6Client, config_parse_ipv6_accept_ra_start_dhcp6_client, 0, offsetof(Network, ipv6_accept_ra_start_dhcp6_client)
IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET6, 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,
.ipv6_accept_ra_use_onlink_prefix = true,
.ipv6_accept_ra_use_mtu = true,
.ipv6_accept_ra_use_hop_limit = true,
+ .ipv6_accept_ra_use_retransmission_time = true,
.ipv6_accept_ra_use_icmp6_ratelimit = true,
.ipv6_accept_ra_route_table = RT_TABLE_MAIN,
.ipv6_accept_ra_route_metric_high = IPV6RA_ROUTE_METRIC_HIGH,
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;
}
int ipv4_route_localnet;
int ipv6_dad_transmits;
uint8_t ipv6_hop_limit;
+ usec_t ipv6_retransmission_time;
int proxy_arp;
int proxy_arp_pvlan;
uint32_t ipv6_mtu;
bool ipv6_accept_ra_use_onlink_prefix;
bool ipv6_accept_ra_use_mtu;
bool ipv6_accept_ra_use_hop_limit;
+ bool ipv6_accept_ra_use_retransmission_time;
bool ipv6_accept_ra_use_icmp6_ratelimit;
bool ipv6_accept_ra_quickack;
bool ipv6_accept_ra_use_captive_portal;
#include "stdio-util.h"
#include "string-util.h"
+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->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;
config_section_free(nexthop->section);
hashmap_free_free(nexthop->group);
+ set_free(nexthop->nexthops);
return mfree(nexthop);
}
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;
}
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);
return 0;
}
}
-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");
+
+ nexthop_detach_from_group_members(nexthop);
- if (size == 0 || size % sizeof(struct nexthop_grp) != 0)
+ 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);
+ (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);
unsigned n_ref;
- uint8_t protocol;
- int ifindex;
- uint32_t id;
- bool blackhole;
+ /* 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_ref(NextHop *nexthop);
#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);
}
siphash24_compress_typesafe(req->type, state);
- if (req->type != REQUEST_TYPE_NEXTHOP) {
+ if (!IN_SET(req->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) {
siphash24_compress_boolean(req->link, state);
if (req->link)
siphash24_compress_typesafe(req->link->ifindex, state);
if (r != 0)
return r;
- if (a->type != REQUEST_TYPE_NEXTHOP) {
+ if (!IN_SET(a->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) {
r = CMP(!!a->link, !!b->link);
if (r != 0)
return r;
Request,
request_hash_func,
request_compare_func,
- request_unref);
+ request_detach);
static int request_new(
Manager *manager,
assert(manager);
+ /* Process only when no remove request is queued. */
+ if (!ordered_set_isempty(manager->remove_request_queue))
+ return 0;
+
manager->request_queued = false;
ORDERED_SET_FOREACH(req, manager->request_queue) {
- _cleanup_(link_unrefp) Link *link = link_ref(req->link);
-
- assert(req->process);
-
if (req->waiting_reply)
- continue; /* Waiting for netlink 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;
-
- r = req->process(req, link, req->userdata);
- if (r == 0) { /* The request is not ready. */
- if (manager->request_queued)
- break; /* a new request is queued during processing the request. */
- continue;
- }
+ break;
- /* 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);
+ /* 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);
- if (r < 0 && link) {
- link_enter_failed(link);
- /* link_enter_failed() may remove multiple requests,
- * hence we need to exit from the loop. */
- break;
+ assert(req->process);
+ r = req->process(req, link, req->userdata);
+ if (r < 0) {
+ request_detach(req);
+
+ if (link) {
+ link_enter_failed(link);
+ /* 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);
if (manager->request_queued)
- break;
+ 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);
} else
dest->metrics_set = NULL;
- return free_and_strdup(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo);
+ return strdup_or_null(src->tcp_congestion_control_algo, &dest->tcp_congestion_control_algo);
}
void route_metric_hash_func(const RouteMetric *metric, struct siphash *state) {
#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 with_weight) {
+ 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 (with_weight)
+ 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 with_weight) {
+ 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 (with_weight) {
+ 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, /* with_weight = */ true);
+}
+
+static int route_nexthop_compare_func(const RouteNextHop *a, const RouteNextHop *b) {
+ return route_nexthop_compare_func_full(a, b, /* with_weight = */ 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) {
+ 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, /* with_weight = */ 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, /* with_weight = */ 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;
+ }}
+}
+
+static int route_nexthop_copy(const RouteNextHop *src, RouteNextHop *dest) {
+ assert(src);
+ assert(dest);
+
+ *dest = *src;
+
+ /* unset pointer copied in the above. */
+ dest->ifname = NULL;
+
+ return strdup_or_null(src->ifindex > 0 ? NULL : src->ifname, &dest->ifname);
+}
+
+static int route_nexthop_dup(const RouteNextHop *src, RouteNextHop **ret) {
+ _cleanup_(route_nexthop_freep) RouteNextHop *dest = NULL;
+ int r;
+
+ assert(src);
+ assert(ret);
+
+ dest = new(RouteNextHop, 1);
+ if (!dest)
+ return -ENOMEM;
+
+ r = route_nexthop_copy(src, dest);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(dest);
+ return 0;
+}
+
+int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest) {
+ int r;
+
+ assert(src);
+ assert(dest);
+
+ if (src->nexthop_id != 0 || route_type_is_reject(src))
+ return 0;
+
+ if (nh)
+ return route_nexthop_copy(nh, &dest->nexthop);
+
+ if (ordered_set_isempty(src->nexthops))
+ return route_nexthop_copy(&src->nexthop, &dest->nexthop);
+
+ ORDERED_SET_FOREACH(nh, src->nexthops) {
+ _cleanup_(route_nexthop_freep) RouteNextHop *nh_dup = NULL;
+
+ r = route_nexthop_dup(nh, &nh_dup);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_ensure_put(&dest->nexthops, &route_nexthop_hash_ops, nh_dup);
+ if (r < 0)
+ return r;
+ assert(r > 0);
+
+ TAKE_PTR(nh_dup);
+ }
+
+ return 0;
+}
+
+static bool multipath_routes_needs_adjust(const Route *route) {
+ assert(route);
+
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops)
+ if (route->nexthop.ifindex == 0)
+ return true;
+
+ return false;
+}
+
+bool route_nexthops_needs_adjust(const Route *route) {
+ assert(route);
+
+ if (route->nexthop_id != 0)
+ /* At this stage, the nexthop may not be configured, or may be under reconfiguring.
+ * Hence, we cannot know if the nexthop is blackhole or not. */
+ return route->type != RTN_BLACKHOLE;
+
+ if (route_type_is_reject(route))
+ return false;
+
+ if (ordered_set_isempty(route->nexthops))
+ return route->nexthop.ifindex == 0;
+
+ return multipath_routes_needs_adjust(route);
+}
+
+static bool route_nexthop_set_ifindex(RouteNextHop *nh, Link *link) {
+ assert(nh);
+ assert(link);
+ assert(link->manager);
+
+ if (nh->ifindex > 0) {
+ nh->ifname = mfree(nh->ifname);
+ return false;
+ }
+
+ /* If an interface name is specified, use it. Otherwise, use the interface that requests this route. */
+ if (nh->ifname && link_get_by_name(link->manager, nh->ifname, &link) < 0)
+ return false;
+
+ nh->ifindex = link->ifindex;
+ nh->ifname = mfree(nh->ifname);
+ return true; /* updated */
+}
+
+int route_adjust_nexthops(Route *route, Link *link) {
+ int r;
+
+ assert(route);
+ assert(link);
+ assert(link->manager);
+
+ /* When an IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel sends
+ * RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type fib_rt_info::type
+ * may not be blackhole. Thus, we cannot know the internal value. Moreover, on route removal, the
+ * matching is done with the hidden value if we set non-zero type in RTM_DELROUTE message. So,
+ * here let's set route type to BLACKHOLE when the nexthop is blackhole. */
+ if (route->nexthop_id != 0) {
+ NextHop *nexthop;
+
+ r = nexthop_is_ready(link->manager, route->nexthop_id, &nexthop);
+ if (r <= 0)
+ return r; /* r == 0 means the nexthop is under (re-)configuring.
+ * We cannot use the currently remembered information. */
+
+ if (!nexthop->blackhole)
+ return false;
+
+ if (route->type == RTN_BLACKHOLE)
+ return false;
+
+ route->type = RTN_BLACKHOLE;
+ return true; /* updated */
+ }
+
+ if (route_type_is_reject(route))
+ return false;
+
+ if (ordered_set_isempty(route->nexthops))
+ return route_nexthop_set_ifindex(&route->nexthop, link);
+
+ if (!multipath_routes_needs_adjust(route))
+ return false;
+
+ _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL;
+ for (;;) {
+ _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
+
+ nh = ordered_set_steal_first(route->nexthops);
+ if (!nh)
+ break;
+
+ (void) route_nexthop_set_ifindex(nh, link);
+
+ r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh);
+ if (r == -EEXIST)
+ continue; /* Duplicated? Let's drop the nexthop. */
+ if (r < 0)
+ return r;
+ assert(r > 0);
+
+ TAKE_PTR(nh);
+ }
+
+ ordered_set_free(route->nexthops);
+ route->nexthops = TAKE_PTR(nexthops);
+ return true; /* updated */
+}
+
+int route_nexthop_get_link(Manager *manager, 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);
+
+ return -ENOENT;
+}
+
+static bool route_nexthop_is_ready_to_configure(const RouteNextHop *nh, Manager *manager, bool onlink) {
+ Link *link;
+
+ assert(nh);
+ assert(manager);
+
+ if (route_nexthop_get_link(manager, nh, &link) < 0)
+ 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, Manager *manager) {
+ int r;
+
+ assert(route);
+ assert(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, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK));
+
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops)
+ if (!route_nexthop_is_ready_to_configure(nh, manager, 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(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);
+
+ 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,
+ .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(const Route *route, sd_netlink_message *message) {
+ _cleanup_free_ struct rtattr *rta = NULL;
+ size_t offset;
+ int r;
+
+ assert(route);
+ assert(message);
+
+ 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;
+
+ if (ordered_set_isempty(route->nexthops)) {
+ r = append_nexthop_one(route, &route->nexthop, &rta, offset);
+ if (r < 0)
+ return r;
+
+ } else {
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops) {
+ struct rtnexthop *rtnh;
+
+ r = append_nexthop_one(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(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;
+
+ /* We request IPv6 multipath routes separately. Even though, if weight is non-zero, we need to use
+ * RTA_MULTIPATH, as we have no way to specify the weight of the nexthop. */
+ if (ordered_set_isempty(route->nexthops) && route->nexthop.weight == 0) {
+
+ 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;
+ }
+
+ assert(route->nexthop.ifindex > 0);
+ return sd_netlink_message_append_u32(message, RTA_OIF, route->nexthop.ifindex);
+ }
+
+ return netlink_message_append_multipath_route(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);
+
+ if (ordered_set_size(route->nexthops) == 1) {
+ _cleanup_(route_nexthop_freep) RouteNextHop *nh = ordered_set_steal_first(route->nexthops);
+
+ route_nexthop_done(&route->nexthop);
+ route->nexthop = TAKE_STRUCT(*nh);
+
+ assert(ordered_set_isempty(route->nexthops));
+ route->nexthops = ordered_set_free(route->nexthops);
+ }
+
+ 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,
void *data,
void *userdata) {
- _cleanup_(multipath_route_freep) MultipathRoute *m = NULL;
+ _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
_cleanup_(route_free_or_set_invalidp) Route *route = NULL;
_cleanup_free_ char *word = NULL;
Network *network = userdata;
- union in_addr_union a;
- int family, r;
const char *p;
char *dev;
+ int r;
assert(filename);
assert(section);
}
if (isempty(rvalue)) {
- route->multipath_routes = ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free);
+ route->nexthops = ordered_set_free(route->nexthops);
TAKE_PTR(route);
return 0;
}
- m = new0(MultipathRoute, 1);
- if (!m)
+ nh = new0(RouteNextHop, 1);
+ if (!nh)
return log_oom();
p = rvalue;
r = parse_ifindex(dev);
if (r > 0)
- m->ifindex = r;
+ nh->ifindex = r;
else {
if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
return 0;
}
- m->ifname = strdup(dev);
- if (!m->ifname)
+ nh->ifname = strdup(dev);
+ if (!nh->ifname)
return log_oom();
}
}
- r = in_addr_from_string_auto(word, &family, &a);
+ 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;
}
- m->gateway.address = a;
- m->gateway.family = family;
if (!isempty(p)) {
- r = safe_atou32(p, &m->weight);
+ 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);
* 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) {
+ 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;
}
- m->weight--;
+ nh->weight--;
}
- r = ordered_set_ensure_put(&route->multipath_routes, NULL, m);
+ r = ordered_set_ensure_put(&route->nexthops, &route_nexthop_hash_ops, nh);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
return 0;
}
- TAKE_PTR(m);
+ TAKE_PTR(nh);
TAKE_PTR(route);
return 0;
}
/* 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;
+
+#define ROUTE_NEXTHOP_NULL ((const 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_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest);
+bool route_nexthops_needs_adjust(const Route *route);
+int route_adjust_nexthops(Route *route, Link *link);
+
+int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret);
+int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager);
+
+int route_nexthops_to_string(const Route *route, char **ret);
+
+int route_nexthops_set_netlink_message(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,
- .gateway_onlink = -1,
- };
-
- *ret = TAKE_PTR(route);
-
- return 0;
-}
-
-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);
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->src_prefixlen, state);
- in_addr_hash_func(&route->src, 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);
- }
-
- in_addr_hash_func(&route->prefsrc, route->family, state);
-
- siphash24_compress_typesafe(route->tos, state);
+ siphash24_compress_typesafe(route->dst_prefixlen, state);
siphash24_compress_typesafe(route->priority, state);
- siphash24_compress_typesafe(route->table, state);
+ siphash24_compress_typesafe(route->tos, 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);
+
+ /* nexthops (id, number of nexthops, nexthop) */
+ route_nexthops_hash_func(route, state);
+
+ /* metrics */
route_metric_hash_func(&route->metric, state);
- siphash24_compress_typesafe(route->nexthop_id, state);
+ break;
+ 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);
+ in_addr_hash_func(&route->dst, route->family, state);
+ siphash24_compress_typesafe(route->dst_prefixlen, 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);
+
+ /* 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->protocol, b->protocol);
+ if (r != 0)
+ return r;
- r = CMP(a->gw_weight, b->gw_weight);
- if (r != 0)
- return r;
- }
+ r = CMP(a->scope, b->scope);
+ if (r != 0)
+ return r;
- r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family));
+ r = CMP(a->type, b->type);
if (r != 0)
return r;
- r = CMP(a->tos, b->tos);
+ r = CMP(a->flags & ~RTNH_COMPARE_MASK, b->flags & ~RTNH_COMPARE_MASK);
if (r != 0)
return r;
- r = CMP(a->priority, b->priority);
+ r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family));
if (r != 0)
return r;
+ r = route_nexthops_compare_func(a, b);
+ if (r != 0)
+ return r;
+
+ 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->protocol, b->protocol);
+ r = memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family));
if (r != 0)
return r;
- r = CMP(a->scope, b->scope);
+ r = CMP(a->dst_prefixlen, b->dst_prefixlen);
if (r != 0)
return r;
- r = CMP(a->type, b->type);
+ r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family));
if (r != 0)
return r;
- r = route_metric_compare_func(&a->metric, &b->metric);
+ 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);
-
- return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW);
-}
-
-static bool route_needs_convert(const Route *route) {
- assert(route);
-
- return route->nexthop_id > 0 || !ordered_set_isempty(route->multipath_routes);
-}
-
-static int route_add(Manager *manager, Link *link, Route *route) {
- int r;
-
- assert(route);
-
- if (route_type_is_reject(route)) {
- assert(manager);
-
- r = set_ensure_put(&manager->routes, &route_hash_ops, route);
- if (r < 0)
- return r;
- if (r == 0)
- return -EEXIST;
-
- route->manager = manager;
- } else {
- assert(link);
-
- r = set_ensure_put(&link->routes, &route_hash_ops, route);
- if (r < 0)
- return r;
- if (r == 0)
- return -EEXIST;
-
- route->link = link;
- }
-
- return 0;
-}
-
-int route_get(Manager *manager, Link *link, const Route *in, Route **ret) {
- Route *route;
-
- assert(in);
-
- if (route_type_is_reject(in)) {
- if (!manager)
- return -ENOENT;
-
- route = set_get(manager->routes, in);
- } else {
- if (!link)
- return -ENOENT;
+int route_new(Route **ret) {
+ _cleanup_(route_freep) Route *route = NULL;
- route = set_get(link->routes, in);
- }
+ route = new(Route, 1);
if (!route)
- return -ENOENT;
+ return -ENOMEM;
- if (ret)
- *ret = route;
+ *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 0;
}
-int route_dup(const Route *src, Route **ret) {
- _cleanup_(route_freep) Route *dest = NULL;
+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;
- /* This does not copy mulipath routes. */
-
- assert(src);
+ assert(network);
assert(ret);
+ assert(filename);
+ assert(section_line > 0);
- dest = newdup(Route, src, 1);
- if (!dest)
- return -ENOMEM;
-
- /* Unset all pointers */
- dest->network = NULL;
- dest->section = NULL;
- dest->link = NULL;
- dest->manager = NULL;
- dest->multipath_routes = NULL;
- dest->metric = ROUTE_METRIC_NULL;
- dest->expire = NULL;
-
- r = route_metric_copy(&src->metric, &dest->metric);
+ r = config_section_new(filename, section_line, &n);
if (r < 0)
return r;
- *ret = TAKE_PTR(dest);
- return 0;
-}
-
-static void route_apply_nexthop(Route *route, const NextHop *nh, uint8_t nh_weight) {
- assert(route);
- assert(nh);
- assert(hashmap_isempty(nh->group));
+ route = hashmap_get(network->routes_by_section, n);
+ if (route) {
+ *ret = TAKE_PTR(route);
+ return 0;
+ }
- route->gw_family = nh->family;
- route->gw = nh->gw;
+ if (hashmap_size(network->routes_by_section) >= routes_max())
+ return -E2BIG;
- if (nh_weight != UINT8_MAX)
- route->gw_weight = nh_weight;
+ r = route_new(&route);
+ if (r < 0)
+ return r;
- if (nh->blackhole)
- route->type = RTN_BLACKHOLE;
-}
+ route->protocol = RTPROT_STATIC;
+ route->network = network;
+ route->section = TAKE_PTR(n);
+ route->source = NETWORK_CONFIG_SOURCE_STATIC;
-static void route_apply_multipath_route(Route *route, const MultipathRoute *m) {
- assert(route);
- assert(m);
+ r = hashmap_ensure_put(&network->routes_by_section, &config_section_hash_ops, route->section, route);
+ if (r < 0)
+ return r;
- route->gw_family = m->gateway.family;
- route->gw = m->gateway.address;
- route->gw_weight = m->weight;
+ *ret = TAKE_PTR(route);
+ return 0;
}
-static int multipath_route_get_link(Manager *manager, const MultipathRoute *m, Link **ret) {
+static int route_add(Manager *manager, Route *route) {
int r;
assert(manager);
- assert(m);
+ assert(route);
+ assert(!route->network);
+ assert(!route->wireguard);
- if (m->ifname) {
- r = link_get_by_name(manager, m->ifname, ret);
- return r < 0 ? r : 1;
+ Link *link;
+ if (route_nexthop_get_link(manager, &route->nexthop, &link) >= 0) {
+ r = set_ensure_put(&link->routes, &route_hash_ops, route);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EEXIST;
- } 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;
+ route->link = link;
+ return 0;
}
- if (ret)
- *ret = NULL;
- return 0;
-}
-
-typedef struct ConvertedRoutes {
- size_t n;
- Route **routes;
- Link **links;
-} ConvertedRoutes;
-
-static ConvertedRoutes *converted_routes_free(ConvertedRoutes *c) {
- if (!c)
- return NULL;
-
- for (size_t i = 0; i < c->n; i++)
- route_free(c->routes[i]);
-
- free(c->routes);
- free(c->links);
+ r = set_ensure_put(&manager->routes, &route_hash_ops, route);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EEXIST;
- return mfree(c);
+ route->manager = manager;
+ return 0;
}
-DEFINE_TRIVIAL_CLEANUP_FUNC(ConvertedRoutes*, converted_routes_free);
+int route_get(Manager *manager, Link *link, const Route *route, Route **ret) {
+ Route *existing;
-static int converted_routes_new(size_t n, ConvertedRoutes **ret) {
- _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL;
- _cleanup_free_ Route **routes = NULL;
- _cleanup_free_ Link **links = NULL;
-
- assert(n > 0);
- assert(ret);
-
- routes = new0(Route*, n);
- if (!routes)
- return -ENOMEM;
-
- links = new0(Link*, n);
- if (!links)
- return -ENOMEM;
+ if (!manager)
+ manager = ASSERT_PTR(ASSERT_PTR(link)->manager);
+ assert(route);
- c = new(ConvertedRoutes, 1);
- if (!c)
- return -ENOMEM;
+ if (route_nexthop_get_link(manager, &route->nexthop, &link) >= 0)
+ existing = set_get(link->routes, route);
+ else
+ existing = set_get(manager->routes, route);
+ if (!existing)
+ return -ENOENT;
- *c = (ConvertedRoutes) {
- .n = n,
- .routes = TAKE_PTR(routes),
- .links = TAKE_PTR(links),
- };
+ if (ret)
+ *ret = existing;
- *ret = TAKE_PTR(c);
return 0;
}
-static int route_convert(Manager *manager, const Route *route, ConvertedRoutes **ret) {
- _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL;
+static int route_get_link(Manager *manager, const Route *route, Link **ret) {
int r;
assert(manager);
- assert(route);
- assert(ret);
-
- if (!route_needs_convert(route)) {
- *ret = NULL;
- return 0;
- }
+ assert(route);
- if (route->nexthop_id > 0) {
- struct nexthop_grp *nhg;
+ if (route->nexthop_id != 0) {
NextHop *nh;
r = nexthop_get_by_id(manager, route->nexthop_id, &nh);
if (r < 0)
return r;
- if (hashmap_isempty(nh->group)) {
- r = converted_routes_new(1, &c);
- if (r < 0)
- return r;
-
- r = route_dup(route, &c->routes[0]);
- if (r < 0)
- return r;
-
- route_apply_nexthop(c->routes[0], nh, UINT8_MAX);
- (void) link_get_by_index(manager, nh->ifindex, c->links);
-
- *ret = TAKE_PTR(c);
- return 1;
- }
+ return link_get_by_index(manager, nh->ifindex, ret);
+ }
- r = converted_routes_new(hashmap_size(nh->group), &c);
- if (r < 0)
- return r;
+ return route_nexthop_get_link(manager, &route->nexthop, ret);
+}
- size_t i = 0;
- HASHMAP_FOREACH(nhg, nh->group) {
- NextHop *h;
+static int route_get_request(Manager *manager, const Route *route, Request **ret) {
+ Request *req;
- r = nexthop_get_by_id(manager, nhg->id, &h);
- if (r < 0)
- return r;
+ assert(manager);
+ assert(route);
- r = route_dup(route, &c->routes[i]);
- if (r < 0)
- return r;
+ req = ordered_set_get(manager->request_queue,
+ &(const Request) {
+ .type = REQUEST_TYPE_ROUTE,
+ .userdata = (void*) route,
+ .hash_func = (hash_func_t) route_hash_func,
+ .compare_func = (compare_func_t) route_compare_func,
+ });
+ if (!req)
+ return -ENOENT;
- route_apply_nexthop(c->routes[i], h, nhg->weight);
- (void) link_get_by_index(manager, h->ifindex, c->links + i);
+ if (ret)
+ *ret = req;
+ return 0;
+}
- i++;
- }
+int route_dup(const Route *src, const RouteNextHop *nh, Route **ret) {
+ _cleanup_(route_freep) Route *dest = NULL;
+ int r;
- *ret = TAKE_PTR(c);
- return 1;
+ assert(src);
+ assert(ret);
- }
+ dest = newdup(Route, src, 1);
+ if (!dest)
+ return -ENOMEM;
- assert(!ordered_set_isempty(route->multipath_routes));
+ /* Unset all pointers */
+ dest->manager = NULL;
+ dest->network = NULL;
+ dest->wireguard = NULL;
+ dest->section = NULL;
+ dest->link = NULL;
+ dest->nexthop = ROUTE_NEXTHOP_NULL;
+ dest->nexthops = NULL;
+ dest->metric = ROUTE_METRIC_NULL;
+ dest->expire = NULL;
- r = converted_routes_new(ordered_set_size(route->multipath_routes), &c);
+ r = route_nexthops_copy(src, nh, dest);
if (r < 0)
return r;
- size_t i = 0;
- MultipathRoute *m;
- ORDERED_SET_FOREACH(m, route->multipath_routes) {
- r = route_dup(route, &c->routes[i]);
- if (r < 0)
- return r;
-
- route_apply_multipath_route(c->routes[i], m);
-
- r = multipath_route_get_link(manager, m, &c->links[i]);
- if (r < 0)
- return r;
-
- i++;
- }
+ r = route_metric_copy(&src->metric, &dest->metric);
+ if (r < 0)
+ return r;
- *ret = TAKE_PTR(c);
- return 1;
+ *ret = TAKE_PTR(dest);
+ return 0;
}
void link_mark_routes(Link *link, NetworkConfigSource source) {
}
}
-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) {
int r;
assert(route);
- assert(req);
-
- /* 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;
- }
- }
+ assert(m);
+ /* 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(m, route->scope);
+ if (r < 0)
+ return r;
+
+ r = sd_rtnl_message_route_set_type(m, route->type);
+ if (r < 0)
+ return r;
- r = sd_rtnl_message_route_set_scope(req, route->scope);
+ r = sd_rtnl_message_route_set_flags(m, route->flags & ~RTNH_COMPARE_MASK);
if (r < 0)
return r;
- r = sd_rtnl_message_route_set_flags(req, route->flags & RTNH_F_ONLINK);
+ /* 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->table < 256) {
- r = sd_rtnl_message_route_set_table(req, route->table);
+ r = sd_rtnl_message_route_set_table(m, route->table);
if (r < 0)
return r;
} else {
- r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC);
+ r = sd_rtnl_message_route_set_table(m, RT_TABLE_UNSPEC);
if (r < 0)
return r;
/* Table attribute to allow more than 256. */
- r = sd_netlink_message_append_u32(req, RTA_TABLE, route->table);
- 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);
+ r = sd_netlink_message_append_u32(m, RTA_TABLE, route->table);
if (r < 0)
return r;
}
- if (route->nexthop_id > 0) {
- r = sd_netlink_message_append_u32(req, RTA_NH_ID, route->nexthop_id);
- if (r < 0)
- return r;
- }
+ r = sd_netlink_message_append_u8(m, RTA_PREF, route->pref);
+ if (r < 0)
+ return r;
- r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref);
+ /* nexthops */
+ r = route_nexthops_set_netlink_message(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;
- unsigned char type;
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
Manager *manager;
Link *link;
int r;
assert(route);
assert(route->manager || (route->link && route->link->manager));
- assert(IN_SET(route->family, AF_INET, AF_INET6));
link = route->link;
manager = route->manager ?: link->manager;
- log_route_debug(route, "Removing", link, manager);
-
- r = sd_rtnl_message_new_route(manager->rtnl, &req,
- RTM_DELROUTE, route->family,
- route->protocol);
- if (r < 0)
- return log_link_error_errno(link, r, "Could not create 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
- * sends RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type
- * fib_rt_info::type may not be blackhole. Thus, we cannot know the internal value.
- * Moreover, on route removal, the matching is done with the hidden value if we set
- * non-zero type in RTM_DELROUTE message. Note, sd_rtnl_message_new_route() sets
- * RTN_UNICAST by default. So, we need to clear the type here. */
- type = RTN_UNSPEC;
- else
- type = route->type;
+ log_route_debug(route, "Removing", manager);
- r = sd_rtnl_message_route_set_type(req, type);
+ r = sd_rtnl_message_new_route(manager->rtnl, &m, RTM_DELROUTE, route->family, route->protocol);
if (r < 0)
- return log_link_error_errno(link, r, "Could not set route type: %m");
+ return log_link_warning_errno(link, r, "Could not create netlink message: %m");
- r = route_set_netlink_message(route, req, link);
+ r = route_set_netlink_message(route, m);
if (r < 0)
- return log_error_errno(r, "Could not fill netlink message: %m");
+ return log_link_warning_errno(link, 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);
return 0;
}
+static int link_unmark_route(Link *link, const Route *route, const RouteNextHop *nh) {
+ _cleanup_(route_freep) Route *tmp = NULL;
+ Route *existing;
+ int r;
+
+ assert(link);
+ assert(route);
+
+ r = route_dup(route, nh, &tmp);
+ if (r < 0)
+ return r;
+
+ r = route_adjust_nexthops(tmp, link);
+ if (r < 0)
+ return r;
+
+ if (route_get(link->manager, link, tmp, &existing) < 0)
+ return 0;
+
+ route_unmark(existing);
+ return 1;
+}
+
static void manager_mark_routes(Manager *manager, bool foreign, const Link *except) {
Route *route;
Link *link;
- int r;
assert(manager);
continue;
HASHMAP_FOREACH(route, link->network->routes_by_section) {
- _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
- Route *existing;
-
- r = route_convert(manager, route, &converted);
- if (r < 0)
- continue;
- if (r == 0) {
- if (route_get(manager, NULL, route, &existing) >= 0)
- route_unmark(existing);
- continue;
- }
+ if (route->family == AF_INET || ordered_set_isempty(route->nexthops))
+ (void) link_unmark_route(link, route, NULL);
- for (size_t i = 0; i < converted->n; i++)
- if (route_get(manager, NULL, converted->routes[i], &existing) >= 0)
- route_unmark(existing);
+ else {
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops)
+ (void) link_unmark_route(link, route, nh);
+ }
}
}
}
if (!link->netdev || link->netdev->kind != NETDEV_KIND_WIREGUARD)
return;
- Route *route, *existing;
+ Route *route;
Wireguard *w = WIREGUARD(link->netdev);
SET_FOREACH(route, w->routes)
- if (route_get(NULL, link, route, &existing) >= 0)
- route_unmark(existing);
+ (void) link_unmark_route(link, route, NULL);
}
int link_drop_foreign_routes(Link *link) {
}
HASHMAP_FOREACH(route, link->network->routes_by_section) {
- _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
- Route *existing;
+ if (route->family == AF_INET || ordered_set_isempty(route->nexthops))
+ (void) link_unmark_route(link, route, NULL);
- r = route_convert(link->manager, route, &converted);
- if (r < 0)
- continue;
- if (r == 0) {
- if (route_get(NULL, link, route, &existing) >= 0)
- route_unmark(existing);
- continue;
+ else {
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops)
+ (void) link_unmark_route(link, route, nh);
}
-
- for (size_t i = 0; i < converted->n; i++)
- if (route_get(NULL, link, converted->routes[i], &existing) >= 0)
- route_unmark(existing);
}
link_unmark_wireguard_routes(link);
}
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);
+
+ /* This may be a bug in the kernel, but the MTU of an IPv6 route can be updated only
+ * when the route has an expiration timer managed by the kernel (not by us).
+ * See fib6_add_rt2node() in net/ipv6/ip6_fib.c of the kernel. */
+ if (existing->family == AF_INET6 &&
+ existing->expiration_managed_by_kernel) {
+ r = route_metric_set(&existing->metric, RTAX_MTU, route_metric_get(&route->metric, RTAX_MTU));
+ if (r < 0) {
+ log_oom();
+ link_enter_failed(link);
+ return 0;
+ }
+ }
+ }
+
+ } 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);
+ r = route_set_netlink_message(route, m);
if (r < 0)
return r;
return r;
}
- /* metrics */
- r = route_metric_set_netlink_message(&route->metric, m);
+ return request_call_netlink_async(link->manager->rtnl, m, req);
+}
+
+static int route_requeue_request(Request *req, Link *link, const Route *route) {
+ _unused_ _cleanup_(request_unrefp) Request *req_unref = NULL;
+ _cleanup_(route_freep) Route *tmp = NULL;
+ int r;
+
+ assert(req);
+ assert(link);
+ assert(link->manager);
+ assert(route);
+
+ /* It is not possible to adjust the Route object owned by Request, as it is used as a key to manage
+ * Request objects in the queue. Hence, we need to re-request with the updated Route object. */
+
+ if (!route_nexthops_needs_adjust(route))
+ return 0; /* The Route object does not need the adjustment. Continue with it. */
+
+ r = route_dup(route, NULL, &tmp);
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 = route_adjust_nexthops(tmp, link);
+ if (r <= 0)
+ return r;
- r = append_nexthops(link, route, m);
- if (r < 0)
- return r;
- }
+ if (route_compare_func(route, tmp) == 0 && route->type == tmp->type)
+ return 0; /* No effective change?? That's OK. */
+
+ /* Avoid the request to be freed by request_detach(). */
+ req_unref = request_ref(req);
+
+ /* Detach the request from the queue, to make not the new request is deduped.
+ * Why this is necessary? IPv6 routes with different type may be handled as the same,
+ * As commented in route_adjust_nexthops(), we need to configure the adjusted type,
+ * otherwise we cannot remove the route on reconfigure or so. If we request the new Route object
+ * without detaching the current request, the new request is deduped, and the route is configured
+ * with unmodified type. */
+ request_detach(req);
+
+ /* Request the route with the adjusted Route object combined with the same other parameters. */
+ r = link_queue_request_full(link,
+ req->type,
+ tmp,
+ req->free_func,
+ req->hash_func,
+ req->compare_func,
+ req->process,
+ req->counter,
+ req->netlink_handler,
+ NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Already queued?? That's OK. Maybe, [Route] section is effectively duplicated. */
- return request_call_netlink_async(link->manager->rtnl, m, req);
+ TAKE_PTR(tmp);
+ return 1; /* New request is queued. Finish to process this request. */
}
static int route_is_ready_to_configure(const Route *route, Link *link) {
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;
-
- r = nexthop_is_ready(link->manager, route->nexthop_id, &nh);
- if (r <= 0)
- return r;
-
- HASHMAP_FOREACH(nhg, nh->group) {
- r = nexthop_is_ready(link->manager, nhg->id, NULL);
- if (r <= 0)
- return r;
- }
- }
-
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->manager);
}
static int route_process_request(Request *req, Link *link, Route *route) {
- _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL;
+ Route *existing;
int r;
assert(req);
if (r == 0)
return 0;
- if (route_needs_convert(route)) {
- r = route_convert(link->manager, route, &converted);
- if (r < 0)
- return log_link_warning_errno(link, r, "Failed to convert route: %m");
-
- assert(r > 0);
- assert(converted);
-
- for (size_t i = 0; i < converted->n; i++) {
- Route *existing;
-
- if (route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) < 0) {
- _cleanup_(route_freep) Route *tmp = NULL;
-
- r = route_dup(converted->routes[i], &tmp);
- if (r < 0)
- return log_oom();
-
- r = route_add(link->manager, converted->links[i] ?: link, tmp);
- if (r < 0)
- return log_link_warning_errno(link, r, "Failed to add route: %m");
-
- TAKE_PTR(tmp);
- } else {
- existing->source = converted->routes[i]->source;
- existing->provider = converted->routes[i]->provider;
- }
- }
- }
-
usec_t now_usec;
assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0);
uint32_t sec = usec_to_sec(route->lifetime_usec, now_usec);
log_link_debug(link, "Refuse to configure %s route with zero lifetime.",
network_config_source_to_string(route->source));
- if (converted)
- for (size_t i = 0; i < converted->n; i++) {
- Route *existing;
+ route_cancel_requesting(route);
+ if (route_get(link->manager, link, route, &existing) >= 0)
+ route_cancel_requesting(existing);
+ return 1;
+ }
- assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0);
- route_cancel_requesting(existing);
- }
- else
- route_cancel_requesting(route);
+ r = route_requeue_request(req, link, route);
+ if (r != 0)
+ return r;
+
+ r = route_configure(route, sec, link, req);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to configure route: %m");
+
+ route_enter_configuring(route);
+ if (route_get(link->manager, link, route, &existing) >= 0)
+ route_enter_configuring(existing);
+ return 1;
+}
+
+static int link_request_route_one(
+ Link *link,
+ const Route *route,
+ const RouteNextHop *nh,
+ unsigned *message_counter,
+ route_netlink_handler_t netlink_handler) {
+
+ _cleanup_(route_freep) Route *tmp = NULL;
+ Route *existing = NULL;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(route);
- return 1;
- }
+ r = route_dup(route, nh, &tmp);
+ if (r < 0)
+ return r;
- r = route_configure(route, sec, link, req);
+ r = route_adjust_nexthops(tmp, link);
if (r < 0)
- return log_link_warning_errno(link, r, "Failed to configure route: %m");
+ return r;
- if (converted)
- for (size_t i = 0; i < converted->n; i++) {
- Route *existing;
+ if (route_get(link->manager, link, tmp, &existing) >= 0)
+ /* Copy state for logging below. */
+ tmp->state = existing->state;
- assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0);
- route_enter_configuring(existing);
- }
- else
- route_enter_configuring(route);
+ log_route_debug(tmp, "Requesting", link->manager);
+ r = link_queue_request_safe(link, REQUEST_TYPE_ROUTE,
+ tmp,
+ route_free,
+ route_hash_func,
+ route_compare_func,
+ route_process_request,
+ message_counter,
+ netlink_handler,
+ NULL);
+ if (r <= 0)
+ return r;
+
+ route_enter_requesting(tmp);
+ if (existing)
+ route_enter_requesting(existing);
+ TAKE_PTR(tmp);
return 1;
}
int link_request_route(
Link *link,
- Route *route,
- bool consume_object,
+ const Route *route,
unsigned *message_counter,
- route_netlink_handler_t netlink_handler,
- Request **ret) {
+ route_netlink_handler_t netlink_handler) {
- Route *existing = NULL;
int r;
assert(link);
assert(link->manager);
assert(route);
assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN);
- assert(!route_needs_convert(route));
-
- (void) route_get(link->manager, link, route, &existing);
-
- if (route->lifetime_usec == 0) {
- if (consume_object)
- route_free(route);
- /* The requested route is outdated. Let's remove it. */
- return route_remove_and_drop(existing);
- }
-
- if (!existing) {
- _cleanup_(route_freep) Route *tmp = NULL;
-
- if (consume_object)
- tmp = route;
- else {
- r = route_dup(route, &tmp);
- if (r < 0)
- return r;
- }
+ if (route->family == AF_INET || route_type_is_reject(route) || ordered_set_isempty(route->nexthops))
+ return link_request_route_one(link, route, NULL, message_counter, netlink_handler);
- r = route_add(link->manager, link, tmp);
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, route->nexthops) {
+ r = link_request_route_one(link, route, nh, message_counter, netlink_handler);
if (r < 0)
return r;
-
- existing = TAKE_PTR(tmp);
- } else {
- existing->source = route->source;
- existing->provider = route->provider;
- 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);
- r = link_queue_request_safe(link, REQUEST_TYPE_ROUTE,
- existing, NULL,
- route_hash_func,
- route_compare_func,
- route_process_request,
- message_counter, netlink_handler, ret);
- if (r <= 0)
- return r;
-
- route_enter_requesting(existing);
- return 1;
+ return 0;
}
static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) {
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 1;
}
-static int link_request_static_route(Link *link, Route *route) {
- assert(link);
- assert(link->manager);
- assert(route);
-
- if (!route_needs_convert(route))
- return link_request_route(link, route, false, &link->static_route_messages,
- static_route_handler, NULL);
-
- log_route_debug(route, "Requesting", link, link->manager);
- return link_queue_request_safe(link, REQUEST_TYPE_ROUTE,
- route, NULL, route_hash_func, route_compare_func,
- route_process_request,
- &link->static_route_messages, static_route_handler, NULL);
-}
-
static int link_request_wireguard_routes(Link *link, bool only_ipv4) {
NetDev *netdev;
Route *route;
if (only_ipv4 && route->family != AF_INET)
continue;
- r = link_request_static_route(link, route);
+ r = link_request_route(link, route, &link->static_route_messages, static_route_handler);
if (r < 0)
return r;
}
if (only_ipv4 && route->family != AF_INET)
continue;
- r = link_request_static_route(link, route);
+ r = link_request_route(link, route, &link->static_route_messages, static_route_handler);
if (r < 0)
return r;
}
}
void route_cancel_request(Route *route, Link *link) {
- Request req;
-
assert(route);
-
- link = route->link ?: link;
-
- assert(link);
+ Manager *manager = ASSERT_PTR(route->manager ?:
+ route->link ? route->link->manager :
+ ASSERT_PTR(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 *req;
+ if (route_get_request(manager, route, &req) >= 0)
+ request_detach(req);
- request_detach(link->manager, &req);
route_cancel_requesting(route);
}
static int process_route_one(
Manager *manager,
- Link *link,
uint16_t type,
Route *in,
const struct rta_cacheinfo *cacheinfo) {
_cleanup_(route_freep) Route *tmp = in;
+ Request *req = NULL;
Route *route = NULL;
- bool update_dhcp4;
+ Link *link = NULL;
+ bool is_new = false, update_dhcp4;
int r;
assert(manager);
assert(tmp);
assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE));
- /* link may be NULL. This consumes 'in'. */
+ (void) route_get(manager, NULL, tmp, &route);
+ (void) route_get_request(manager, tmp, &req);
+ (void) route_get_link(manager, tmp, &link);
update_dhcp4 = link && tmp->family == AF_INET6 && tmp->dst_prefixlen == 0;
- (void) route_get(manager, link, tmp, &route);
-
switch (type) {
case RTM_NEWROUTE:
- if (route) {
- route->flags = tmp->flags;
- route_enter_configured(route);
- log_route_debug(route, "Received remembered", link, manager);
-
- 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);
- r = route_add(manager, link, tmp);
+ if (!route) {
+ if (!manager->manage_foreign_routes && !(req && req->waiting_reply)) {
+ route_enter_configured(tmp);
+ log_route_debug(tmp, "Ignoring received", manager);
+ return 0;
+ }
+
+ /* If we do not know the route, then save it. */
+ r = route_add(manager, 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->nexthop.weight = tmp->nexthop.weight;
+
+ /* Also update information that cannot be obtained through netlink notification. */
+ if (req && req->waiting_reply) {
+ Route *rt = ASSERT_PTR(req->userdata);
+
+ route->source = rt->source;
+ route->provider = rt->provider;
+ route->lifetime_usec = rt->lifetime_usec;
}
+ route_enter_configured(route);
+ log_route_debug(route, is_new ? "Received new" : "Received remembered", manager);
+
+ (void) route_setup_timer(route, cacheinfo);
+
break;
case RTM_DELROUTE:
if (route) {
route_enter_removed(route);
- if (route->state == 0) {
- log_route_debug(route, "Forgetting", link, manager);
- route_free(route);
- } else
- log_route_debug(route, "Removed", link, manager);
+ log_route_debug(route, "Forgetting removed", manager);
+ route_free(route);
} else
log_route_debug(tmp,
manager->manage_foreign_routes ? "Kernel removed unknown" : "Ignoring received",
- link, manager);
+ manager);
+
+ if (req)
+ route_enter_removed(req->userdata);
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_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;
- }
- }
-
+ 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_warning_errno(link, r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m");
+ log_warning_errno(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 (tmp->family == AF_INET || ordered_set_isempty(tmp->nexthops))
+ return process_route_one(m, type, TAKE_PTR(tmp), has_cacheinfo ? &cacheinfo : 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);
- if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: failed to convert received route, ignoring: %m");
- return 0;
- }
+ RouteNextHop *nh;
+ ORDERED_SET_FOREACH(nh, tmp->nexthops) {
+ _cleanup_(route_freep) Route *dup = NULL;
- assert(r > 0);
- assert(converted);
+ r = route_dup(tmp, nh, &dup);
+ if (r < 0)
+ return log_oom();
- for (size_t i = 0; i < converted->n; i++)
- (void) process_route_one(m,
- converted->links[i] ?: link,
- type,
- TAKE_PTR(converted->routes[i]),
- has_cacheinfo ? &cacheinfo : NULL);
+ r = process_route_one(m, type, TAKE_PTR(dup), has_cacheinfo ? &cacheinfo : NULL);
+ if (r < 0)
+ return r;
+ }
return 1;
}
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->gw_family = AF_UNSPEC;
- route->gw = IN_ADDR_NULL;
- TAKE_PTR(route);
- return 0;
- }
-
- if (streq(rvalue, "_dhcp")) {
- route->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(route);
- return 0;
- }
-
- if (streq(rvalue, "_dhcp4")) {
- route->gw_family = AF_INET;
- route->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(route);
- return 0;
- }
-
- if (streq(rvalue, "_ipv6ra")) {
- route->gw_family = AF_INET6;
- route->gateway_from_dhcp_or_ra = true;
- TAKE_PTR(route);
- return 0;
- }
- }
-
- r = in_addr_from_string_auto(rvalue, &route->gw_family, &route->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_preferred_src(
const char *unit,
const char *filename,
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 = 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;
- }
-
- route->gateway_onlink = r;
-
- TAKE_PTR(route);
- return 0;
-}
-
int config_parse_ipv6_route_preference(
const char *unit,
const char *filename,
return 0;
}
-static int route_section_verify(Route *route, Network *network) {
+int route_section_verify(Route *route) {
+ int r;
+
+ assert(route);
+ assert(route->section);
+
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);
}
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;
-
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;
- unsigned char pref;
- unsigned flags;
- int gateway_onlink; /* Only used in conf parser and route_section_verify(). */
- uint32_t nexthop_id;
+ 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;
+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);
-Route *route_free(Route *route);
-DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_free);
-int route_dup(const Route *src, Route **ret);
+int route_dup(const Route *src, const RouteNextHop *nh, 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);
void route_cancel_request(Route *route, Link *link);
int link_request_route(
Link *link,
- Route *route,
- bool consume_object,
+ const Route *route,
unsigned *message_counter,
- route_netlink_handler_t netlink_handler,
- Request **ret);
+ route_netlink_handler_t netlink_handler);
int link_request_static_routes(Link *link, bool only_ipv4);
int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m);
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_gateway_onlink);
CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_route_preference);
CONFIG_PARSER_PROTOTYPE(config_parse_route_protocol);
CONFIG_PARSER_PROTOTYPE(config_parse_route_type);
in_addr_hash_func(&rule->from, rule->family, state);
siphash24_compress_typesafe(rule->from_prefixlen, state);
+ siphash24_compress_boolean(rule->l3mdev, state);
+
in_addr_hash_func(&rule->to, rule->family, state);
siphash24_compress_typesafe(rule->to_prefixlen, state);
if (r != 0)
return r;
+ r = CMP(a->l3mdev, b->l3mdev);
+ if (r != 0)
+ return r;
+
r = CMP(a->to_prefixlen, b->to_prefixlen);
if (r != 0)
return r;
return r;
}
- if (rule->table < 256) {
+ if (rule->l3mdev)
+ r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC);
+ else if (rule->table < 256)
r = sd_rtnl_message_routing_policy_rule_set_table(m, rule->table);
- if (r < 0)
- return r;
- } else {
+ else {
r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC);
if (r < 0)
return r;
r = sd_netlink_message_append_u32(m, FRA_TABLE, rule->table);
- if (r < 0)
- return r;
}
+ if (r < 0)
+ return r;
if (rule->fwmark > 0) {
r = sd_netlink_message_append_u32(m, FRA_FWMARK, rule->fwmark);
return r;
}
+ if (rule->l3mdev) {
+ r = sd_netlink_message_append_u8(m, FRA_L3MDEV, 1);
+ if (r < 0)
+ return r;
+ }
+
if (rule->suppress_prefixlen >= 0) {
r = sd_netlink_message_append_u32(m, FRA_SUPPRESS_PREFIXLEN, (uint32_t) rule->suppress_prefixlen);
if (r < 0)
static const RoutingPolicyRule kernel_rules[] = {
{ .family = AF_INET, .priority_set = true, .priority = 0, .table = RT_TABLE_LOCAL, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
+ { .family = AF_INET, .priority_set = true, .priority = 1000, .table = RT_TABLE_UNSPEC, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true },
{ .family = AF_INET, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
{ .family = AF_INET, .priority_set = true, .priority = 32767, .table = RT_TABLE_DEFAULT, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
{ .family = AF_INET6, .priority_set = true, .priority = 0, .table = RT_TABLE_LOCAL, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
+ { .family = AF_INET6, .priority_set = true, .priority = 1000, .table = RT_TABLE_UNSPEC, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true },
{ .family = AF_INET6, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, },
};
static bool routing_policy_rule_is_created_by_kernel(const RoutingPolicyRule *rule) {
assert(rule);
- if (rule->l3mdev > 0)
- /* Currently, [RoutingPolicyRule] does not explicitly set FRA_L3MDEV. So, if the flag
- * is set, it is safe to treat the rule as created by kernel. */
- return true;
-
for (size_t i = 0; i < ELEMENTSOF(kernel_rules); i++)
if (routing_policy_rule_equal(rule, &kernel_rules[i]))
return true;
return 0;
}
- r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &tmp->l3mdev);
+ uint8_t l3mdev = 0;
+ r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &l3mdev);
if (r < 0 && r != -ENODATA) {
log_warning_errno(r, "rtnl: could not get FRA_L3MDEV attribute, ignoring: %m");
return 0;
}
+ tmp->l3mdev = l3mdev != 0;
r = sd_netlink_message_read(message, FRA_SPORT_RANGE, sizeof(tmp->sport), &tmp->sport);
if (r < 0 && r != -ENODATA) {
return 0;
}
+int config_parse_routing_policy_rule_l3mdev(
+ 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_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL;
+ Network *network = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = routing_policy_rule_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return log_oom();
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule l3mdev, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n->l3mdev = r;
+
+ TAKE_PTR(n);
+ return 0;
+}
+
int config_parse_routing_policy_rule_family(
const char *unit,
const char *filename,
/* rule->family can be AF_UNSPEC only when Family=both. */
}
- /* Currently, [RoutingPolicyRule] does not have a setting to set FRA_L3MDEV flag. Please also
- * update routing_policy_rule_is_created_by_kernel() when a new setting which sets the flag is
- * added in the future. */
- if (rule->l3mdev > 0)
- assert_not_reached();
-
return 0;
}
bool invert_rule;
bool priority_set;
+ bool l3mdev; /* FRA_L3MDEV */
uint8_t tos;
uint8_t type;
uint8_t protocol; /* FRA_PROTOCOL */
uint8_t to_prefixlen;
uint8_t from_prefixlen;
- uint8_t l3mdev; /* FRA_L3MDEV */
uint32_t table;
uint32_t fwmark;
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_prefix);
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_priority);
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_device);
+CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_l3mdev);
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_port_range);
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_ip_protocol);
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_invert);
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 sysctl_write_ip_property_int(AF_INET6, link->ifname, "hop_limit", link->network->ipv6_hop_limit);
}
+static int link_set_ipv6_retransmission_time(Link *link) {
+ usec_t retrans_time_ms;
+
+ assert(link);
+
+ if (!link_is_configured_for_family(link, AF_INET6))
+ return 0;
+
+ if (!timestamp_is_set(link->network->ipv6_retransmission_time))
+ return 0;
+
+ retrans_time_ms = DIV_ROUND_UP(link->network->ipv6_retransmission_time, USEC_PER_MSEC);
+ if (retrans_time_ms <= 0 || retrans_time_ms > UINT32_MAX)
+ return 0;
+
+ return sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", retrans_time_ms);
+}
+
static int link_set_ipv6_proxy_ndp(Link *link) {
bool v;
if (r < 0)
log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface, ignoring: %m");
+ r = link_set_ipv6_retransmission_time(link);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot set IPv6 retransmission time for interface, ignoring: %m");
+
r = link_set_ipv6_proxy_ndp(link);
if (r < 0)
log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m");
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 (!sd)
return log_oom();
- cm = reallocarray(*custom_mounts, sizeof(CustomMount), *n_custom_mounts + 1);
+ cm = reallocarray(*custom_mounts, *n_custom_mounts + 1, sizeof(CustomMount));
if (!cm)
return log_oom();
_cleanup_close_ int orig_mntns_fd = -EBADF;
int r, rr;
- r = namespace_open(0, NULL, &orig_mntns_fd, NULL, NULL, NULL);
+ r = namespace_open(0,
+ /* ret_pidns_fd = */ NULL,
+ &orig_mntns_fd,
+ /* ret_netns_fd = */ NULL,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to pin originating mount namespace: %m");
- r = namespace_enter(-EBADF, mntns_fd, -EBADF, -EBADF, -EBADF);
+ r = namespace_enter(/* pidns_fd = */ -EBADF,
+ mntns_fd,
+ /* netns_fd = */ -EBADF,
+ /* userns_fd = */ -EBADF,
+ /* root_fd = */ -EBADF);
if (r < 0)
return log_error_errno(r, "Failed to enter mount namespace: %m");
rr = do_wipe_fully_visible_fs();
- r = namespace_enter(-EBADF, orig_mntns_fd, -EBADF, -EBADF, -EBADF);
+ r = namespace_enter(/* pidns_fd = */ -EBADF,
+ orig_mntns_fd,
+ /* netns_fd = */ -EBADF,
+ /* userns_fd = */ -EBADF,
+ /* root_fd = */ -EBADF);
if (r < 0)
return log_error_errno(r, "Failed to enter original mount namespace: %m");
#include <net/if.h>
#include <linux/if.h>
+#include <linux/nl80211.h>
#include <linux/veth.h>
#include <sys/file.h>
+#include <sys/mount.h>
#include "sd-device.h"
#include "sd-id128.h"
#include "sd-netlink.h"
#include "alloc-util.h"
+#include "device-private.h"
+#include "device-util.h"
#include "ether-addr-util.h"
+#include "fd-util.h"
#include "hexdecoct.h"
#include "lock-util.h"
#include "missing_network.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "namespace-util.h"
#include "netif-naming-scheme.h"
#include "netlink-util.h"
#include "nspawn-network.h"
#include "parse-util.h"
+#include "process-util.h"
#include "siphash24.h"
#include "socket-netlink.h"
#include "socket-util.h"
return 0;
}
-int move_network_interfaces(int netns_fd, char **iface_pairs) {
- _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+static int netns_child_begin(int netns_fd, int *ret_original_netns_fd) {
+ _cleanup_close_ int original_netns_fd = -EBADF;
int r;
- if (strv_isempty(iface_pairs))
+ assert(netns_fd >= 0);
+
+ if (ret_original_netns_fd) {
+ r = namespace_open(0,
+ /* ret_pidns_fd = */ NULL,
+ /* ret_mntns_fd = */ NULL,
+ &original_netns_fd,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open original network namespace: %m");
+ }
+
+ r = namespace_enter(/* pidns_fd = */ -EBADF,
+ /* mntns_fd = */ -EBADF,
+ netns_fd,
+ /* userns_fd = */ -EBADF,
+ /* root_fd = */ -EBADF);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enter child network namespace: %m");
+
+ r = umount_recursive("/sys/", /* flags = */ 0);
+ if (r < 0)
+ log_debug_errno(r, "Failed to unmount directories below /sys/, ignoring: %m");
+
+ (void) mkdir_p("/sys/", 0755);
+
+ /* Populate new sysfs instance associated with the client netns, to make sd_device usable. */
+ r = mount_nofollow_verbose(LOG_ERR, "sysfs", "/sys/", "sysfs",
+ MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, /* opts = */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to mount sysfs on /sys/: %m");
+
+ /* udev_avaliable() might be called previously and the result may be cached.
+ * Now, we (re-)mount sysfs. Hence, we need to reset the cache. */
+ reset_cached_udev_availability();
+
+ if (ret_original_netns_fd)
+ *ret_original_netns_fd = TAKE_FD(original_netns_fd);
+
+ return 0;
+}
+
+static int netns_fork_and_wait(int netns_fd, int *ret_original_netns_fd) {
+ int r;
+
+ assert(netns_fd >= 0);
+
+ r = safe_fork("(sd-netns)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to fork process (sd-netns): %m");
+ if (r == 0) {
+ if (netns_child_begin(netns_fd, ret_original_netns_fd) < 0)
+ _exit(EXIT_FAILURE);
+
return 0;
+ }
- r = sd_netlink_open(&rtnl);
+ if (ret_original_netns_fd)
+ *ret_original_netns_fd = -EBADF;
+
+ return 1;
+}
+
+static int needs_rename(sd_netlink **rtnl, sd_device *dev, const char *name) {
+ int r;
+
+ assert(rtnl);
+ assert(dev);
+ assert(name);
+
+ const char *ifname;
+ r = sd_device_get_sysname(dev, &ifname);
if (r < 0)
- return log_error_errno(r, "Failed to connect to netlink: %m");
+ return r;
- STRV_FOREACH_PAIR(i, b, iface_pairs) {
- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
- int ifi;
+ if (streq(name, ifname))
+ return false;
- ifi = rtnl_resolve_interface_or_warn(&rtnl, *i);
- if (ifi < 0)
- return ifi;
+ int ifindex;
+ r = sd_device_get_ifindex(dev, &ifindex);
+ if (r < 0)
+ return r;
+
+ _cleanup_strv_free_ char **altnames = NULL;
+ r = rtnl_get_link_alternative_names(rtnl, ifindex, &altnames);
+ if (r == -EOPNOTSUPP)
+ return true; /* alternative interface name is not supported, hence the name is not
+ * assigned to the interface. */
+ if (r < 0)
+ return r;
- r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi);
+ return !strv_contains(altnames, name);
+}
+
+static int move_wlan_interface_impl(sd_netlink **genl, int netns_fd, sd_device *dev) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *our_genl = NULL;
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(netns_fd >= 0);
+ assert(dev);
+
+ if (!genl)
+ genl = &our_genl;
+ if (!*genl) {
+ r = sd_genl_socket_open(genl);
if (r < 0)
- return log_error_errno(r, "Failed to allocate netlink message: %m");
+ return log_error_errno(r, "Failed to connect to generic netlink: %m");
+ }
+
+ r = sd_genl_message_new(*genl, NL80211_GENL_NAME, NL80211_CMD_SET_WIPHY_NETNS, &m);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to allocate netlink message: %m");
+
+ uint32_t phy_index;
+ r = device_get_sysattr_u32(dev, "phy80211/index", &phy_index);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to get phy index: %m");
+
+ r = sd_netlink_message_append_u32(m, NL80211_ATTR_WIPHY, phy_index);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to append phy index to netlink message: %m");
+
+ r = sd_netlink_message_append_u32(m, NL80211_ATTR_NETNS_FD, netns_fd);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to append namespace fd to netlink message: %m");
+
+ r = sd_netlink_call(*genl, m, 0, NULL);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to move interface to namespace: %m");
+
+ return 0;
+}
+
+static int move_wlan_interface_one(
+ sd_netlink **rtnl,
+ sd_netlink **genl,
+ int *temp_netns_fd,
+ int netns_fd,
+ sd_device *dev,
+ const char *name) {
+
+ int r;
- r = sd_netlink_message_append_u32(m, IFLA_NET_NS_FD, netns_fd);
+ assert(rtnl);
+ assert(genl);
+ assert(temp_netns_fd);
+ assert(netns_fd >= 0);
+ assert(dev);
+ assert(name);
+
+ r = needs_rename(rtnl, dev, name);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to determine if the interface should be renamed to '%s': %m", name);
+ if (r == 0)
+ return move_wlan_interface_impl(genl, netns_fd, dev);
+
+ /* The command NL80211_CMD_SET_WIPHY_NETNS takes phy instead of network interface, and does not take
+ * an interface name in the passed network namespace. Hence, we need to move the phy and interface to
+ * a temporary network namespace, rename the interface in it, and move them to the requested netns. */
+
+ if (*temp_netns_fd < 0) {
+ r = netns_acquire();
if (r < 0)
- return log_error_errno(r, "Failed to append namespace fd to netlink message: %m");
+ return log_error_errno(r, "Failed to acquire new network namespace: %m");
+ *temp_netns_fd = r;
+ }
+
+ r = move_wlan_interface_impl(genl, *temp_netns_fd, dev);
+ if (r < 0)
+ return r;
+
+ const char *sysname;
+ r = sd_device_get_sysname(dev, &sysname);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to get interface name: %m");
- if (!streq(*b, *i)) {
- r = sd_netlink_message_append_string(m, IFLA_IFNAME, *b);
- if (r < 0)
- return log_error_errno(r, "Failed to add netlink interface name: %m");
+ r = netns_fork_and_wait(*temp_netns_fd, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to fork process (nspawn-rename-wlan): %m");
+ if (r == 0) {
+ _cleanup_(sd_device_unrefp) sd_device *temp_dev = NULL;
+
+ r = rtnl_rename_link(NULL, sysname, name);
+ if (r < 0) {
+ log_error_errno(r, "Failed to rename network interface '%s' to '%s': %m", sysname, name);
+ goto finalize;
}
- r = sd_netlink_call(rtnl, m, 0, NULL);
+ r = sd_device_new_from_ifname(&temp_dev, name);
+ if (r < 0) {
+ log_error_errno(r, "Failed to acquire device '%s': %m", name);
+ goto finalize;
+ }
+
+ r = move_wlan_interface_impl(NULL, netns_fd, temp_dev);
+
+ finalize:
+ _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+
+ return 0;
+}
+
+static int move_network_interface_one(sd_netlink **rtnl, int netns_fd, sd_device *dev, const char *name) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(rtnl);
+ assert(netns_fd >= 0);
+ assert(dev);
+ assert(name);
+
+ if (!*rtnl) {
+ r = sd_netlink_open(rtnl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to rtnetlink: %m");
+ }
+
+ int ifindex;
+ r = sd_device_get_ifindex(dev, &ifindex);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to get ifindex: %m");
+
+ r = sd_rtnl_message_new_link(*rtnl, &m, RTM_SETLINK, ifindex);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to allocate netlink message: %m");
+
+ r = sd_netlink_message_append_u32(m, IFLA_NET_NS_FD, netns_fd);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to append namespace fd to netlink message: %m");
+
+ r = needs_rename(rtnl, dev, name);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to determine if the interface should be renamed to '%s': %m", name);
+ if (r > 0) {
+ r = sd_netlink_message_append_string(m, IFLA_IFNAME, name);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to add netlink interface name: %m");
+ }
+
+ r = sd_netlink_call(*rtnl, m, 0, NULL);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to move interface to namespace: %m");
+
+ return 0;
+}
+
+int move_network_interfaces(int netns_fd, char **iface_pairs) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL, *genl = NULL;
+ _cleanup_close_ int temp_netns_fd = -EBADF;
+ int r;
+
+ assert(netns_fd >= 0);
+
+ if (strv_isempty(iface_pairs))
+ return 0;
+
+ STRV_FOREACH_PAIR(from, to, iface_pairs) {
+ _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+
+ r = sd_device_new_from_ifname(&dev, *from);
if (r < 0)
- return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i);
+ return log_error_errno(r, "Unknown interface name %s: %m", *from);
+
+ if (device_is_devtype(dev, "wlan"))
+ r = move_wlan_interface_one(&rtnl, &genl, &temp_netns_fd, netns_fd, dev, *to);
+ else
+ r = move_network_interface_one(&rtnl, netns_fd, dev, *to);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int move_back_network_interfaces(int child_netns_fd, char **interface_pairs) {
+ _cleanup_close_ int parent_netns_fd = -EBADF;
+ int r;
+
+ assert(child_netns_fd >= 0);
+
+ if (strv_isempty(interface_pairs))
+ return 0;
+
+ r = netns_fork_and_wait(child_netns_fd, &parent_netns_fd);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* Reverse network interfaces pair list so that interfaces get their initial name back.
+ * This is about ensuring interfaces get their old name back when being moved back. */
+ interface_pairs = strv_reverse(interface_pairs);
+
+ r = move_network_interfaces(parent_netns_fd, interface_pairs);
+ _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}
return 0;
int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs);
int move_network_interfaces(int netns_fd, char **iface_pairs);
+int move_back_network_interfaces(int child_netns_fd, char **interface_pairs);
int veth_extra_parse(char ***l, const char *p);
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();
}
return r;
if (arg_userns_mode != USER_NAMESPACE_NO) {
- r = namespace_open(0, NULL, &mntns_fd, NULL, NULL, NULL);
+ r = namespace_open(0,
+ /* ret_pidns_fd = */ NULL,
+ &mntns_fd,
+ /* ret_netns_fd = */ NULL,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to pin outer mount namespace: %m");
* user if user namespaces are turned on. */
if (arg_network_namespace_path) {
- r = namespace_enter(-1, -1, netns_fd, -1, -1);
+ r = namespace_enter(/* pidns_fd = */ -EBADF,
+ /* mntns_fd = */ -EBADF,
+ netns_fd,
+ /* userns_fd = */ -EBADF,
+ /* root_fd = */ -EBADF);
if (r < 0)
return log_error_errno(r, "Failed to join network namespace: %m");
}
return r;
/* Make some superficial checks whether the range is currently known in the user database */
- if (getpwuid(candidate))
+ if (getpwuid_malloc(candidate, /* ret= */ NULL) >= 0)
goto next;
- if (getpwuid(candidate + UINT32_C(0xFFFE)))
+ if (getpwuid_malloc(candidate + UINT32_C(0xFFFE), /* ret= */ NULL) >= 0)
goto next;
- if (getgrgid(candidate))
+ if (getgrgid_malloc(candidate, /* ret= */ NULL) >= 0)
goto next;
- if (getgrgid(candidate + UINT32_C(0xFFFE)))
+ if (getgrgid_malloc(candidate + UINT32_C(0xFFFE), /* ret= */ NULL) >= 0)
goto next;
*ret_lock_file = lf;
if (child_netns_fd < 0) {
/* Make sure we have an open file descriptor to the child's network
* namespace so it stays alive even if the child exits. */
- r = namespace_open(*pid, NULL, NULL, &child_netns_fd, NULL, NULL);
+ r = namespace_open(*pid,
+ /* ret_pidns_fd = */ NULL,
+ /* ret_mntns_fd = */ NULL,
+ &child_netns_fd,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to open child network namespace: %m");
}
fd_kmsg_fifo = safe_close(fd_kmsg_fifo);
if (arg_private_network) {
- /* Move network interfaces back to the parent network namespace. We use `safe_fork`
- * to avoid having to move the parent to the child network namespace. */
- r = safe_fork(NULL, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL);
+ r = move_back_network_interfaces(child_netns_fd, arg_network_interfaces);
if (r < 0)
return r;
-
- if (r == 0) {
- _cleanup_close_ int parent_netns_fd = -EBADF;
-
- r = namespace_open(getpid_cached(), NULL, NULL, &parent_netns_fd, NULL, NULL);
- if (r < 0) {
- log_error_errno(r, "Failed to open parent network namespace: %m");
- _exit(EXIT_FAILURE);
- }
-
- r = namespace_enter(-1, -1, child_netns_fd, -1, -1);
- if (r < 0) {
- log_error_errno(r, "Failed to enter child network namespace: %m");
- _exit(EXIT_FAILURE);
- }
-
- /* Reverse network interfaces pair list so that interfaces get their initial name back.
- * This is about ensuring interfaces get their old name back when being moved back. */
- arg_network_interfaces = strv_reverse(arg_network_interfaces);
-
- r = move_network_interfaces(parent_netns_fd, arg_network_interfaces);
- if (r < 0)
- log_error_errno(r, "Failed to move network interfaces back to parent network namespace: %m");
-
- _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
- }
}
r = wait_for_container(TAKE_PID(*pid), &container_status);
if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */
arg_quiet = true;
- if (!arg_quiet)
- log_info("Spawning container %s on %s.\nPress Ctrl-] three times within 1s to kill container.",
- arg_machine, arg_image ?: arg_directory);
+ if (!arg_quiet) {
+ const char *t = arg_image ?: arg_directory;
+ _cleanup_free_ char *u = NULL;
+ (void) terminal_urlify_path(t, t, &u);
+
+ log_info("%s %sSpawning container %s on %s.%s\n"
+ "%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal(),
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
+ }
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0);
* configuration can distinguish such executed but negative replies from complete failure to
* talk to resolved). */
const char *error_id;
- r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id, NULL);
+ r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id);
if (r < 0)
goto fail;
if (!isempty(error_id)) {
goto fail;
const char *error_id;
- r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id, NULL);
+ r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id);
if (r < 0)
goto fail;
if (!isempty(error_id)) {
goto fail;
const char* error_id;
- r = varlink_call(link, "io.systemd.Resolve.ResolveAddress", cparams, &rparams, &error_id, NULL);
+ r = varlink_call(link, "io.systemd.Resolve.ResolveAddress", cparams, &rparams, &error_id);
if (r < 0)
goto fail;
if (!isempty(error_id)) {
int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */
} variables[] = {
{ EFI_VENDOR_GLOBAL, "SecureBoot", 0 },
- { EFI_VENDOR_GLOBAL, "PK", 0 },
- { EFI_VENDOR_GLOBAL, "KEK", 0 },
+ { EFI_VENDOR_GLOBAL, "PK", 1 },
+ { EFI_VENDOR_GLOBAL, "KEK", 1 },
{ EFI_VENDOR_DATABASE, "db", 1 },
{ EFI_VENDOR_DATABASE, "dbx", 1 },
{ EFI_VENDOR_DATABASE, "dbt", -1 },
#include <linux/loop.h>
+#include "sd-messages.h"
+
#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-locator.h"
return true;
}
+static void log_portable_verb(
+ const char *verb,
+ const char *message_id,
+ const char *image_path,
+ OrderedHashmap *extension_images,
+ char **extension_image_paths,
+ PortableFlags flags) {
+
+ _cleanup_free_ char *root_base_name = NULL, *extensions_joined = NULL;
+ _cleanup_strv_free_ char **extension_base_names = NULL;
+ Image *ext;
+ int r;
+
+ assert(verb);
+ assert(message_id);
+ assert(image_path);
+ assert(!extension_images || !extension_image_paths);
+
+ /* Use the same structured metadata as it is attached to units via LogExtraFields=. The main image
+ * is logged as PORTABLE_ROOT= and extensions, if any, as individual PORTABLE_EXTENSION= fields. */
+
+ r = path_extract_filename(image_path, &root_base_name);
+ if (r < 0)
+ log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", image_path);
+
+ ORDERED_HASHMAP_FOREACH(ext, extension_images) {
+ _cleanup_free_ char *extension_base_name = NULL;
+
+ r = path_extract_filename(ext->path, &extension_base_name);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", ext->path);
+ continue;
+ }
+
+ r = strv_extendf(&extension_base_names, "PORTABLE_EXTENSION=%s", extension_base_name);
+ if (r < 0)
+ log_oom_debug();
+
+ if (!strextend_with_separator(&extensions_joined, ", ", ext->path))
+ log_oom_debug();
+ }
+
+ STRV_FOREACH(e, extension_image_paths) {
+ _cleanup_free_ char *extension_base_name = NULL;
+
+ r = path_extract_filename(*e, &extension_base_name);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", *e);
+ continue;
+ }
+
+ r = strv_extendf(&extension_base_names, "PORTABLE_EXTENSION=%s", extension_base_name);
+ if (r < 0)
+ log_oom_debug();
+
+ if (!strextend_with_separator(&extensions_joined, ", ", *e))
+ log_oom_debug();
+ }
+
+ LOG_CONTEXT_PUSH_STRV(extension_base_names);
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE("Successfully %s%s '%s%s%s'",
+ verb,
+ FLAGS_SET(flags, PORTABLE_RUNTIME) ? " ephemeral" : "",
+ image_path,
+ isempty(extensions_joined) ? "" : "' and its extension(s) '",
+ strempty(extensions_joined)),
+ message_id,
+ "PORTABLE_ROOT=%s", strna(root_base_name));
+}
+
int portable_attach(
sd_bus *bus,
const char *name_or_path,
* operation otherwise. */
(void) install_image_and_extensions_symlinks(image, extension_images, flags, changes, n_changes);
+ log_portable_verb(
+ "attached",
+ "MESSAGE_ID=" SD_MESSAGE_PORTABLE_ATTACHED_STR,
+ image->path,
+ extension_images,
+ /* extension_image_paths= */ NULL,
+ flags);
+
return 0;
}
if (rmdir(where) >= 0)
portable_changes_add(changes, n_changes, PORTABLE_UNLINK, where, NULL);
+ log_portable_verb(
+ "detached",
+ "MESSAGE_ID=" SD_MESSAGE_PORTABLE_DETACHED_STR,
+ name_or_path,
+ /* extension_images= */ NULL,
+ extension_image_paths,
+ flags);
+
return ret;
not_found:
if (r < 0)
return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
- r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpStatistics", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpStatistics", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue DumpStatistics() varlink call: %m");
+ return r;
if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
return json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
- r = varlink_call(vl, "io.systemd.Resolve.Monitor.ResetStatistics", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.ResetStatistics", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue ResetStatistics() varlink call: %m");
+ return r;
if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
return json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
- r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpCache", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpCache", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue DumpCache() varlink call: %m");
+ return r;
d = json_variant_by_key(reply, "dump");
if (!d)
if (r < 0)
return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
- r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpServerState", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpServerState", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue DumpServerState() varlink call: %m");
+ return r;
d = json_variant_by_key(reply, "dump");
if (!d)
r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL);
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ r = dns_packet_append_uint16(p, rr->svcb.priority, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_name(p, rr->svcb.target_name, false, false, NULL);
+ if (r < 0)
+ goto fail;
+
+ LIST_FOREACH(params, i, rr->svcb.params) {
+ r = dns_packet_append_uint16(p, i->key, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint16(p, i->length, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_blob(p, i->value, i->length, NULL);
+ if (r < 0)
+ goto fail;
+ }
+ break;
+
case DNS_TYPE_CAA:
r = dns_packet_append_uint8(p, rr->caa.flags, NULL);
if (r < 0)
return m <= 9 && e <= 9 && (m > 0 || e == 0);
}
+static bool dns_svc_param_is_valid(DnsSvcParam *i) {
+ if (!i)
+ return false;
+
+ switch (i->key) {
+ /* RFC 9460, section 7.1.1: alpn-ids must exactly fill SvcParamValue */
+ case DNS_SVC_PARAM_KEY_ALPN: {
+ size_t sz = 0;
+ if (i->length <= 0)
+ return false;
+ while (sz < i->length)
+ sz += 1 + i->value[sz]; /* N.B. will not overflow */
+ return sz == i->length;
+ }
+
+ /* RFC 9460, section 7.1.1: value must be empty */
+ case DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN:
+ return i->length == 0;
+
+ /* RFC 9460, section 7.2 */
+ case DNS_SVC_PARAM_KEY_PORT:
+ return i->length == 2;
+
+ /* RFC 9460, section 7.3: addrs must exactly fill SvcParamValue */
+ case DNS_SVC_PARAM_KEY_IPV4HINT:
+ return i->length % (sizeof (struct in_addr)) == 0;
+ case DNS_SVC_PARAM_KEY_IPV6HINT:
+ return i->length % (sizeof (struct in6_addr)) == 0;
+
+ /* Otherwise, permit any value */
+ default:
+ return true;
+ }
+}
+
int dns_packet_read_rr(
DnsPacket *p,
DnsResourceRecord **ret,
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ r = dns_packet_read_uint16(p, &rr->svcb.priority, NULL);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_read_name(p, &rr->svcb.target_name, false /* uncompressed */, NULL);
+ if (r < 0)
+ return r;
+
+ DnsSvcParam *last = NULL;
+ while (p->rindex - offset < rdlength) {
+ _cleanup_free_ DnsSvcParam *i = NULL;
+ uint16_t svc_param_key;
+ uint16_t sz;
+
+ r = dns_packet_read_uint16(p, &svc_param_key, NULL);
+ if (r < 0)
+ return r;
+ /* RFC 9460, section 2.2 says we must consider an RR malformed if SvcParamKeys are
+ * not in strictly increasing order */
+ if (last && last->key >= svc_param_key)
+ return -EBADMSG;
+
+ r = dns_packet_read_uint16(p, &sz, NULL);
+ if (r < 0)
+ return r;
+
+ i = malloc0(offsetof(DnsSvcParam, value) + sz);
+ if (!i)
+ return -ENOMEM;
+
+ i->key = svc_param_key;
+ i->length = sz;
+ r = dns_packet_read_blob(p, &i->value, sz, NULL);
+ if (r < 0)
+ return r;
+ if (!dns_svc_param_is_valid(i))
+ return -EBADMSG;
+
+ LIST_INSERT_AFTER(params, rr->svcb.params, last, i);
+ last = TAKE_PTR(i);
+ }
+
+ break;
+
case DNS_TYPE_CAA:
r = dns_packet_read_uint8(p, &rr->caa.flags, NULL);
if (r < 0)
return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i);
}
+static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = {
+ [DNS_SVC_PARAM_KEY_MANDATORY] = "mandatory",
+ [DNS_SVC_PARAM_KEY_ALPN] = "alpn",
+ [DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn",
+ [DNS_SVC_PARAM_KEY_PORT] = "port",
+ [DNS_SVC_PARAM_KEY_IPV4HINT] = "ipv4hint",
+ [DNS_SVC_PARAM_KEY_ECH] = "ech",
+ [DNS_SVC_PARAM_KEY_IPV6HINT] = "ipv6hint",
+ [DNS_SVC_PARAM_KEY_DOHPATH] = "dohpath",
+ [DNS_SVC_PARAM_KEY_OHTTP] = "ohttp",
+};
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int);
+
+const char *format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) {
+ const char *p = dns_svc_param_key_to_string(i);
+ if (p)
+ return p;
+
+ return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i);
+}
+
static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
[DNS_PROTOCOL_DNS] = "dns",
[DNS_PROTOCOL_MDNS] = "mdns",
const char* dns_protocol_to_string(DnsProtocol p) _const_;
DnsProtocol dns_protocol_from_string(const char *s) _pure_;
+/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */
+enum {
+ DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 section 8 */
+ DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 section 7.1 */
+ DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 Section 7.1 */
+ DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 section 7.2 */
+ DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 section 7.3 */
+ DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */
+ DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 section 7.3 */
+ DNS_SVC_PARAM_KEY_DOHPATH = 7, /* RFC 9461 */
+ DNS_SVC_PARAM_KEY_OHTTP = 8,
+ _DNS_SVC_PARAM_KEY_MAX_DEFINED,
+ DNS_SVC_PARAM_KEY_INVALID = 65535 /* RFC 9460 */
+};
+
+const char* dns_svc_param_key_to_string(int i) _const_;
+const char *format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]);
+#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {})
+
#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
+#include "unaligned.h"
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) {
DnsResourceKey *k;
free(rr->tlsa.data);
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ free(rr->svcb.target_name);
+ dns_svc_param_free_all(rr->svcb.params);
+ break;
+
case DNS_TYPE_CAA:
free(rr->caa.tag);
free(rr->caa.value);
a->tlsa.matching_type == b->tlsa.matching_type &&
FIELD_EQUAL(a->tlsa, b->tlsa, data);
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ return a->svcb.priority == b->svcb.priority &&
+ dns_name_equal(a->svcb.target_name, b->svcb.target_name) &&
+ dns_svc_params_equal(a->svcb.params, b->svcb.params);
+
case DNS_TYPE_CAA:
return a->caa.flags == b->caa.flags &&
streq(a->caa.tag, b->caa.tag) &&
return s;
}
+static char *format_svc_param_value(DnsSvcParam *i) {
+ _cleanup_free_ char *value = NULL;
+
+ assert(i);
+
+ switch (i->key) {
+ case DNS_SVC_PARAM_KEY_ALPN: {
+ size_t offset = 0;
+ _cleanup_strv_free_ char **values_strv = NULL;
+ while (offset < i->length) {
+ size_t sz = (uint8_t) i->value[offset++];
+
+ char *alpn = cescape_length((char *)&i->value[offset], sz);
+ if (!alpn)
+ return NULL;
+
+ if (strv_push(&values_strv, alpn) < 0)
+ return NULL;
+
+ offset += sz;
+ }
+ value = strv_join(values_strv, ",");
+ if (!value)
+ return NULL;
+ break;
+
+ }
+ case DNS_SVC_PARAM_KEY_PORT: {
+ uint16_t port = unaligned_read_be16(i->value);
+ if (asprintf(&value, "%" PRIu16, port) < 0)
+ return NULL;
+ return TAKE_PTR(value);
+ }
+ case DNS_SVC_PARAM_KEY_IPV4HINT: {
+ const struct in_addr *addrs = i->value_in_addr;
+ _cleanup_strv_free_ char **values_strv = NULL;
+ for (size_t n = 0; n < i->length / sizeof (struct in_addr); n++) {
+ char *addr;
+ if (in_addr_to_string(AF_INET, (const union in_addr_union*) &addrs[n], &addr) < 0)
+ return NULL;
+ if (strv_push(&values_strv, addr) < 0)
+ return NULL;
+ }
+ return strv_join(values_strv, ",");
+ }
+ case DNS_SVC_PARAM_KEY_IPV6HINT: {
+ const struct in6_addr *addrs = i->value_in6_addr;
+ _cleanup_strv_free_ char **values_strv = NULL;
+ for (size_t n = 0; n < i->length / sizeof (struct in6_addr); n++) {
+ char *addr;
+ if (in_addr_to_string(AF_INET6, (const union in_addr_union*) &addrs[n], &addr) < 0)
+ return NULL;
+ if (strv_push(&values_strv, addr) < 0)
+ return NULL;
+ }
+ return strv_join(values_strv, ",");
+ }
+ default: {
+ value = decescape((char *)&i->value, " ,", i->length);
+ if (!value)
+ return NULL;
+ break;
+ }
+ }
+
+ char *qvalue;
+ if (asprintf(&qvalue, "\"%s\"", value) < 0)
+ return NULL;
+ return qvalue;
+}
+
+static char *format_svc_param(DnsSvcParam *i) {
+ const char *key = FORMAT_DNS_SVC_PARAM_KEY(i->key);
+ _cleanup_free_ char *value = NULL;
+
+ assert(i);
+
+ if (i->length == 0)
+ return strdup(key);
+
+ value = format_svc_param_value(i);
+ if (!value)
+ return NULL;
+
+ return strjoin(key, "=", value);
+}
+
+static char *format_svc_params(DnsSvcParam *first) {
+ _cleanup_strv_free_ char **params = NULL;
+
+ LIST_FOREACH(params, i, first) {
+ char *param = format_svc_param(i);
+ if (!param)
+ return NULL;
+ if (strv_push(¶ms, param) < 0)
+ return NULL;
+ }
+
+ return strv_join(params, " ");
+}
+
const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
_cleanup_free_ char *s = NULL, *t = NULL;
char k[DNS_RESOURCE_KEY_STRING_MAX];
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ t = format_svc_params(rr->svcb.params);
+ if (!t)
+ return NULL;
+ r = asprintf(&s, "%s %d %s %s", k, rr->svcb.priority,
+ isempty(rr->svcb.target_name) ? "." : rr->svcb.target_name,
+ t);
+ if (r < 0)
+ return NULL;
+
+ break;
+
case DNS_TYPE_OPENPGPKEY:
r = asprintf(&s, "%s", k);
if (r < 0)
siphash24_compress_safe(rr->tlsa.data, rr->tlsa.data_size, state);
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ dns_name_hash_func(rr->svcb.target_name, state);
+ siphash24_compress_typesafe(rr->svcb.priority, state);
+ LIST_FOREACH(params, j, rr->svcb.params) {
+ siphash24_compress_typesafe(j->key, state);
+ siphash24_compress_safe(j->value, j->length, state);
+ }
+ break;
+
case DNS_TYPE_CAA:
siphash24_compress_typesafe(rr->caa.flags, state);
string_hash_func(rr->caa.tag, state);
copy->caa.value_size = rr->caa.value_size;
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ copy->svcb.priority = rr->svcb.priority;
+ copy->svcb.target_name = strdup(rr->svcb.target_name);
+ if (!copy->svcb.target_name)
+ return NULL;
+ copy->svcb.params = dns_svc_params_copy(rr->svcb.params);
+ if (rr->svcb.params && !copy->svcb.params)
+ return NULL;
+ break;
+
case DNS_TYPE_OPT:
default:
copy->generic.data = memdup(rr->generic.data, rr->generic.data_size);
return NULL;
}
+DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *first) {
+ LIST_FOREACH(params, i, first)
+ free(i);
+
+ return NULL;
+}
+
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
DnsTxtItem *bb = b;
return copy;
}
+bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b) {
+ DnsSvcParam *bb = b;
+
+ if (a == b)
+ return true;
+
+ LIST_FOREACH(params, aa, a) {
+ if (!bb)
+ return false;
+
+ if (aa->key != bb->key)
+ return false;
+
+ if (memcmp_nn(aa->value, aa->length, bb->value, bb->length) != 0)
+ return false;
+
+ bb = bb->params_next;
+ }
+
+ return !bb;
+}
+
+DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first) {
+ DnsSvcParam *copy = NULL, *end = NULL;
+
+ LIST_FOREACH(params, i, first) {
+ DnsSvcParam *j;
+
+ j = memdup(i, offsetof(DnsSvcParam, value) + i->length);
+ if (!j)
+ return dns_svc_param_free_all(copy);
+
+ LIST_INSERT_AFTER(params, copy, end, j);
+ end = j;
+ }
+
+ return copy;
+}
+
int dns_txt_item_new_empty(DnsTxtItem **ret) {
DnsTxtItem *i;
r = json_variant_new_array(ret, elements, n);
finalize:
- for (size_t i = 0; i < n; i++)
- json_variant_unref(elements[i]);
+ json_variant_unref_many(elements, n);
+ return r;
+}
+
+static int svc_params_to_json(DnsSvcParam *params, JsonVariant **ret) {
+ JsonVariant **elements = NULL;
+ size_t n = 0;
+ int r;
+
+ assert(ret);
+
+ LIST_FOREACH(params, i, params) {
+ if (!GREEDY_REALLOC(elements, n + 1)) {
+ r = -ENOMEM;
+ goto finalize;
+ }
+
+ r = json_variant_new_base64(elements + n, i->value, i->length);
+ if (r < 0)
+ goto finalize;
+
+ n++;
+ }
- free(elements);
+ r = json_variant_new_array(ret, elements, n);
+finalize:
+ json_variant_unref_many(elements, n);
return r;
}
JSON_BUILD_PAIR("matchingType", JSON_BUILD_UNSIGNED(rr->tlsa.matching_type)),
JSON_BUILD_PAIR("data", JSON_BUILD_HEX(rr->tlsa.data, rr->tlsa.data_size))));
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS: {
+ _cleanup_(json_variant_unrefp) JsonVariant *p = NULL;
+ r = svc_params_to_json(rr->svcb.params, &p);
+ if (r < 0)
+ return r;
+
+ return json_build(ret,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)),
+ JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->svcb.priority)),
+ JSON_BUILD_PAIR("target", JSON_BUILD_STRING(rr->svcb.target_name)),
+ JSON_BUILD_PAIR("params", JSON_BUILD_VARIANT(p))));
+ }
+
case DNS_TYPE_CAA:
return json_build(ret,
JSON_BUILD_OBJECT(
typedef struct DnsResourceKey DnsResourceKey;
typedef struct DnsResourceRecord DnsResourceRecord;
typedef struct DnsTxtItem DnsTxtItem;
+typedef struct DnsSvcParam DnsSvcParam;
/* DNSKEY RR flags */
#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0)
uint8_t data[];
};
+struct DnsSvcParam {
+ uint16_t key;
+ size_t length;
+ LIST_FIELDS(DnsSvcParam, params);
+ union {
+ DECLARE_FLEX_ARRAY(uint8_t, value);
+ DECLARE_FLEX_ARRAY(struct in_addr, value_in_addr);
+ DECLARE_FLEX_ARRAY(struct in6_addr, value_in6_addr);
+ };
+};
+
struct DnsResourceRecord {
unsigned n_ref;
uint32_t ttl;
uint8_t matching_type;
} tlsa;
+ /* https://tools.ietf.org/html/rfc9460 */
+ struct {
+ uint16_t priority;
+ char *target_name;
+ DnsSvcParam *params;
+ } svcb, https;
+
/* https://tools.ietf.org/html/rfc6844 */
struct {
char *tag;
DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i);
int dns_txt_item_new_empty(DnsTxtItem **ret);
+DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *i);
+bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b);
+DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first);
+
int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, size_t size);
int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret);
}
}
- 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;
}
{ "same-dir", no_argument, NULL, 'd' },
{ "shell", no_argument, NULL, 'S' },
{ "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE },
- { "background", no_argument, NULL, ARG_BACKGROUND },
+ { "background", required_argument, NULL, ARG_BACKGROUND },
{},
};
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)
+ swap = strjoina(" (swap: ", FORMAT_BYTES(c.memory_swap_peak), ")");
+ else
+ swap = "";
+
+ log_info("Memory peak: %s%s", FORMAT_BYTES(c.memory_peak), swap);
+ }
+
+ const char *ip_ingress = NULL, *ip_egress = NULL;
- if (c.memory_swap_peak != UINT64_MAX)
- log_info("Memory swap peak: %s", FORMAT_BYTES(c.memory_swap_peak));
+ 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 (c.ip_ingress_bytes != UINT64_MAX)
- log_info("IP traffic received: %s", FORMAT_BYTES(c.ip_ingress_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,
/* The map name is primarily informational for debugging purposes, and typically too short
* to carry the full unit name, hence we employ a trivial lossy escaping to make it fit
* (truncation + only alphanumerical, "." and "_" are allowed as per
- * https://www.kernel.org/doc/html/next/bpf/maps.html#usage-notes) */
+ * https://docs.kernel.org/bpf/maps.html#usage-notes) */
for (size_t i = 0; i < sizeof(attr.map_name) - 1 && *n; i++, n++)
attr.map_name[i] = strchr(ALPHANUMERICAL ".", *n) ? *n : '_';
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;
}
if (isempty(rvalue)) {
*t = -1;
- return 0;
+ return 1;
}
r = parse_tristate(rvalue, t);
return 0;
}
- return 0;
+ return 1;
}
int config_parse_string(
#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))
/* 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->iov_len <
- ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
+ 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)) +
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size.");
if (input->iov_len <
- 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)) +
+ p +
ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
};
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **initrd_release = NULL, **sysext_release = NULL, **confext_release = NULL;
+ _cleanup_free_ char *hostname = NULL, *t = NULL;
_cleanup_close_pair_ int error_pipe[2] = EBADF_PAIR;
- _cleanup_(rmdir_and_freep) char *t = NULL;
_cleanup_(sigkill_waitp) pid_t child = 0;
sd_id128_t machine_id = SD_ID128_NULL;
- _cleanup_free_ char *hostname = NULL;
unsigned n_meta_initialized = 0;
int fds[2 * _META_MAX], r, v;
int has_init_system = -1;
}
}
- r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t);
+ r = get_common_dissect_directory(&t);
if (r < 0)
goto finish;
return 0;
}
+
+int get_common_dissect_directory(char **ret) {
+ _cleanup_free_ char *t = NULL;
+ int r;
+
+ /* A common location we mount dissected images to. The assumption is that everyone who uses this
+ * function runs in their own private mount namespace (with mount propagation off on /run/systemd/,
+ * and thus can mount something here without affecting anyone else). */
+
+ t = strdup("/run/systemd/dissect-root");
+ if (!t)
+ return log_oom_debug();
+
+ r = mkdir_parents(t, 0755);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create parent dirs of mount point '%s': %m", t);
+
+ r = RET_NERRNO(mkdir(t, 0000)); /* It's supposed to be overmounted, hence let's make this inaccessible */
+ if (r < 0 && r != -EEXIST)
+ return log_debug_errno(r, "Failed to create mount point '%s': %m", t);
+
+ if (ret)
+ *ret = TAKE_PTR(t);
+
+ return 0;
+}
return m->decrypted_node ? m->decrypted_fstype : m->fstype;
}
+
+int get_common_dissect_directory(char **ret);
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
- bool matching = false;
- JsonVariant *m;
-
if (!json_variant_is_object(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
- m = json_variant_by_key(e, "matchMachineId");
- if (m) {
- r = per_machine_id_match(m, flags);
- if (r < 0)
- return r;
-
- matching = r > 0;
- }
-
- if (!matching) {
- m = json_variant_by_key(e, "matchHostname");
- if (m) {
- r = per_machine_hostname_match(m, flags);
- if (r < 0)
- return r;
-
- matching = r > 0;
- }
- }
-
- if (!matching)
+ r = per_machine_match(e, flags);
+ if (r < 0)
+ return r;
+ if (r == 0)
continue;
r = json_dispatch(e, per_machine_dispatch_table, flags, userdata);
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;
/* 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 (!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;
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);
if (r < 0)
return r;
- r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd);
+ r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &rootfd);
if (r < 0)
return r;
fd = safe_close(fd);
/* Store current mount namespace */
- r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL);
+ r = namespace_open(0,
+ /* ret_pidns_fd = */ NULL,
+ &initial_mntns_fd,
+ /* ret_netns_fd = */ NULL,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
if (r < 0)
return log_error_errno(r, "Can't fetch current mount namespace: %m");
return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id);
/* Return to initial namespace and proceed a lazy tmpfs unmount */
- r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1);
+ r = namespace_enter(/* pidns_fd = */ -EBADF,
+ initial_mntns_fd,
+ /* netns_fd = */ -EBADF,
+ /* userns_fd = */ -EBADF,
+ /* root_fd = */ -EBADF);
if (r < 0)
return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id);
"-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 (!pidref_is_set(target))
return -ESRCH;
- r = namespace_open(target->pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
+ r = namespace_open(target->pid, &pidns_fd, &mntns_fd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &root_fd);
if (r < 0)
return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m");
(void) fd_nonblock(f->output_fd, false);
if (colors_enabled())
- (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_LINE, SIZE_MAX);
+ (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_SCREEN, SIZE_MAX);
if (f->close_output_fd)
f->output_fd = safe_close(f->output_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,
+ /* ret_pidns_fd = */ NULL,
+ /* ret_mntns_fd = */ NULL,
+ &_netns_fd,
+ /* ret_userns_fd = */ NULL,
+ /* ret_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);
}
}
+/* 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;
/* 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 (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 (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.");
#include "id128-util.h"
#include "log.h"
#include "macro.h"
+#include "missing_threads.h"
#include "parse-util.h"
#include "path-util.h"
#include "signal-util.h"
(errno == ENOENT ? true : -errno) : false;
}
-bool udev_available(void) {
- static int cache = -1;
+static int cached_udev_availability = -1;
+
+void reset_cached_udev_availability(void) {
+ cached_udev_availability = -1;
+}
+bool udev_available(void) {
/* The service systemd-udevd is started only when /sys is read write.
* See systemd-udevd.service: ConditionPathIsReadWrite=/sys
* Also, our container interface (http://systemd.io/CONTAINER_INTERFACE/) states that /sys must
* be mounted in read-only mode in containers. */
- if (cache >= 0)
- return cache;
+ if (cached_udev_availability >= 0)
+ return cached_udev_availability;
- return (cache = (path_is_read_only_fs("/sys/") <= 0));
+ return (cached_udev_availability = (path_is_read_only_fs("/sys/") <= 0));
}
int device_get_vendor_string(sd_device *device, const char **ret) {
int udev_queue_is_empty(void);
+void reset_cached_udev_availability(void);
bool udev_available(void);
int device_get_vendor_string(sd_device *device, const char **ret);
bool with_shadow,
UserRecord **ret) {
- _cleanup_free_ char *buf = NULL, *sbuf = NULL;
- struct passwd pwd, *result;
+ _cleanup_free_ char *sbuf = NULL;
+ _cleanup_free_ struct passwd *result = NULL;
bool incomplete = false;
- size_t buflen = 4096;
struct spwd spwd, *sresult = NULL;
int r;
assert(name);
- for (;;) {
- buf = malloc(buflen);
- if (!buf)
- return -ENOMEM;
-
- r = getpwnam_r(name, &pwd, buf, buflen, &result);
- if (r == 0) {
- if (!result)
- return -ESRCH;
-
- break;
- }
-
- if (r < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value");
- if (r != ERANGE)
- return -r;
-
- if (buflen > SIZE_MAX / 2)
- return -ERANGE;
-
- buflen *= 2;
- buf = mfree(buf);
- }
+ r = getpwnam_malloc(name, &result);
+ if (r < 0)
+ return r;
if (with_shadow) {
r = nss_spwd_for_passwd(result, &spwd, &sbuf);
bool with_shadow,
UserRecord **ret) {
- _cleanup_free_ char *buf = NULL, *sbuf = NULL;
- struct passwd pwd, *result;
+ _cleanup_free_ char *sbuf = NULL;
+ _cleanup_free_ struct passwd *result = NULL;
bool incomplete = false;
- size_t buflen = 4096;
struct spwd spwd, *sresult = NULL;
int r;
- for (;;) {
- buf = malloc(buflen);
- if (!buf)
- return -ENOMEM;
-
- r = getpwuid_r(uid, &pwd, buf, buflen, &result);
- if (r == 0) {
- if (!result)
- return -ESRCH;
-
- break;
- }
- if (r < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value");
- if (r != ERANGE)
- return -r;
-
- if (buflen > SIZE_MAX / 2)
- return -ERANGE;
-
- buflen *= 2;
- buf = mfree(buf);
- }
+ r = getpwuid_malloc(uid, &result);
+ if (r < 0)
+ return r;
if (with_shadow) {
r = nss_spwd_for_passwd(result, &spwd, &sbuf);
bool with_shadow,
GroupRecord **ret) {
- _cleanup_free_ char *buf = NULL, *sbuf = NULL;
- struct group grp, *result;
+ _cleanup_free_ char *sbuf = NULL;
+ _cleanup_free_ struct group *result = NULL;
bool incomplete = false;
- size_t buflen = 4096;
struct sgrp sgrp, *sresult = NULL;
int r;
assert(name);
- for (;;) {
- buf = malloc(buflen);
- if (!buf)
- return -ENOMEM;
-
- r = getgrnam_r(name, &grp, buf, buflen, &result);
- if (r == 0) {
- if (!result)
- return -ESRCH;
-
- break;
- }
-
- if (r < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value");
- if (r != ERANGE)
- return -r;
- if (buflen > SIZE_MAX / 2)
- return -ERANGE;
-
- buflen *= 2;
- buf = mfree(buf);
- }
+ r = getgrnam_malloc(name, &result);
+ if (r < 0)
+ return r;
if (with_shadow) {
r = nss_sgrp_for_group(result, &sgrp, &sbuf);
bool with_shadow,
GroupRecord **ret) {
- _cleanup_free_ char *buf = NULL, *sbuf = NULL;
- struct group grp, *result;
+ _cleanup_free_ char *sbuf = NULL;
+ _cleanup_free_ struct group *result = NULL;
bool incomplete = false;
- size_t buflen = 4096;
struct sgrp sgrp, *sresult = NULL;
int r;
- for (;;) {
- buf = malloc(buflen);
- if (!buf)
- return -ENOMEM;
-
- r = getgrgid_r(gid, &grp, buf, buflen, &result);
- if (r == 0) {
- if (!result)
- return -ESRCH;
- break;
- }
-
- if (r < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value");
- if (r != ERANGE)
- return -r;
- if (buflen > SIZE_MAX / 2)
- return -ERANGE;
-
- buflen *= 2;
- buf = mfree(buf);
- }
+ r = getgrgid_malloc(gid, &result);
+ if (r < 0)
+ return r;
if (with_shadow) {
r = nss_sgrp_for_group(result, &sgrp, &sbuf);
return false;
}
+int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags) {
+ JsonVariant *m;
+ int r;
+
+ assert(json_variant_is_object(entry));
+
+ m = json_variant_by_key(entry, "matchMachineId");
+ if (m) {
+ r = per_machine_id_match(m, flags);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return true;
+ }
+
+ m = json_variant_by_key(entry, "matchHostname");
+ if (m) {
+ r = per_machine_hostname_match(m, flags);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return true;
+ }
+
+ return false;
+}
+
static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch per_machine_dispatch_table[] = {
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
- bool matching = false;
- JsonVariant *m;
-
if (!json_variant_is_object(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
- m = json_variant_by_key(e, "matchMachineId");
- if (m) {
- r = per_machine_id_match(m, flags);
- if (r < 0)
- return r;
-
- matching = r > 0;
- }
-
- if (!matching) {
- m = json_variant_by_key(e, "matchHostname");
- if (m) {
- r = per_machine_hostname_match(m, flags);
- if (r < 0)
- return r;
-
- matching = r > 0;
- }
- }
-
- if (!matching)
+ r = per_machine_match(e, flags);
+ if (r < 0)
+ return r;
+ if (r == 0)
continue;
r = json_dispatch(e, per_machine_dispatch_table, flags, userdata);
int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags);
int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags);
+int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags);
int user_group_record_mangle(JsonVariant *v, UserRecordLoadFlags load_flags, JsonVariant **ret_variant, UserRecordMask *ret_mask);
const char* user_storage_to_string(UserStorage t) _const_;
_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);
}
}
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_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE));
+ VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(target, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(params, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY));
VARLINK_DEFINE_STRUCT_TYPE(
ResourceRecordArray,
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 */
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 varlink_observe(v, method, parameters);
}
-int varlink_call(
+int varlink_call_full(
Varlink *v,
const char *method,
JsonVariant *parameters,
switch (v->state) {
- case VARLINK_CALLED:
+ case VARLINK_CALLED: {
assert(v->current);
varlink_set_state(v, VARLINK_IDLE_CLIENT);
assert(v->n_pending == 1);
v->n_pending--;
+ JsonVariant *e = json_variant_by_key(v->current, "error"),
+ *p = json_variant_by_key(v->current, "parameters");
+
+ /* If caller doesn't ask for the error string, then let's return an error code in case of failure */
+ if (!ret_error_id && e)
+ return varlink_error_to_errno(json_variant_string(e), p);
+
if (ret_parameters)
- *ret_parameters = json_variant_by_key(v->current, "parameters");
+ *ret_parameters = p;
if (ret_error_id)
- *ret_error_id = json_variant_string(json_variant_by_key(v->current, "error"));
+ *ret_error_id = e ? json_variant_string(e) : NULL;
if (ret_flags)
*ret_flags = 0;
return 1;
+ }
case VARLINK_PENDING_DISCONNECT:
case VARLINK_DISCONNECTED:
}
}
-int varlink_callb(
+int varlink_callb_ap(
Varlink *v,
const char *method,
JsonVariant **ret_parameters,
const char **ret_error_id,
- VarlinkReplyFlags *ret_flags, ...) {
+ VarlinkReplyFlags *ret_flags,
+ va_list ap) {
+
+ _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+ int r;
+
+ assert_return(v, -EINVAL);
+ assert_return(method, -EINVAL);
+
+ r = json_buildv(¶meters, ap);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to build json message: %m");
+
+ return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
+}
+
+int varlink_call_and_log(
+ Varlink *v,
+ const char *method,
+ JsonVariant *parameters,
+ JsonVariant **ret_parameters) {
+
+ JsonVariant *reply = NULL;
+ const char *error_id = NULL;
+ int r;
+
+ assert_return(v, -EINVAL);
+ assert_return(method, -EINVAL);
+
+ r = varlink_call(v, method, parameters, &reply, &error_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue %s() varlink call: %m", method);
+ if (error_id)
+ return log_error_errno(varlink_error_to_errno(error_id, reply),
+ "Failed to issue %s() varlink call: %s", method, error_id);
+
+ if (ret_parameters)
+ *ret_parameters = TAKE_PTR(reply);
+
+ return 0;
+}
+
+int varlink_callb_and_log(
+ Varlink *v,
+ const char *method,
+ JsonVariant **ret_parameters,
+ ...) {
_cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
va_list ap;
int r;
assert_return(v, -EINVAL);
+ assert_return(method, -EINVAL);
- va_start(ap, ret_flags);
+ va_start(ap, ret_parameters);
r = json_buildv(¶meters, ap);
va_end(ap);
-
if (r < 0)
- return varlink_log_errno(v, r, "Failed to build json message: %m");
+ return log_error_errno(r, "Failed to build JSON message: %m");
- return varlink_call(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
+ return varlink_call_and_log(v, method, parameters, ret_parameters);
}
static void varlink_collect_context_free(VarlinkCollectContext *cc) {
/* If we hit an error, we will drop all collected replies and just return the error_id and flags in varlink_collect() */
if (error_id) {
context->error_id = error_id;
+
+ json_variant_unref(context->parameters);
+ context->parameters = json_variant_ref(parameters);
+
return 0;
}
/* If we get an error from any of the replies, return immediately with just the error_id and flags*/
if (context.error_id) {
+
+ /* If caller doesn't ask for the error string, then let's return an error code in case of failure */
+ if (!ret_error_id)
+ return varlink_error_to_errno(context.error_id, context.parameters);
+
+ if (ret_parameters)
+ *ret_parameters = TAKE_PTR(context.parameters);
if (ret_error_id)
*ret_error_id = TAKE_PTR(context.error_id);
if (ret_flags)
assert_not_reached();
}
+ if (!ret_error_id && context.error_id)
+ return varlink_error_to_errno(context.error_id, context.parameters);
+
if (ret_parameters)
*ret_parameters = TAKE_PTR(context.parameters);
if (ret_error_id)
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;
return true;
}
+
+int varlink_error_to_errno(const char *error, JsonVariant *parameters) {
+ static const struct {
+ const char *error;
+ int value;
+ } table[] = {
+ { VARLINK_ERROR_DISCONNECTED, -ECONNRESET },
+ { VARLINK_ERROR_TIMEOUT, -ETIMEDOUT },
+ { VARLINK_ERROR_PROTOCOL, -EPROTO },
+ { VARLINK_ERROR_INTERFACE_NOT_FOUND, -EADDRNOTAVAIL },
+ { VARLINK_ERROR_METHOD_NOT_FOUND, -ENXIO },
+ { VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, -ENOTTY },
+ { VARLINK_ERROR_INVALID_PARAMETER, -EINVAL },
+ { VARLINK_ERROR_PERMISSION_DENIED, -EACCES },
+ { VARLINK_ERROR_EXPECTED_MORE, -EBADE },
+ };
+
+ if (!error)
+ return 0;
+
+ FOREACH_ARRAY(t, table, ELEMENTSOF(table))
+ if (streq(error, t->error))
+ return t->value;
+
+ if (streq(error, VARLINK_ERROR_SYSTEM) && parameters) {
+ JsonVariant *e;
+
+ e = json_variant_by_key(parameters, "errno");
+ if (json_variant_is_integer(e)) {
+ int64_t i;
+
+ i = json_variant_integer(e);
+ if (i > 0 && i < ERRNO_MAX)
+ return -i;
+ }
+ }
+
+ return -EBADR; /* Catch-all */
+}
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_sendb(Varlink *v, const char *method, ...);
/* Send method call and wait for reply */
-int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
-int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...);
+int varlink_call_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
+static inline int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) {
+ return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
+}
+int varlink_call_and_log(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters);
+
+int varlink_callb_ap(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, va_list ap);
+static inline int varlink_callb_full(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, ret_flags);
+ r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, ret_flags, ap);
+ va_end(ap);
+ return r;
+}
+static inline int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, ret_error_id);
+ r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, NULL, ap);
+ va_end(ap);
+ return r;
+}
+int varlink_callb_and_log(Varlink *v, const char *method, JsonVariant **ret_parameters, ...);
/* Send method call and begin collecting all 'more' replies into an array, finishing when a final reply is sent */
int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
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);
int varlink_invocation(VarlinkInvocationFlags flags);
+int varlink_error_to_errno(const char *error, JsonVariant *parameters);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref);
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref);
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref);
},
]
-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')
+if sshconfdir != 'no'
+ 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)
+ 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'))
+ meson.add_install_script(sh, '-c',
+ ln_s.format(libexecdir / 'ssh_config.d' / '20-systemd-ssh-proxy.conf', sshconfdir / '20-systemd-ssh-proxy.conf'))
+endif
return 0;
}
- return log_debug_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
+ return log_error_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
}
r = make_sshd_template_unit(
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;
}
#define SD_MESSAGE_SYSV_GENERATOR_DEPRECATED SD_ID128_MAKE(a8,fa,8d,ac,db,1d,44,3e,95,03,b8,be,36,7a,6a,db)
#define SD_MESSAGE_SYSV_GENERATOR_DEPRECATED_STR SD_ID128_MAKE_STR(a8,fa,8d,ac,db,1d,44,3e,95,03,b8,be,36,7a,6a,db)
+#define SD_MESSAGE_PORTABLE_ATTACHED SD_ID128_MAKE(18,7c,62,eb,1e,7f,46,3b,b5,30,39,4f,52,cb,09,0f)
+#define SD_MESSAGE_PORTABLE_ATTACHED_STR SD_ID128_MAKE_STR(18,7c,62,eb,1e,7f,46,3b,b5,30,39,4f,52,cb,09,0f)
+#define SD_MESSAGE_PORTABLE_DETACHED SD_ID128_MAKE(76,c5,c7,54,d6,28,49,0d,8e,cb,a4,c9,d0,42,11,2b)
+#define SD_MESSAGE_PORTABLE_DETACHED_STR SD_ID128_MAKE_STR(76,c5,c7,54,d6,28,49,0d,8e,cb,a4,c9,d0,42,11,2b)
+
_SD_END_DECLARATIONS;
#endif
int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret);
int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret);
int sd_ndisc_router_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret);
+int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret);
int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret);
/* Generic option access */
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);
uid_range_free(c->uid_range);
}
-static int errno_is_not_exists(int code) {
- /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
- * not found. */
- return IN_SET(code, 0, ENOENT, ESRCH, EBADF, EPERM);
-}
-
/* Note: the lifetime of the compound literal is the immediately surrounding block,
* see C11 §6.5.2.5, and
* https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */
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);
const char *name,
bool check_with_gid) {
+ int r;
assert(c);
/* Let's see if we already have assigned the UID a second time */
/* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
if (!arg_root) {
- struct passwd *p;
- struct group *g;
+ _cleanup_free_ struct group *g = NULL;
- errno = 0;
- p = getpwuid(uid);
- if (p)
+ r = getpwuid_malloc(uid, /* ret= */ NULL);
+ if (r >= 0)
return 0;
- if (!IN_SET(errno, 0, ENOENT))
- return -errno;
+ if (r != -ESRCH)
+ return r;
if (check_with_gid) {
- errno = 0;
- g = getgrgid((gid_t) uid);
- if (g) {
+ r = getgrgid_malloc((gid_t) uid, &g);
+ if (r >= 0) {
if (!streq(g->gr_name, name))
return 0;
- } else if (!IN_SET(errno, 0, ENOENT))
- return -errno;
+ } else if (r != -ESRCH)
+ return r;
}
}
}
if (!arg_root) {
- struct passwd *p;
+ _cleanup_free_ struct passwd *p = NULL;
/* Also check NSS */
- errno = 0;
- p = getpwnam(i->name);
- if (p) {
+ r = getpwnam_malloc(i->name, &p);
+ if (r >= 0) {
log_debug("User %s already exists.", i->name);
i->uid = p->pw_uid;
i->uid_set = true;
return 0;
}
- if (!errno_is_not_exists(errno))
- return log_error_errno(errno, "Failed to check if user %s already exists: %m", i->name);
+ if (r != -ESRCH)
+ return log_error_errno(r, "Failed to check if user %s already exists: %m", i->name);
}
/* Try to use the suggested numeric UID */
const char *groupname,
bool check_with_uid) {
- struct group *g;
- struct passwd *p;
Item *user;
char *username;
+ int r;
assert(c);
assert(groupname);
}
if (!arg_root) {
- errno = 0;
- g = getgrgid(gid);
- if (g)
+ r = getgrgid_malloc(gid, /* ret= */ NULL);
+ if (r >= 0)
return 0;
- if (!IN_SET(errno, 0, ENOENT))
- return -errno;
+ if (r != -ESRCH)
+ return r;
if (check_with_uid) {
- errno = 0;
- p = getpwuid((uid_t) gid);
- if (p)
+ r = getpwuid_malloc(gid, /* ret= */ NULL);
+ if (r >= 0)
return 0;
- if (!IN_SET(errno, 0, ENOENT))
- return -errno;
+ if (r != -ESRCH)
+ return r;
}
}
gid_t *ret_gid) {
void *z;
+ int r;
assert(c);
assert(ret_gid);
/* Also check NSS */
if (!arg_root) {
- struct group *g;
+ _cleanup_free_ struct group *g = NULL;
- errno = 0;
- g = getgrnam(name);
- if (g) {
+ r = getgrnam_malloc(name, &g);
+ if (r >= 0) {
*ret_gid = g->gr_gid;
return 0;
}
- if (!errno_is_not_exists(errno))
- return log_error_errno(errno, "Failed to check if group %s already exists: %m", name);
+ if (r != -ESRCH)
+ return log_error_errno(r, "Failed to check if group %s already exists: %m", name);
}
return -ENOENT;
size_t i;
int *p;
- dup = memdup_suffix0_multiply(org, sizeof(int), 3);
+ dup = memdup_suffix0_multiply(org, 3, sizeof(int));
assert_se(dup);
assert_se(dup[0] == 1);
assert_se(dup[1] == 2);
assert_se(((uint8_t*) dup)[sizeof(int) * 3] == 0);
free(dup);
- dup = memdup_multiply(org, sizeof(int), 3);
+ dup = memdup_multiply(org, 3, sizeof(int));
assert_se(dup);
assert_se(dup[0] == 1);
assert_se(dup[1] == 2);
#include "creds-util.h"
#include "fileio.h"
+#include "format-util.h"
+#include "hexdecoct.h"
#include "id128-util.h"
#include "iovec-util.h"
#include "path-util.h"
if (ec)
assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true) >= 0);
+}
+
+TEST(mime_type_matches) {
+
+ static const sd_id128_t tags[] = {
+ CRED_AES256_GCM_BY_HOST,
+ CRED_AES256_GCM_BY_TPM2_HMAC,
+ 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_NULL,
+ };
+
+ /* Generates the right <match/> expressions for these credentials according to the shared mime-info spec */
+ FOREACH_ARRAY(t, tags, ELEMENTSOF(tags)) {
+ _cleanup_free_ char *encoded = NULL;
+ assert_se(base64mem(t, sizeof(sd_id128_t), &encoded) >= 0);
+
+ /* Validate that the size matches expectations for the 4/3 factor size increase (rounding up) */
+ assert_se(strlen(encoded) == DIV_ROUND_UP((128U / 8U), 3U) * 4U);
+
+ /* Cut off rounded string where the ID ends, but now round down to get rid of characters that might contain follow-up data */
+ encoded[128 / 6] = 0;
+
+ printf("<match type=\"string\" value=\"%s\" offset=\"0\"/>\n", encoded);
+ }
}
DEFINE_TEST_MAIN(LOG_INFO);
test_octescape_one("\123\213\222", "\123\\213\\222");
}
+static void test_decescape_one(const char *s, const char *bad, const char *expected) {
+ _cleanup_free_ char *ret = NULL;
+
+ assert_se(ret = decescape(s, bad, strlen_ptr(s)));
+ log_debug("decescape(\"%s\") → \"%s\" (expected: \"%s\")", strnull(s), ret, expected);
+ assert_se(streq(ret, expected));
+}
+
+TEST(decescape) {
+ test_decescape_one(NULL, "bad", "");
+ test_decescape_one("foo", "", "foo");
+ test_decescape_one("foo", "f", "\\102oo");
+ test_decescape_one("foo", "o", "f\\111\\111");
+ test_decescape_one("go\"bb\\ledyg\x03ook\r\n", "", "go\\034bb\\092ledyg\\003ook\\013\\010");
+ test_decescape_one("\\xff\xff" "f", "f", "\\092x\\102\\102\\255\\102");
+ test_decescape_one("all", "all", "\\097\\108\\108");
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
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);
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) {
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
JsonVariant *reply = NULL;
const char *error_id = NULL;
- assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
+ assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("foo", 8),
JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0);
json_variant_dump(expected_reply, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
assert_se(json_variant_equal(reply, expected_reply));
- assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
+ assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("foo", 9),
JSON_BUILD_PAIR_UNSIGNED("bar", 8),
assert_se(!error_id);
assert_se(json_variant_equal(reply, expected_reply));
- assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
+ assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("foo", 8),
JSON_BUILD_PAIR_UNSIGNED("bar", 9),
JSON_BUILD_PAIR_STRING("zzz", "pfft"))) >= 0);
assert_se(streq_ptr(error_id, VARLINK_ERROR_INVALID_PARAMETER));
- assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
+ assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("foo", "wuff"),
JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0);
}
assert_se(x == 6);
- assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e, NULL) >= 0);
+ assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e) >= 0);
assert_se(json_variant_integer(json_variant_by_key(o, "sum")) == 88 + 99);
assert_se(!e);
assert_se(varlink_push_fd(c, fd2) == 1);
assert_se(varlink_push_fd(c, fd3) == 2);
- assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("fd", JSON_BUILD_STRING("whoop")))) >= 0);
+ assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("fd", JSON_BUILD_STRING("whoop")))) >= 0);
int fd4 = varlink_peek_fd(c, 0);
int fd5 = varlink_peek_fd(c, 1);
test_fd(fd4, "miau", 4);
test_fd(fd5, "wuff", 4);
- assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0);
+ assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0);
assert_se(streq_ptr(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist"));
assert_se(streq(e, VARLINK_ERROR_METHOD_NOT_FOUND));
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;
* 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;
}
* 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);
@classmethod
def scrape_x86(cls, filename, opts=None):
# Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L136
- # and https://www.kernel.org/doc/html/latest/x86/boot.html#the-real-mode-kernel-header
+ # and https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header
with open(filename, 'rb') as f:
f.seek(0x202)
magic = f.read(4)
# 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,
},
]
-custom_target(
- '20-systemd-userdb.conf',
- input : '20-systemd-userdb.conf.in',
- output : '20-systemd-userdb.conf',
- command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
- install : conf.get('ENABLE_USERDB') == 1 and sshdconfdir != 'no',
- install_dir : libexecdir / 'sshd_config.d')
+if conf.get('ENABLE_USERDB') == 1 and sshdconfdir != 'no'
+ custom_target(
+ '20-systemd-userdb.conf',
+ input : '20-systemd-userdb.conf.in',
+ output : '20-systemd-userdb.conf',
+ command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
+ install : true,
+ install_dir : libexecdir / 'sshd_config.d')
-install_emptydir(sshdconfdir)
+ install_emptydir(sshdconfdir)
-meson.add_install_script(sh, '-c',
- ln_s.format(libexecdir / 'sshd_config.d' / '20-systemd-userdb.conf', sshdconfdir / '20-systemd-userdb.conf'))
+ meson.add_install_script(sh, '-c',
+ ln_s.format(libexecdir / 'sshd_config.d' / '20-systemd-userdb.conf', sshdconfdir / '20-systemd-userdb.conf'))
+endif
return r;
JsonVariant *reply = NULL;
- const char *error = NULL;
- r = varlink_call(vl, "org.varlink.service.GetInfo", NULL, &reply, &error, NULL);
+ r = varlink_call_and_log(vl, "org.varlink.service.GetInfo", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue GetInfo() call: %m");
- if (error)
- return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInfo() failed: %s", error);
+ return r;
pager_open(arg_pager_flags);
return r;
JsonVariant *reply = NULL;
- const char *error = NULL;
- r = varlink_callb(vl, "org.varlink.service.GetInterfaceDescription", &reply, &error, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface)));
+ r = varlink_callb_and_log(
+ vl,
+ "org.varlink.service.GetInterfaceDescription",
+ &reply,
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface)));
if (r < 0)
- return log_error_errno(r, "Failed to issue GetInterfaceDescription() call: %m");
- if (error)
- return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInterfaceDescription() failed: %s", error);
+ return r;
pager_open(arg_pager_flags);
JsonVariant *reply = NULL;
const char *error = NULL;
- r = varlink_call(vl, method, jp, &reply, &error, NULL);
+ r = varlink_call(vl, method, jp, &reply, &error);
if (r < 0)
return log_error_errno(r, "Failed to issue %s() call: %m", method);
return NULL;
free(config->path);
+ free(config->format);
free(config->vars);
+ free(config->vars_format);
return mfree(config);
}
log_debug_errno(errno, "/dev/kvm not found. Not using KVM acceleration.");
return false;
}
- if (errno == EPERM) {
+ if (ERRNO_IS_PRIVILEGE(errno)) {
log_debug_errno(errno, "Permission denied to access /dev/kvm. Not using KVM acceleration.");
return false;
}
fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC);
if (fd >= 0)
return true;
- if (errno == ENODEV) {
+ if (ERRNO_IS_DEVICE_ABSENT(errno)) {
log_debug_errno(errno, "/dev/vhost-vsock device doesn't exist. Not adding a vsock device to the virtual machine.");
return false;
}
- if (errno == EPERM) {
+ if (ERRNO_IS_PRIVILEGE(errno)) {
log_debug_errno(errno, "Permission denied to access /dev/vhost-vsock. Not adding a vsock device to the virtual machine.");
return false;
}
typedef struct FirmwareData {
char **features;
char *firmware;
+ char *firmware_format;
char *vars;
+ char *vars_format;
} FirmwareData;
+static bool firmware_data_supports_sb(const FirmwareData *fwd) {
+ assert(fwd);
+
+ return strv_contains(fwd->features, "secure-boot");
+}
+
static FirmwareData* firmware_data_free(FirmwareData *fwd) {
if (!fwd)
return NULL;
- fwd->features = strv_free(fwd->features);
- fwd->firmware = mfree(fwd->firmware);
- fwd->vars = mfree(fwd->vars);
+ strv_free(fwd->features);
+ free(fwd->firmware);
+ free(fwd->firmware_format);
+ free(fwd->vars);
+ free(fwd->vars_format);
return mfree(fwd);
}
static int firmware_executable(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch table[] = {
- { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY },
- { "format", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY },
+ { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY },
+ { "format", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware_format), JSON_MANDATORY },
{}
};
- 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) {
static const JsonDispatch table[] = {
- { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY },
- { "format", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY },
+ { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY },
+ { "format", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars_format), JSON_MANDATORY },
{}
};
- 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);
+}
+
+static int get_firmware_search_dirs(char ***ret) {
+ int r;
+
+ assert(ret);
+
+ /* Search in:
+ * - $XDG_CONFIG_HOME/qemu/firmware
+ * - /etc/qemu/firmware
+ * - /usr/share/qemu/firmware
+ *
+ * Prioritising entries in "more specific" directories */
+
+ _cleanup_free_ char *user_firmware_dir = NULL;
+ r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware");
+ if (r < 0)
+ return r;
+
+ _cleanup_strv_free_ char **l = NULL;
+ l = strv_new(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware");
+ if (!l)
+ return log_oom_debug();
+
+ *ret = TAKE_PTR(l);
+ return 0;
+}
+
+int list_ovmf_config(char ***ret) {
+ _cleanup_strv_free_ char **search_dirs = NULL;
+ int r;
+
+ assert(ret);
+
+ r = get_firmware_search_dirs(&search_dirs);
+ if (r < 0)
+ return r;
+
+ r = conf_files_list_strv(
+ ret,
+ ".json",
+ /* root= */ NULL,
+ CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR,
+ (const char *const*) search_dirs);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to list firmware files: %m");
+
+ return 0;
+}
+
+static int load_firmware_data(const char *path, FirmwareData **ret) {
+ int r;
+
+ assert(path);
+ assert(ret);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *json = NULL;
+ r = json_parse_file(
+ /* f= */ NULL,
+ path,
+ /* flags= */ 0,
+ &json,
+ /* ret_line= */ NULL,
+ /* ret_column= */ NULL);
+ if (r < 0)
+ return r;
+
+ static const JsonDispatch table[] = {
+ { "description", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY },
+ { "interface-types", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY },
+ { "mapping", JSON_VARIANT_OBJECT, firmware_mapping, 0, JSON_MANDATORY },
+ { "targets", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY },
+ { "features", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY },
+ { "tags", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY },
+ {}
+ };
+
+ _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
+ fwd = new0(FirmwareData, 1);
+ if (!fwd)
+ return -ENOMEM;
+
+ r = json_dispatch(json, table, JSON_ALLOW_EXTENSIONS, fwd);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(fwd);
+ return 0;
+}
+
+static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) {
+ assert(fwd);
+ assert(ret);
+
+ _cleanup_free_ OvmfConfig *config = NULL;
+ config = new(OvmfConfig, 1);
+ if (!config)
+ return -ENOMEM;
+
+ *config = (OvmfConfig) {
+ .path = TAKE_PTR(fwd->firmware),
+ .format = TAKE_PTR(fwd->firmware_format),
+ .vars = TAKE_PTR(fwd->vars),
+ .vars_format = TAKE_PTR(fwd->vars_format),
+ .supports_sb = firmware_data_supports_sb(fwd),
+ };
+
+ *ret = TAKE_PTR(config);
+ return 0;
+}
+
+int load_ovmf_config(const char *path, OvmfConfig **ret) {
+ _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
+ int r;
+
+ assert(path);
+ assert(ret);
+
+ r = load_firmware_data(path, &fwd);
+ if (r < 0)
+ return r;
+
+ return ovmf_config_make(fwd, ret);
}
int find_ovmf_config(int search_sb, OvmfConfig **ret) {
_cleanup_(ovmf_config_freep) OvmfConfig *config = NULL;
- _cleanup_free_ char *user_firmware_dir = NULL;
_cleanup_strv_free_ char **conf_files = NULL;
int r;
+ assert(ret);
+
/* Search in:
* - $XDG_CONFIG_HOME/qemu/firmware
* - /etc/qemu/firmware
* Prioritising entries in "more specific" directories
*/
- r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware");
+ r = list_ovmf_config(&conf_files);
if (r < 0)
return r;
- r = conf_files_list_strv(&conf_files, ".json", NULL, CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR,
- STRV_MAKE_CONST(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware"));
- if (r < 0)
- return log_debug_errno(r, "Failed to list config files: %m");
-
STRV_FOREACH(file, conf_files) {
_cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
- _cleanup_(json_variant_unrefp) JsonVariant *config_json = NULL;
- _cleanup_free_ char *contents = NULL;
- size_t contents_sz = 0;
-
- r = read_full_file(*file, &contents, &contents_sz);
- if (r == -ENOMEM)
- return r;
- if (r < 0) {
- log_debug_errno(r, "Failed to read contents of %s - ignoring: %m", *file);
- continue;
- }
- r = json_parse(contents, 0, &config_json, NULL, NULL);
- if (r == -ENOMEM)
- return r;
+ r = load_firmware_data(*file, &fwd);
if (r < 0) {
- log_debug_errno(r, "Failed to parse the JSON in %s - ignoring: %m", *file);
+ log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file);
continue;
}
- static const JsonDispatch table[] = {
- { "description", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY },
- { "interface-types", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY },
- { "mapping", JSON_VARIANT_OBJECT, firmware_mapping, 0, JSON_MANDATORY },
- { "targets", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY },
- { "features", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY },
- { "tags", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY },
- {}
- };
-
- fwd = new0(FirmwareData, 1);
- if (!fwd)
- return -ENOMEM;
-
- r = json_dispatch(config_json, table, 0, fwd);
- if (r == -ENOMEM)
- return r;
- if (r < 0) {
- log_debug_errno(r, "Failed to extract the required fields from the JSON in %s - ignoring: %m", *file);
+ if (strv_contains(fwd->features, "enrolled-keys")) {
+ log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues.", *file);
continue;
}
- int sb_present = !!strv_find(fwd->features, "secure-boot");
-
/* exclude firmware which doesn't match our Secure Boot requirements */
- if (search_sb >= 0 && search_sb != sb_present) {
- log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration", *file);
+ if (search_sb >= 0 && !!search_sb != firmware_data_supports_sb(fwd)) {
+ log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration.", *file);
continue;
}
- config = new0(OvmfConfig, 1);
- if (!config)
- return -ENOMEM;
+ r = ovmf_config_make(fwd, &config);
+ if (r < 0)
+ return r;
- config->path = TAKE_PTR(fwd->firmware);
- config->vars = TAKE_PTR(fwd->vars);
- config->supports_sb = sb_present;
+ log_debug("Selected firmware definition %s.", *file);
break;
}
#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 *format;
char *vars;
+ char *vars_format;
bool supports_sb;
} OvmfConfig;
+static inline const char *ovmf_config_format(const OvmfConfig *c) {
+ return ASSERT_PTR(c)->format ?: "raw";
+}
+
+static inline const char *ovmf_config_vars_format(const OvmfConfig *c) {
+ return ASSERT_PTR(c)->vars_format ?: "raw";
+}
+
OvmfConfig* ovmf_config_free(OvmfConfig *ovmf_config);
DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free);
int qemu_check_kvm_support(void);
int qemu_check_vsock_support(void);
-int find_ovmf_config(int search_sb, OvmfConfig **ret_ovmf_config);
+int list_ovmf_config(char ***ret);
+int load_ovmf_config(const char *path, OvmfConfig **ret);
+int find_ovmf_config(int search_sb, OvmfConfig **ret);
int find_qemu_binary(char **ret_qemu_binary);
int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock);
#include "copy.h"
#include "creds-util.h"
#include "escape.h"
+#include "event-util.h"
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
#include "vmspawn-settings.h"
#include "vmspawn-util.h"
+static bool arg_quiet = false;
static PagerFlags arg_pager_flags = 0;
static char *arg_image = NULL;
static char *arg_machine = NULL;
static char *arg_qemu_smp = NULL;
-static uint64_t arg_qemu_mem = 2ULL * 1024ULL * 1024ULL * 1024ULL;
+static uint64_t arg_qemu_mem = UINT64_C(2) * U64_GB;
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 = {};
static SettingsMask arg_settings_mask = 0;
static char **arg_parameters = NULL;
+static char *arg_firmware = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_machine, freep);
STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep);
STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
+STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
"%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"
+ " -q --quiet Do not show status information\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"
+ " --firmware=PATH|list Select firmware definition file (or list available)\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"
ARG_SECURE_BOOT,
ARG_SET_CREDENTIAL,
ARG_LOAD_CREDENTIAL,
+ ARG_FIRMWARE,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
+ { "quiet", no_argument, NULL, 'q' },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "image", required_argument, NULL, 'i' },
{ "machine", required_argument, NULL, 'M' },
{ "secure-boot", required_argument, NULL, ARG_SECURE_BOOT },
{ "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
+ { "firmware", required_argument, NULL, ARG_FIRMWARE },
{}
};
assert(argv);
optind = 0;
- while ((c = getopt_long(argc, argv, "+hi:M", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "+hi:Mq", options, NULL)) >= 0)
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
+ case 'q':
+ arg_quiet = true;
+ break;
+
case 'i':
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
if (r < 0)
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;
break;
}
+ case ARG_FIRMWARE:
+ if (streq(optarg, "list")) {
+ _cleanup_strv_free_ char **l = NULL;
+
+ r = list_ovmf_config(&l);
+ if (r < 0)
+ return log_error_errno(r, "Failed to list firmwares: %m");
+
+ bool nl = false;
+ fputstrv(stdout, l, "\n", &nl);
+ if (nl)
+ putchar('\n');
+
+ return 0;
+ }
+
+ if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./"))
+ return log_error_errno(SYNTHETIC_ERRNO(errno), "Absolute path or path starting with './' required.");
+
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware);
+ if (r < 0)
+ return r;
+
+ break;
+
case '?':
return -EINVAL;
return 0;
}
-static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_event_source **notify_event_source) {
+static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_event_source **ret_notify_event_source) {
int r;
- r = sd_event_add_io(event, notify_event_source, fd, EPOLLIN, vmspawn_dispatch_vsock_connections, exit_status);
+ assert(event);
+ assert(fd >= 0);
+ assert(exit_status);
+ assert(ret_notify_event_source);
+
+ r = sd_event_add_io(event, ret_notify_event_source, fd, EPOLLIN, vmspawn_dispatch_vsock_connections, exit_status);
if (r < 0)
return log_error_errno(r, "Failed to allocate notify socket event source: %m");
- (void) sd_event_source_set_description(*notify_event_source, "vmspawn-notify-sock");
+ (void) sd_event_source_set_description(*ret_notify_event_source, "vmspawn-notify-sock");
return 0;
}
static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
- pid_t pid;
+ PidRef *pidref = userdata;
+ int r;
+
+ /* TODO: actually talk to qemu and ask the guest to shutdown here */
- pid = PTR_TO_PID(userdata);
- if (pid > 0) {
- /* TODO: actually talk to qemu and ask the guest to shutdown here */
- if (kill(pid, SIGKILL) >= 0) {
+ if (pidref) {
+ r = pidref_kill(pidref, SIGKILL);
+ if (r < 0)
+ log_warning_errno(r, "Failed to kill qemu, terminating: %m");
+ else {
log_info("Trying to halt qemu. Send SIGTERM again to trigger vmspawn to immediately terminate.");
sd_event_source_set_userdata(s, NULL);
return 0;
use_kvm = r;
}
- r = find_ovmf_config(arg_secure_boot, &ovmf_config);
+ if (arg_firmware)
+ r = load_ovmf_config(arg_firmware, &ovmf_config);
+ else
+ r = find_ovmf_config(arg_secure_boot, &ovmf_config);
if (r < 0)
return log_error_errno(r, "Failed to find OVMF config: %m");
if (r < 0)
return log_error_errno(r, "Failed to find QEMU binary: %m");
- if (asprintf(&mem, "%.4fM", (double)arg_qemu_mem / (1024.0 * 1024.0)) < 0)
+ if (asprintf(&mem, "%" PRIu64, DIV_ROUND_UP(arg_qemu_mem, U64_MB)) < 0)
return log_oom();
cmdline = strv_new(
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) {
if (r < 0)
return log_oom();
- r = strv_extendf(&cmdline, "if=pflash,format=raw,readonly=on,file=%s", ovmf_config->path);
+ r = strv_extendf(&cmdline, "if=pflash,format=%s,readonly=on,file=%s", ovmf_config_format(ovmf_config), ovmf_config->path);
if (r < 0)
return log_oom();
(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();
- r = strv_extendf(&cmdline, "file=%s,if=pflash,format=raw", ovmf_vars_to);
+ r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", ovmf_vars_to, ovmf_config_format(ovmf_config));
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();
return log_error_errno(r, "Failed to call getsockname on vsock: %m");
}
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *joined = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY);
+ if (!joined)
+ return log_oom();
+
+ log_debug("Executing: %s", joined);
+ }
+
_cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
r = sd_event_new(&event);
(void) sd_event_set_watchdog(event, true);
- pid_t child_pid;
- r = safe_fork_full(
+ _cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL;
+
+ r = pidref_safe_fork_full(
qemu_binary,
- NULL,
+ /* stdio_fds= */ NULL,
&child_vsock_fd, 1, /* pass the vsock fd to qemu */
- FORK_CLOEXEC_OFF,
- &child_pid);
+ FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE,
+ &child_pidref);
if (r < 0)
- return log_error_errno(r, "Failed to fork off %s: %m", qemu_binary);
+ return r;
if (r == 0) {
/* set TERM and LANG if they are missing */
if (setenv("TERM", "vt220", 0) < 0)
if (setenv("LANG", "C.UTF-8", 0) < 0)
return log_oom();
- execve(qemu_binary, cmdline, environ);
+ execv(qemu_binary, cmdline);
log_error_errno(errno, "Failed to execve %s: %m", qemu_binary);
_exit(EXIT_FAILURE);
}
+ /* Close the vsock fd we passed to qemu in the parent. We don't need it anymore. */
+ child_vsock_fd = safe_close(child_vsock_fd);
int exit_status = INT_MAX;
if (use_vsock) {
}
/* 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, &child_pidref);
+ (void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
- (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);
+ (void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL);
r = sd_event_loop(event);
if (r < 0)
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);
+ if (!arg_quiet) {
+ _cleanup_free_ char *u = NULL;
+ (void) terminal_urlify_path(arg_image, arg_image, &u);
+
+ log_info("%s %sSpawning VM %s on %s.%s\n"
+ "%s %sPress %sCtrl-a x%s to kill VM.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: arg_image, ansi_normal(),
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
+ }
+
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
return run_virtual_machine();
}
local workspace="${1:?}"
local container="$workspace/testsuite-13-container-template"
+ # For virtual wlan interface.
+ instmods mac80211_hwsim
+ generate_module_dependencies
+
# Create a dummy container "template" with a minimal toolset, which we can
# then use as a base for our nspawn/machinectl tests
initdir="$container" setup_basic_dirs
Key=
Kind=
L2MissNotification=
+L3MasterDevice=
L3MissNotification=
LACPTransmitRate=
LLDP=
onlinesign NS ns1.unsigned
signed NS ns1.unsigned
unsigned NS ns1.unsigned
+
+svcb SVCB 1 . alpn=dot ipv4hint=10.0.0.1 ipv6hint=fd00:dead:beef:cafe::1
+https HTTPS 1 . alpn="h2,h3"
--- /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
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[NetDev]
+Name=test25
+Kind=dummy
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test1
+
+[Network]
+IPv6AcceptRA=no
+
+[RoutingPolicyRule]
+Priority=1500
+L3MasterDevice=true
+
+[RoutingPolicyRule]
+Priority=2000
+L3MasterDevice=true
+Type=unreachable
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=0
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=3
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=4
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=infinity
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=-2
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=999999999999999999
--- /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
# These tests can be executed in the systemd mkosi image when booted in QEMU. After booting the QEMU VM,
# simply run this file which can be found in the VM at /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py.
+#
+# To run an individual test, specify it as a command line argument in the form
+# of <class>.<test_function>. E.g. the NetworkdMTUTests class has a test
+# function called test_ipv6_mtu(). To run just that test use:
+#
+# sudo ./systemd-networkd-tests.py NetworkdMTUTests.test_ipv6_mtu
+#
+# Similarly, other indivdual tests can be run, eg.:
+#
+# sudo ./systemd-networkd-tests.py NetworkdNetworkTests.test_ipv6_neigh_retrans_time
import argparse
import datetime
import json
import os
import pathlib
+import random
import re
import shutil
import signal
return f
+def expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable():
+ def f(func):
+ rc = call_quiet('ip rule add not from 192.168.100.19 l3mdev')
+ call_quiet('ip rule del not from 192.168.100.19 l3mdev')
+ return func if rc == 0 else unittest.expectedFailure(func)
+
+ return f
+
def expectedFailureIfNexthopIsNotAvailable():
def f(func):
rc = call_quiet('ip nexthop list')
with open(os.path.join('/proc/sys/net', ipv, 'conf', link, attribute), encoding='utf-8') as f:
return f.readline().strip()
+def read_ip_neigh_sysctl_attr(link, attribute, ipv):
+ with open(os.path.join('/proc/sys/net', ipv, 'neigh', link, attribute), encoding='utf-8') as f:
+ return f.readline().strip()
+
def read_ipv6_sysctl_attr(link, attribute):
return read_ip_sysctl_attr(link, attribute, 'ipv6')
+def read_ipv6_neigh_sysctl_attr(link, attribute):
+ return read_ip_neigh_sysctl_attr(link, attribute, 'ipv6')
+
def read_ipv4_sysctl_attr(link, attribute):
return read_ip_sysctl_attr(link, attribute, 'ipv4')
def check_ipv6_sysctl_attr(self, link, attribute, expected):
self.assertEqual(read_ipv6_sysctl_attr(link, attribute), expected)
+ def check_ipv6_neigh_sysctl_attr(self, link, attribute, expected):
+ self.assertEqual(read_ipv6_neigh_sysctl_attr(link, attribute), expected)
+
def wait_links(self, *links, timeout=20, fail_assert=True):
def links_exist(*links):
for link in links:
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):
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.assertRegex(output, 'tcp')
self.assertRegex(output, 'lookup 7')
+ @expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable()
+ def test_routing_policy_rule_l3mdev(self):
+ copy_network_unit('25-fibrule-l3mdev.network', '11-dummy.netdev')
+ start_networkd()
+ self.wait_online(['test1:degraded'])
+
+ output = check_output('ip rule')
+ print(output)
+ self.assertIn('1500: from all lookup [l3mdev-table]', output)
+ self.assertIn('2000: from all lookup [l3mdev-table] unreachable', output)
+
@expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable()
def test_routing_policy_rule_uidrange(self):
copy_network_unit('25-fibrule-uidrange.network', '11-dummy.netdev')
for i in range(1, 5):
self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy')
+ def test_ipv6_neigh_retrans_time(self):
+ link='test25'
+ copy_network_unit('25-dummy.netdev', '25-dummy.network')
+ start_networkd()
+
+ self.wait_online([f'{link}:degraded'])
+ remove_network_unit('25-dummy.network')
+
+ # expect retrans_time_ms updated
+ copy_network_unit('25-ipv6-neigh-retrans-time-3s.network')
+ networkctl_reload()
+ self.wait_online([f'{link}:degraded'])
+ self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+ remove_network_unit('25-ipv6-neigh-retrans-time-3s.network')
+
+ # expect retrans_time_ms unchanged
+ copy_network_unit('25-ipv6-neigh-retrans-time-0s.network')
+ networkctl_reload()
+ self.wait_online([f'{link}:degraded'])
+ self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+ remove_network_unit('25-ipv6-neigh-retrans-time-0s.network')
+
+ # expect retrans_time_ms unchanged
+ copy_network_unit('25-ipv6-neigh-retrans-time-toobig.network')
+ networkctl_reload()
+ self.wait_online([f'{link}:degraded'])
+ self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+ remove_network_unit('25-ipv6-neigh-retrans-time-toobig.network')
+
+ # expect retrans_time_ms unchanged
+ copy_network_unit('25-ipv6-neigh-retrans-time-infinity.network')
+ networkctl_reload()
+ self.wait_online([f'{link}:degraded'])
+ self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+ remove_network_unit('25-ipv6-neigh-retrans-time-infinity.network')
+
+ # expect retrans_time_ms unchanged
+ copy_network_unit('25-ipv6-neigh-retrans-time-invalid.network')
+ networkctl_reload()
+ self.wait_online([f'{link}:degraded'])
+ self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000')
+ remove_network_unit('25-ipv6-neigh-retrans-time-invalid.network')
+
+ # expect retrans_time_ms updated
+ copy_network_unit('25-ipv6-neigh-retrans-time-4s.network')
+ networkctl_reload()
+ self.wait_online([f'{link}:degraded'])
+ self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '4000')
+ remove_network_unit('25-ipv6-neigh-retrans-time-4s.network')
+
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',
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()
+
+ # 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.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 = networkctl_status('veth99')
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()
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):
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Path]
+PathChanged=/tmp/copyme
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+ExecStart=cp -v /tmp/copyme /tmp/copied
+# once cp exits, service goes into deactivating state and then runs ExecStop
+ExecStop=flock -e /tmp/noexit true
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Make sure that we never mistake a process starting but failing quickly for a process failing to start, with Type=exec.
+# See https://github.com/systemd/systemd/pull/30799
+
+seq 25 | xargs -n 1 -P 0 systemd-run -p Type=exec /bin/false
}
nspawn_settings_cleanup() {
- for dev in sd-host-only sd-shared{1,2} sd-macvlan{1,2} sd-ipvlan{1,2}; do
+ for dev in sd-host-only sd-shared{1,2,3} sd-macvlan{1,2} sd-ipvlan{1,2}; do
ip link del "$dev" || :
done
}
testcase_nspawn_settings() {
- local root container dev private_users
+ local root container dev private_users wlan_names='' wlan_checks=''
mkdir -p /run/systemd/nspawn
root="$(mktemp -d /var/lib/machines/testsuite-13.nspawn-settings.XXX)"
rm -f "/etc/systemd/nspawn/$container.nspawn"
mkdir -p "$root/tmp" "$root"/opt/{tmp,inaccessible,also-inaccessible}
- for dev in sd-host-only sd-shared{1,2} sd-macvlan{1,2} sd-macvlanloong sd-ipvlan{1,2} sd-ipvlanlooong; do
+ # add virtual wlan interfaces
+ if modprobe mac80211_hwsim radios=2; then
+ wlan_names='wlan0 wlan1:wl-renamed1'
+ wlan_checks='ip link | grep wlan0\nip link | grep wl-renamed1'
+ fi
+
+ for dev in sd-host-only sd-shared{1,2,3} sd-macvlan{1,2} sd-macvlanloong sd-ipvlan{1,2} sd-ipvlanlooong; do
ip link add "$dev" type dummy
done
udevadm settle
+ ip link property add dev sd-shared3 altname sd-altname3 altname sd-altname-tooooooooooooo-long
ip link
trap nspawn_settings_cleanup RETURN
VirtualEthernet=yes
VirtualEthernetExtra=my-fancy-veth1
VirtualEthernetExtra=fancy-veth2:my-fancy-veth2
-Interface=sd-shared1 sd-shared2:sd-shared2
+Interface=sd-shared1 sd-shared2:sd-renamed2 sd-shared3:sd-altname3 ${wlan_names}
MACVLAN=sd-macvlan1 sd-macvlan2:my-macvlan2 sd-macvlanloong
IPVLAN=sd-ipvlan1 sd-ipvlan2:my-ipvlan2 sd-ipvlanlooong
Zone=sd-zone0
ip link | grep my-fancy-veth1@
ip link | grep my-fancy-veth2@
ip link | grep sd-shared1
-ip link | grep sd-shared2
+ip link | grep sd-renamed2
+ip link | grep sd-shared3
+ip link | grep sd-altname3
+ip link | grep sd-altname-tooooooooooooo-long
ip link | grep mv-sd-macvlan1@
ip link | grep my-macvlan2@
ip link | grep iv-sd-ipvlan1@
ip link | grep my-ipvlan2@
EOF
+ echo -e "$wlan_checks" >>"$root/entrypoint.sh"
+
timeout 30 systemd-nspawn --directory="$root"
# And now for stuff that needs to run separately
systemctl stop getty@tty2.service
- for s in $(loginctl --no-legend list-sessions | grep tty | 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 tty | 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 tty | 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 | grep tty | 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 | grep tty | 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 | grep tty | 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 tty | 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 | grep tty | 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 | grep tty | 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 | grep tty | 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 | grep tty | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)"
- seat=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $4 }')
- assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $5 }')" tty2
- assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $6 }')" active
- assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $7 }')" no
- assert_eq "$(loginctl list-sessions --no-legend | grep tty | 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?}"
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 tty | grep -c "logind-test-user")" 0
+ assert_eq "$(loginctl --no-legend | grep -v manager | grep -c "logind-test-user")" 0
}
testcase_ambient_caps() {
# 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 \
assert_not_in "test63-issue-24577.service" "$output"
assert_in "test63-issue-24577-dep.service" "$output"
+# Test for race condition fixed by https://github.com/systemd/systemd/pull/30768
+# Here's the schedule of events that we to happen during this test:
+# (This test) (The service)
+# .path unit monitors /tmp/copyme for changes
+# Take lock on /tmp/noexeit ↓
+# Write to /tmp/copyme ↓
+# Wait for deactivating Started
+# ↓ Copies /tmp/copyme to /tmp/copied
+# ↓ Tells manager it's shutting down
+# Ensure service did the copy Tries to lock /tmp/noexit and blocks
+# Write to /tmp/copyme ↓
+#
+# Now at this point the test can diverge. If we regress, this second write is
+# missed and we'll see:
+# ... (second write) ... (blocked)
+# Drop lock on /tmp/noexit ↓
+# Wait for service to do copy Unblocks and exits
+# ↓ (dead)
+# ↓
+# (timeout)
+# Test fails
+#
+# Otherwise, we'll see:
+# ... (second write) ... (blocked)
+# Drop lock on /tmp/noexit ↓ and .path unit queues a new start job
+# Wait for service to do copy Unblocks and exits
+# ↓ Starts again b/c of queued job
+# ↓ Copies again
+# Test Passes
+systemctl start test63-pr-30768.path
+exec {lock}<>/tmp/noexit
+flock -e $lock
+echo test1 > /tmp/copyme
+# shellcheck disable=SC2016
+timeout 30 bash -c 'until test "$(systemctl show test63-pr-30768.service -P ActiveState)" = deactivating; do sleep .2; done'
+diff /tmp/copyme /tmp/copied
+echo test2 > /tmp/copyme
+exec {lock}<&-
+timeout 30 bash -c 'until diff /tmp/copyme /tmp/copied; do sleep .2; done'
+
systemctl log-level info
touch /testok
fi
ROOTID=$(mktemp -u)
+# Needed on Ubuntu/Debian as we copy binaries manually
+mkdir -p /run/sshd
removesshid() {
rm -f "$ROOTID" "$ROOTID".pub
grep -qF "status: NOERROR" "$RUN_OUT"
grep -qE "IN\s+SOA\s+ns1\.unsigned\.test\." "$RUN_OUT"
+run resolvectl query -t SVCB svcb.test
+grep -qF 'alpn="dot"' "$RUN_OUT"
+grep -qF "ipv4hint=10.0.0.1" "$RUN_OUT"
+
+run resolvectl query -t HTTPS https.test
+grep -qF 'alpn="h2,h3"' "$RUN_OUT"
: "--- ZONE: unsigned.test. ---"
run dig @ns1.unsigned.test +short unsigned.test A unsigned.test AAAA
# (at your option) any later version.
[Unit]
-Description=Hibernate
+Description=System Hibernate
Documentation=man:systemd-hibernate.service(8)
DefaultDependencies=no
Requires=sleep.target
# (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
# (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