Compare commits
13 Commits
4c641d2457
...
dc81256b26
Author | SHA1 | Date |
---|---|---|
Vishal Chillara | dc81256b26 | |
Yu Watanabe | fed7857672 | |
Lennart Poettering | c8d45ebfd6 | |
Lennart Poettering | acc8bae0b3 | |
Lennart Poettering | a6d7cc74d6 | |
Luca Boccassi | d80d7a2f2a | |
Luca Boccassi | 088793239e | |
Luca Boccassi | 94a46c20da | |
Luca Boccassi | d6f4c96b10 | |
Luca Boccassi | 9e51b12e13 | |
Frantisek Sumsal | e9cfc2bd94 | |
Vishal Chillara Srinivas | 08860aa147 | |
Vishal Chillara Srinivas | 2e5bf2774e |
322
NEWS
322
NEWS
|
@ -103,37 +103,37 @@ CHANGES WITH 257 in spe:
|
|||
|
||||
libsystemd:
|
||||
|
||||
* systemd's JSON API is now available as public interface of libsystemd
|
||||
under the name "sd-json". The purpose of the library is to allow
|
||||
structures to be conveniently created in C code and serialized to
|
||||
JSON, and for JSON to be conveniently deserialized into in-memory
|
||||
structures, using callbacks to handle specific keys. Various data
|
||||
types like integers, floats, booleans, strings, UUIDs, base64-encoded
|
||||
and hex-encoded binary data, and arrays are supported natively. The
|
||||
library has been part of systemd for a while as internal component,
|
||||
and now being made publicly available, too. On major user of sd-json
|
||||
is the JSON interface sd-varlink (see below). Note that documentation
|
||||
on sd-json is very much incomplete for now, but the systemd codebase
|
||||
should provide plenty code real-life code examples.
|
||||
* systemd's JSON API is now available as public interface of
|
||||
libsystemd, under the name "sd-json". The purpose of the library is
|
||||
to allow structures to be conveniently created in C code and
|
||||
serialized to JSON, and for JSON to be conveniently deserialized into
|
||||
in-memory structures, using callbacks to handle specific
|
||||
keys. Various data types like integers, floats, booleans, strings,
|
||||
UUIDs, base64-encoded and hex-encoded binary data, and arrays are
|
||||
supported natively. The library has been part of systemd for a while
|
||||
as internal component, and is now made publicly available. One major
|
||||
user of sd-json is sd-varlink (see below). Note that the
|
||||
documentation of sd-json is very much incomplete for now, but the
|
||||
systemd codebase provides plenty real-life code examples.
|
||||
|
||||
* libsystemd's Varlink IPC API is now available as part of libsystemd
|
||||
* systemd's Varlink IPC API is now available as part of libsystemd,
|
||||
under the name "sd-varlink". This library is a C implementation of
|
||||
the Varlink IPC system (https://varlink.org/) that has been adopted
|
||||
by systemd for various interfaces. It relies on the sd-json JSON
|
||||
component, see above. Note that documentation on sd-varlink is very
|
||||
much incomplete for now, but the systemd codebase should provide
|
||||
plenty code real-life code examples.
|
||||
component, see above. Note that the documentation of sd-varlink is
|
||||
very much incomplete for now, but the systemd codebase provides
|
||||
plenty real-life code examples.
|
||||
|
||||
* sd-bus gained a new call sd_bus_pending_method_calls() which returns
|
||||
the number of currently open asynchronous method calls initiated on
|
||||
this connection towards peers.
|
||||
|
||||
* sd-device gained a new call sd_device_monitor_is_running() that
|
||||
returns whener the specified monitor object is already running. It
|
||||
returns whether the specified monitor object is already running. It
|
||||
also gained sd_device_monitor_get_fd(),
|
||||
sd_device_monitor_get_events(), sd_device_monitor_get_timeout() and
|
||||
sd_device_monitor_receive() to permit sd-device to run on a foreign
|
||||
event loop implementation. It also gained
|
||||
sd_device_monitor_receive() to permit sd-device to run on top of a
|
||||
foreign event loop implementation. It also gained
|
||||
sd_device_get_driver_subsystem() which returns the subsystem of
|
||||
driver objects. The new sd_device_get_device_id() call returns a
|
||||
short string identifying the device record.
|
||||
|
@ -148,8 +148,9 @@ CHANGES WITH 257 in spe:
|
|||
* Multipath TCP (MPTCP) is now supported as a socket protocol for
|
||||
.socket units.
|
||||
|
||||
* New /etc/fstab option x-systemd.wants= creates "Wants" dependencies.
|
||||
(This is similar to the previously available x-systemd.requires=.)
|
||||
* A new /etc/fstab option x-systemd.wants= creates "Wants="
|
||||
dependencies. (This is similar to the previously available
|
||||
x-systemd.requires=.)
|
||||
|
||||
* The initialization of the system clock during boot and updates has
|
||||
been simplified: both PID 1 or systemd-timesyncd will pick the latest
|
||||
|
@ -161,17 +162,17 @@ CHANGES WITH 257 in spe:
|
|||
shutdown, so that the user may use it to initiate a reboot if the
|
||||
system freezes otherwise.
|
||||
|
||||
* The new unit option PrivateUsers=identity can be used to request a
|
||||
user namespace with an identity mapping for the first 65536
|
||||
UIDs/GIDs. This is analogous to the systemd-nspawn's
|
||||
* The new value "identity" for the unit setting PrivateUsers= may be
|
||||
used to request a user namespace with an identity mapping for the
|
||||
first 65536 UIDs/GIDs. This is analogous to the systemd-nspawn's
|
||||
--private-users=identity.
|
||||
|
||||
* The new unit option PrivateTmp=disconnected can be used to specify
|
||||
that a separate tmpfs instance should be used for /tmp/ and /var/tmp/
|
||||
for the unit.
|
||||
* The new value "disconnected" for the unit setting PrivateTmp= may be
|
||||
used to specify that a separate tmpfs instance should be used for
|
||||
/tmp/ and /var/tmp/ for the unit.
|
||||
|
||||
* The manager (and various other tools too) use pidfds in more places
|
||||
to refer to processes.
|
||||
* The server manager (and various other tools too) use pidfds in more
|
||||
places to refer to processes.
|
||||
|
||||
* A build option -D link-executor-shared=false can be used to build
|
||||
the systemd-executor binary (added in a previous release) in a way
|
||||
|
@ -185,41 +186,41 @@ CHANGES WITH 257 in spe:
|
|||
execute.
|
||||
|
||||
* The systemd.machine_id= kernel command line parameter interpreted by
|
||||
PID 1 now supports an additional special value: if "firmware" is
|
||||
specified the machine ID is initialized from the SMBIOS/DeviceTree
|
||||
system UUID. (Previously this was already done in VM environments,
|
||||
this extends the concept to any system, but only on explicit request
|
||||
via this option.)
|
||||
PID 1 now supports an additional special value: if set to "firmware"
|
||||
the machine ID is initialized from the SMBIOS/DeviceTree system
|
||||
UUID. (Previously this was already done automatically in VM
|
||||
environments, this extends the concept to any system, but only on
|
||||
explicit request via this option.)
|
||||
|
||||
* The ImportCredential= setting in service unit files now permits
|
||||
renaming credentials imported.
|
||||
renaming of credentials as they are imported.
|
||||
|
||||
* The RestartMode= gained a new "debug" setting. If specified and the
|
||||
service fails so that it shall be restarted it is invoked in
|
||||
* The RestartMode= setting gained a new "debug" value. If specified and
|
||||
the service fails so that it shall be restarted it is invoked in
|
||||
"debugging mode". Debugging mode means that the $DEBUG_INVOCATION
|
||||
environment variable will be set to "1" for the new
|
||||
invocation. Moreover, any setting LogLevelMax= will be temporarily
|
||||
changed to "debug" for the next invocation. This mode is useful to
|
||||
repeat invocation of tools if they fail but with additional logging
|
||||
or testing routines turned on.
|
||||
automatically repeat invocation of tools in case they fail – but with
|
||||
additional logging or testing routines enabled.
|
||||
|
||||
* A new service setting BindLogSockets= has been added that
|
||||
controls whether the AF_UNIX sockets required for logging shall be
|
||||
bind mounted to the mount sandbox allocated for the service.
|
||||
|
||||
* PID 1 will now optionally load a policy for the new Linux IPE LSM at
|
||||
boot.
|
||||
* At early boot, PID 1 will now optionally load a policy for the new
|
||||
Linux IPE LSM.
|
||||
|
||||
* Transient services (StartTransientUnit() D-Bus method) may now
|
||||
receive additional, arbitrary file descriptors to pass to executed
|
||||
service processes on activation using the new ExtraFileDescriptor=
|
||||
unit property.
|
||||
* Transient services (as invoked by the StartTransientUnit() D-Bus
|
||||
method) may now receive additional, arbitrary file descriptors to
|
||||
pass to executed service processes during activation using the new
|
||||
ExtraFileDescriptor= unit property.
|
||||
|
||||
* Calendar .timer units gained a new boolean DeferReactivation=
|
||||
option. If enabled and the repetitive calendar timer elapses again
|
||||
while the service the timer activates is still running, immediate
|
||||
reactivation once it finishes is skipped, and the timer has to elapse
|
||||
again before the service is reactivated.
|
||||
reactivation of the service once it finishes is skipped, and the
|
||||
timer has to elapse again before the service is reactivated.
|
||||
|
||||
* Generator processes invoked by the service manager will now receive a
|
||||
new environment variable $SYSTEMD_SOFT_REBOOTS_COUNT that indicates
|
||||
|
@ -245,10 +246,10 @@ CHANGES WITH 257 in spe:
|
|||
"strict" a new cgroup namespace is allocated for the service, and
|
||||
cgroupfs is mounted read-only for the service.
|
||||
|
||||
* The StateDirectory=, RuntimeDirectory=, CacheDirectory=, LogsDirectory=,
|
||||
and ConfigurationDirectory= settings gained support for configuring the
|
||||
respective directories as read-only, via a ':ro' flag that can be
|
||||
appended to each setting.
|
||||
* The StateDirectory=, RuntimeDirectory=, CacheDirectory=,
|
||||
LogsDirectory=, and ConfigurationDirectory= settings gained support
|
||||
for configuring the respective directories as read-only, via a ':ro'
|
||||
flag that can be appended to each setting's value.
|
||||
|
||||
* When DynamicUser= is combined with
|
||||
StateDirectory=/RuntimeDirectory=/CacheDirectory=/LogsDirectory= and
|
||||
|
@ -258,15 +259,15 @@ CHANGES WITH 257 in spe:
|
|||
chown()ing.
|
||||
|
||||
* A new service property PrivatePIDs= has been added that runs executed
|
||||
processes as PID 1 - the init process - within their own PID namespace.
|
||||
PrivatePIDs= also mounts /proc/ so only processes within the new PID
|
||||
namespace are visible.
|
||||
processes as PID 1 - the init process - within their own PID
|
||||
namespace. PrivatePIDs= also mounts /proc/ so only processes within
|
||||
the new PID namespace are visible.
|
||||
|
||||
systemd-udevd:
|
||||
|
||||
* udev rules now set 'uaccess' for /dev/udmabuf, giving locally
|
||||
logged-in users access to the hardware. This is necessary to support
|
||||
IPMI cameras with libcamera.
|
||||
logged-in users access to the hardware. This is useful in order to
|
||||
support IPMI cameras with libcamera.
|
||||
|
||||
* Serial port devices will no longer show up as systemd units, unless
|
||||
they have an IO port or memory assigned to them. This means that only
|
||||
|
@ -281,9 +282,9 @@ CHANGES WITH 257 in spe:
|
|||
searched for both on the interface's parent device (as before) and
|
||||
the device itself (new).
|
||||
|
||||
* Various USB hardware wallets have are now recognized by udev via a
|
||||
.hwdb file, and get the ID_HARDWARE_WALLET= property set, which
|
||||
enables "uaccess" for them, i.e. direct unprivileged access.
|
||||
* Various USB hardware wallets are now recognized by udev via a .hwdb
|
||||
file, and get the ID_HARDWARE_WALLET= property set, which enables
|
||||
"uaccess" for them, i.e. direct unprivileged access.
|
||||
|
||||
* udevadm info will now output the device ID string in lines prefixed
|
||||
with "J:", and the driver subsystem in lines prefixed with "B:".
|
||||
|
@ -293,8 +294,8 @@ CHANGES WITH 257 in spe:
|
|||
|
||||
systemd-logind:
|
||||
|
||||
* New DesignatedMaintenanceTime= configuration option allows
|
||||
shutdowns to be automatically scheduled at the specified time.
|
||||
* New DesignatedMaintenanceTime= configuration option allows shutdowns
|
||||
to be automatically scheduled at the specified time.
|
||||
|
||||
* logind now reacts to Ctrl-Alt-Shift-Esc being pressed. It will send
|
||||
out a org.freedesktop.login1.SecureAttentionKey signal, indicating a
|
||||
|
@ -308,8 +309,8 @@ CHANGES WITH 257 in spe:
|
|||
session switches away.
|
||||
|
||||
* systemd-logind now exposes two D-Bus properties CanLock and CanIdle
|
||||
for all sessions that indicate whether the session's class supports
|
||||
screen locking and idle detection.
|
||||
for all sessions. These properties indicate whether the session's
|
||||
class supports screen locking and idleness detection.
|
||||
|
||||
* systemd-inhibit now allows interactive polkit authorization. It
|
||||
gained a --no-ask-password option to suppress it.
|
||||
|
@ -320,12 +321,13 @@ CHANGES WITH 257 in spe:
|
|||
Machines started via the systemd-vmspawn@.service unit will now be
|
||||
registered with systemd-machined.
|
||||
|
||||
* systemd-machined gained a pretty complete set of Varlink interfaces
|
||||
to its functionality as alternative to the existing D-Bus interface.
|
||||
* systemd-machined gained a pretty complete set of Varlink APIs
|
||||
exposing its functionality. This is an alternative to the
|
||||
pre-existing D-Bus interface.
|
||||
|
||||
systemd-resolved:
|
||||
|
||||
* resolvconf command now supports '-p' switch. If specified, the
|
||||
* The resolvconf command now supports '-p' switch. If specified, the
|
||||
interface will not be used as the default route for domain name
|
||||
lookups.
|
||||
|
||||
|
@ -337,11 +339,11 @@ CHANGES WITH 257 in spe:
|
|||
* IPv6 address labels can be configured in a new [IPv6AddressLabel]
|
||||
section with Prefix= and Label= settings.
|
||||
|
||||
* 'networkctl edit' can now read the new contents from standard input
|
||||
with the new --stdin option.
|
||||
* 'networkctl edit' can now read the new file contents from standard
|
||||
input with the new --stdin option.
|
||||
|
||||
* 'networkctl edit' and 'cat' now supports editing .netdev files by
|
||||
link. 'networkctl cat' can also list all configuration files
|
||||
* 'networkctl edit' and 'cat' now support editing/showing .netdev files
|
||||
by link. 'networkctl cat' can also list all configuration files
|
||||
associated with an interface at once with ':all'.
|
||||
|
||||
* networkctl gained a --no-ask-password option to suppress interactive
|
||||
|
@ -350,7 +352,7 @@ CHANGES WITH 257 in spe:
|
|||
* "mac" has been added to the default AlternativeNamesPolicy= setting
|
||||
for network links (via 99-default.link). This means "enx*" interface
|
||||
names will now be added to the list of alternative interface names by
|
||||
default for all interfaces that have a MAC address assigned to them
|
||||
default, for all interfaces that have a MAC address assigned
|
||||
by hardware.
|
||||
|
||||
* networkd .netdev bridge devices gained a new setting FDBMaxLearned=
|
||||
|
@ -365,18 +367,18 @@ CHANGES WITH 257 in spe:
|
|||
thus highlighting conflict of ownership/management of these knobs.
|
||||
|
||||
* systemd-networkd will now make RFC9463 DNR fields available to
|
||||
systemd-resolved, for automatic DoT configuration, and similar.
|
||||
systemd-resolved, for automatic DNS DoT configuration, and similar.
|
||||
|
||||
systemd-boot, systemd-stub, and related tools:
|
||||
|
||||
* The EFI stub now supports loading of .ucode sections with microcode
|
||||
from PE add-on files. It now also supports loading .initrd sections
|
||||
from PE add-on files. It also now supports loading .initrd sections
|
||||
from PE add-on files.
|
||||
|
||||
* A new .profile PE section type is now documented and supported in
|
||||
systemd-measure, ukify, systemd-stub and systemd-boot. Those new
|
||||
systemd-measure, ukify, systemd-stub and systemd-boot. These new
|
||||
sections allow multiple "profiles" to be stored together in the UKI,
|
||||
with .profile sections creating groupings of sections in the UKI,
|
||||
where each .profile section creates groupings of sections in the UKI,
|
||||
allowing some sections to be shared and other sections like .cmdline
|
||||
or .initrd unique to the profile. This may be used to provide a
|
||||
single UKI that synthesizes multiple menu items in the boot menu (for
|
||||
|
@ -389,10 +391,10 @@ CHANGES WITH 257 in spe:
|
|||
can contain multiple .dtbauto sections, and the 'compatible' string
|
||||
therein will be compared with the equivalent field in the DTB
|
||||
provided by the firmware, if present. If absent, SMBIOS will be used
|
||||
to calculate hardware IDs and compare them with the content of
|
||||
.hwids. This allows including multiple DTBs in a single UKI, with
|
||||
the bootloader automatically selecting the correct one for the
|
||||
current hardware.
|
||||
to calculate hardware IDs (CHIDs) and look them up in the content of
|
||||
.hwids, hopefully revealing an fallback 'compatible' string. This
|
||||
allows including multiple DTBs in a single UKI, with systemd-stub
|
||||
automatically loading the correct one for the current hardware.
|
||||
|
||||
* ukify gained an --extend switch to import an existing UKI to
|
||||
be extended, and a --measure-base= switch to support measurement
|
||||
|
@ -405,25 +407,26 @@ CHANGES WITH 257 in spe:
|
|||
|
||||
* systemd-stub will report the partition UUID and image identifier its
|
||||
UKI executable is placed on separately from the data systemd-boot
|
||||
provides about where to find its own executable. This is useful when
|
||||
systemd-boot and UKIs are placed on distinct partitions (i.e. ESP and
|
||||
XBOOTLDR).
|
||||
provides about where to find its own executable, via EFI
|
||||
variables. This is useful when systemd-boot and UKIs are placed on
|
||||
distinct partitions (i.e. ESP and XBOOTLDR).
|
||||
|
||||
* bootctl --print-loader-path and --print-stub-path that output the
|
||||
path to the boot loader or UKI used for the current boot.
|
||||
* bootctl gained new switches --print-loader-path and --print-stub-path
|
||||
that output the path to the boot loader or UKI used for the current
|
||||
boot.
|
||||
|
||||
* bootctl kernel-identify now supports identifying EFI add-ons.
|
||||
* bootctl kernel-identify now recognizes EFI add-ons.
|
||||
|
||||
* bootctl gained a --random-seed=yes|no option to control provisioning
|
||||
of the random seed file in ESP. (This is useful when producing an
|
||||
image that will be used multiple times.)
|
||||
of the random seed file in the ESP. (This is useful when producing an
|
||||
image that will be used in multiple instances.)
|
||||
|
||||
* bootctl now optionally supports installing UEFI Secure Boot databases
|
||||
(ESLs) for systemd-boot to pick up and automatically enroll if the
|
||||
system is booted in Setup Mode. This is controlled via bootctl's new
|
||||
--secure-boot-auto-enroll=yes switch (and some auxiliary ones). A
|
||||
certificate can be provided in DER format, and it is automatically
|
||||
converted into an ESL, as needed.
|
||||
(i.e. db/dbx/… databases in ESL format) for systemd-boot to pick up
|
||||
and automatically enroll if the system is booted in Setup Mode. This
|
||||
is controlled via bootctl's new --secure-boot-auto-enroll=yes switch
|
||||
(and some auxiliary ones). A certificate can be provided in DER
|
||||
format, and is automatically converted into an ESL, as needed.
|
||||
|
||||
* bootctl, systemd-measure, systemd-repart when referencing signing
|
||||
keys on OpenSSL engines may now query for PINs and similar via
|
||||
|
@ -431,9 +434,9 @@ CHANGES WITH 257 in spe:
|
|||
caching and UI).
|
||||
|
||||
* A new systemd-sbsign tool has been added, that can be used to sign
|
||||
EFI binaries (PE). This tool supports OpenSSL engines and providers,
|
||||
with pin caching support for PKCS11. ukify supports it as an
|
||||
alternative to sbsigntool and pesign.
|
||||
EFI binaries (PE) for Secure Boot. This tool supports OpenSSL engines
|
||||
and providers, with pin caching support for PKCS11. ukify supports it
|
||||
as an alternative to sbsigntool and pesign.
|
||||
|
||||
The journal:
|
||||
|
||||
|
@ -468,22 +471,22 @@ CHANGES WITH 257 in spe:
|
|||
and AppStream metadata.
|
||||
|
||||
* Transfer definitions for systemd-sysupdate are supposed to carry the
|
||||
".transfer" suffix now, changing from ".conf". The latter is
|
||||
supported for compatibility too, but it's recommended to rename all
|
||||
files reflecting this suffix change.
|
||||
".transfer" suffix now, changing from ".conf". The latter remains
|
||||
supported for compatibility, but it's recommended to rename all files
|
||||
reflecting this suffix change.
|
||||
|
||||
* systemd-sysupdate now supports a new ".feature" files that may be
|
||||
* systemd-sysupdate now supports new ".feature" files that may be
|
||||
used in conjunction with ".transfer" files to group them together, and
|
||||
allow them to be turned off or on, individually per group.
|
||||
|
||||
TPM & systemd-cryptsetup:
|
||||
|
||||
* The 'tpm2' verb which lists usable TPM2 devices has been moved from
|
||||
systemd-creds to systemd-analyze.
|
||||
* The 'has-tpm2' verb which reports whether TPM2 functionality is
|
||||
available has been moved from systemd-creds to systemd-analyze.
|
||||
|
||||
* systemd-tpm2-setup will gracefully handle TPMs that have a PIN set on
|
||||
the TPM, and not automatically set up a Storage Root Key (SRK) in
|
||||
that case.
|
||||
the TPM, and not attempt to automatically set up a Storage Root Key
|
||||
(SRK) in that case.
|
||||
|
||||
* New crypttab option password-cache=yes|no|read-only can be used to
|
||||
customize password caching.
|
||||
|
@ -525,7 +528,7 @@ CHANGES WITH 257 in spe:
|
|||
start the specified executable on the remote side, and communicate
|
||||
with the remote process using the Varlink protocol.
|
||||
|
||||
"ssh:" address specification has been renamed to "ssh-unix:"
|
||||
The "ssh:" address specification has been renamed to "ssh-unix:"
|
||||
(reflecting the fact it is used to connect to a remote AF_UNIX socket
|
||||
via SSH). The old syntax is still supported for backwards
|
||||
compatibility.
|
||||
|
@ -546,7 +549,8 @@ CHANGES WITH 257 in spe:
|
|||
to enable internal compression in filesystems created offline.
|
||||
|
||||
* systemd-repart understands a new MakeSymlinks= option to create one
|
||||
or more symlinks (each specified as a symlink name and target).
|
||||
or more symlinks (each specified as a symlink name and target) within
|
||||
a newly formatted file system.
|
||||
|
||||
* systemd-repart gained a new SupplementFor= setting that allows
|
||||
allocating a partition only if some other existing partition cannot
|
||||
|
@ -559,15 +563,15 @@ CHANGES WITH 257 in spe:
|
|||
|
||||
systemd-ssh-proxy:
|
||||
|
||||
* systemd-ssh-proxy now also supports the "VSOCK MUX" protocol used by
|
||||
CloudHypervisor/Firecracker to expose AF_VSOCK sockets of the VM on
|
||||
the host. Or in other words: it's now possible to directly connect to
|
||||
ssh via AF_VSOCK from hosts to VMs of these two hypervisors
|
||||
(previously this was only supported for hypervisors which expose
|
||||
AF_VSOCK on the host as AF_VSOCK, such as qemu).
|
||||
* systemd-ssh-proxy now also supports the AF_UNIX-based "VSOCK MUX"
|
||||
protocol used by CloudHypervisor/Firecracker to expose AF_VSOCK
|
||||
sockets of the VM on the host. Or in other words: it's now possible
|
||||
to directly connect to ssh via AF_VSOCK from hosts to VMs of these
|
||||
two hypervisors (previously this was only supported for hypervisors
|
||||
which expose AF_VSOCK on the host as AF_VSOCK, such as qemu).
|
||||
|
||||
* systemd-ssh-proxy can now reference local VMs by their name: connect
|
||||
to any local VM "foobar" registered with machined via "ssh
|
||||
to any local VM "foobar" registered with systemd-machined via "ssh
|
||||
machine/foobar" using the AF_VSOCK protocol.
|
||||
|
||||
systemd-analyze:
|
||||
|
@ -591,7 +595,6 @@ CHANGES WITH 257 in spe:
|
|||
|
||||
* 'busctl monitor' gained new options --limit-messages= and --timeout=
|
||||
to set the number of matches or limit the runtime of the command.
|
||||
This is intended to be used in scripts.
|
||||
|
||||
* busctl now supports doing method calls with embedded unix file
|
||||
descriptors.
|
||||
|
@ -609,9 +612,9 @@ CHANGES WITH 257 in spe:
|
|||
|
||||
systemd-importd:
|
||||
|
||||
* A new generator sytemd-import-generator has been added to
|
||||
synthetisize image download jobs. This provides functionality similar
|
||||
to importctl, but configured via the kernel command line and system
|
||||
* A new generator sytemd-import-generator has been added to synthesize
|
||||
image download jobs. This provides functionality similar to
|
||||
importctl, but is configured via the kernel command line and system
|
||||
credentials. It may be used to automatically download sysext,
|
||||
confext, portable service, nspawn container or vmspawn VM images at
|
||||
boot.
|
||||
|
@ -640,6 +643,32 @@ CHANGES WITH 257 in spe:
|
|||
systemd-homed to allow users to change selected properties of their
|
||||
own user records.
|
||||
|
||||
systemd-run & run0:
|
||||
|
||||
* run0 gained a new pair of settings --pty and --pipe that control
|
||||
whether to invoke the specified binary on a freshly allocated pseudo
|
||||
TTY, or whether to pass the client's STDIN/STDOUT/STDERR through
|
||||
directly.
|
||||
|
||||
* run0 gained a new switch --shell-prompt-prefix= that permits passing
|
||||
in a string to display on each shell prompt as prefix. If not
|
||||
specified otherwise this will show a superhero emoji (🦸), in order
|
||||
to visually communicate the temporarily elevated privileges a run0
|
||||
session provides. This makes use of the $SHELL_PROMPT_PREFIX
|
||||
environment variables mentioned below.
|
||||
|
||||
* systemd-run can output some of its runtime data in JSON format via
|
||||
the new --json= option.
|
||||
|
||||
systemd-tmpfiles:
|
||||
|
||||
* systemd-tmpfiles --purge switch now requires specification of at
|
||||
least one tmpfiles.d/ drop-in file.
|
||||
|
||||
* tmpfiles.d/ files gained a new '?' specifier for the 'L' line type to
|
||||
create a symlink only if the source exists, and gracefully skip the
|
||||
line otherwise.
|
||||
|
||||
Miscellaneous:
|
||||
|
||||
* systemctl now supports the --now option with the 'reenable' verb.
|
||||
|
@ -654,21 +683,13 @@ CHANGES WITH 257 in spe:
|
|||
* localectl gained a -l/--full option to show output without
|
||||
ellipsization.
|
||||
|
||||
* systemd-run can output some data as JSON via the new --json= option.
|
||||
|
||||
* timedatectl now supports interactive polkit authorization.
|
||||
|
||||
* systemd-tmpfiles --purge switch now requires specification of at
|
||||
least one tmpfiles.d/ drop-in file.
|
||||
|
||||
* tmpfiles.d gained a new '?' specifier for the 'L' type to create a
|
||||
symlink only if the source exists, and gracefully skip otherwise.
|
||||
|
||||
* The new Linux mseal(), listmount(), statmount() syscalls have been
|
||||
added to relevant system call groups.
|
||||
|
||||
* The systemd-ask-password concept has been extended with a per-user
|
||||
concept, i.e. user programs may now ask for passwords via the same
|
||||
* The systemd-ask-password logic has been extended with a per-user
|
||||
scope, i.e. user programs may now ask for passwords via the same
|
||||
mechanism and the previously system-wide only mechanism.
|
||||
|
||||
* A new set of system/service credentials are added:
|
||||
|
@ -681,17 +702,8 @@ CHANGES WITH 257 in spe:
|
|||
useful to visually highlight the fact a specific shell prompt
|
||||
originates from a specific system, execution context or tool. These
|
||||
credentials and environment variables are supposed to be generically
|
||||
useful within and outside of the immediate systemd context.
|
||||
|
||||
* run0 gained a new pair of settings --pty and --pipe that control
|
||||
whether to invoke the specified binary on a freshly allocated pseudo
|
||||
TTY, or whether to pass the client's STDIN/STDOUT/STDERR through
|
||||
directly. run0 also gained a new switch --shell-prompt-prefix= that
|
||||
permits passing in a string to display on each shell prompt as
|
||||
prefix. If not specified otherwise this will show a superman emoji
|
||||
(🦸), in order to visually communicate the temporarily elevated
|
||||
privileges a run0 session provides. This makes use of the
|
||||
$SHELL_PROMPT_PREFIX environment variables mentioned above.
|
||||
useful within and outside of the immediate systemd context. It is
|
||||
also used by 'run0', see above.
|
||||
|
||||
* New RELEASE_TYPE=, EXPERIMENT=, EXPERIMENT_URL= fields have been
|
||||
defined for the /etc/os-release file. For example,
|
||||
|
@ -718,28 +730,28 @@ CHANGES WITH 257 in spe:
|
|||
https://github.com/microsoft/terminal/pull/8055
|
||||
https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
|
||||
|
||||
* systemd-sysusers is now able to create fully locked accounts. For
|
||||
compatibility it so far created accounts with a locked (i.e. invalid)
|
||||
password, but not marked locked as a whole. With the new "!" modifier
|
||||
for "u" lines, it is now possible to create fully locked
|
||||
accounts. The distinction between accounts with a locked password and
|
||||
fully locked accounts is relevant when considering non-password forms
|
||||
of authentication, i.e. SSH and such. It is strongly recommended to
|
||||
make use of this new feature for almost all system accounts, since
|
||||
they usually do not require (and should not permit) interactive
|
||||
logins. All of systemd's own system users have been changed to be
|
||||
marked as fully locked.
|
||||
* systemd-sysusers is now able to create fully locked user
|
||||
accounts. For compatibility it so far created accounts with a locked
|
||||
(i.e. invalid) password, but not marked locked as a whole. With the
|
||||
new "!" modifier for "u" lines, it is now possible to create fully
|
||||
locked accounts. The distinction between accounts with a locked
|
||||
password and fully locked accounts is relevant when considering
|
||||
non-password forms of authentication, i.e. SSH and such. It is
|
||||
strongly recommended to make use of this new feature for almost all
|
||||
system accounts, since they usually do not require (and should not
|
||||
permit) interactive logins. All of systemd's own system users have
|
||||
been changed to be marked as fully locked.
|
||||
|
||||
* systemd-coredump now supports a new EnterNamespace= option, which
|
||||
defaults to off. If enabled systemd-coredump will access the mount
|
||||
namespace of any crashed process to acquire debug symbol information,
|
||||
in order to be able to symbolized backtraces. This option is useful
|
||||
to improve backtraces of processes of containerized
|
||||
applications. (Note that the host systemd-coredump preferably
|
||||
dispatches coredump processing to the container itself, if it
|
||||
supports that. Only full-OS containers which run systemd inside will
|
||||
support this however, in which case EnterNamespace= might be an
|
||||
alternative approach to acquire symbolized backtraces.)
|
||||
in order to be able to symbolize backtraces. This option is useful to
|
||||
improve backtraces of processes of containerized applications. (Note
|
||||
that the host systemd-coredump preferably dispatches coredump
|
||||
processing to the container itself, if it supports that. Only full-OS
|
||||
containers which run systemd inside will support this however, in
|
||||
other cases EnterNamespace= might be an suitable approach to acquire
|
||||
symbolized backtraces.)
|
||||
|
||||
Contributions from: A. Wilcox, Abderrahim Kitouni, Adrian Vovk,
|
||||
Alain Greppin, Allison Karlitskaya, Alyssa Ross, Anders Jonsson,
|
||||
|
|
6
TODO
6
TODO
|
@ -129,6 +129,12 @@ Deprecations and removals:
|
|||
|
||||
Features:
|
||||
|
||||
* machined: when registering a machine, also take a relative cgroup path,
|
||||
relative to the machine's unit. This is useful when registering unpriv
|
||||
machines, as they might sit down the cgroup tree, below a cgroup delegation
|
||||
boundary. Then, install an inotify watch on that cgroup to track when the
|
||||
machine's local cgroup goes down.
|
||||
|
||||
* resolved: report ttl in resolution replies if we know it. This data is useful
|
||||
for tools such as wireguard which want to periodically re-resolve DNS names,
|
||||
and might want to use the TTL has hint for that.
|
||||
|
|
|
@ -15,18 +15,19 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
|||
6. [RC1] Update library numbers in `meson.build`
|
||||
7. Update version number in `meson.version` (e.g. from `256~devel` to `256~rc1` or from `256~rc3` to `256`). Note that this uses a tilde (\~) instead of a hyphen (-) because tildes sort lower in version comparisons according to the [version format specification](https://uapi-group.org/specifications/specs/version_format_specification/), and we want `255~rc1` to sort lower than `255`.
|
||||
8. Check dbus docs with `ninja -C build update-dbus-docs`
|
||||
9. Update translation strings (`ninja -C build systemd-pot`, `ninja -C build systemd-update-po`) - drop the header comments from `systemd.pot` + re-add SPDX before committing. If the only change in a file is the 'POT-Creation-Date' field, then ignore that file.
|
||||
10. Tag the release: `version="v$(sed 's/~/-/g' meson.version)" && git tag -s "${version}" -m "systemd ${version}"` (tildes are replaced with hyphens, because git doesn't accept the former).
|
||||
11. Do `ninja -C build`
|
||||
12. Make sure that the version string and package string match: `build/systemctl --version`
|
||||
13. [FINAL] Close the github milestone and open a new one (https://github.com/systemd/systemd/milestones)
|
||||
14. "Draft" a new release on github (https://github.com/systemd/systemd/releases/new), mark "This is a pre-release" if appropriate.
|
||||
15. Check that announcement to systemd-devel, with a copy&paste from NEWS, was sent. This should happen automatically.
|
||||
16. Update IRC topic (`/msg chanserv TOPIC #systemd Version NNN released | Online resources https://systemd.io/`)
|
||||
17. [FINAL] Create an empty -stable branch: `git push systemd origin/main:refs/heads/v${version}-stable`.
|
||||
18. [FINAL] Build and upload the documentation (on the -stable branch): `ninja -C build doc-sync`
|
||||
9. Check manpages list with `ninja -C build update-man-rules`
|
||||
10. Update translation strings (`ninja -C build systemd-pot`, `ninja -C build systemd-update-po`) - drop the header comments from `systemd.pot` + re-add SPDX before committing. If the only change in a file is the 'POT-Creation-Date' field, then ignore that file.
|
||||
11. Tag the release: `version="v$(sed 's/~/-/g' meson.version)" && git tag -s "${version}" -m "systemd ${version}"` (tildes are replaced with hyphens, because git doesn't accept the former).
|
||||
12. Do `ninja -C build`
|
||||
13. Make sure that the version string and package string match: `build/systemctl --version`
|
||||
14. [FINAL] Close the github milestone and open a new one (https://github.com/systemd/systemd/milestones)
|
||||
15. "Draft" a new release on github (https://github.com/systemd/systemd/releases/new), mark "This is a pre-release" if appropriate.
|
||||
16. Check that announcement to systemd-devel, with a copy&paste from NEWS, was sent. This should happen automatically.
|
||||
17. Update IRC topic (`/msg chanserv TOPIC #systemd Version NNN released | Online resources https://systemd.io/`)
|
||||
18. [FINAL] Create an empty -stable branch: `git push systemd origin/main:refs/heads/v${version}-stable`.
|
||||
19. [FINAL] Build and upload the documentation (on the -stable branch): `ninja -C build doc-sync`
|
||||
20. [FINAL] Change the Github Pages branch to the newly created branch (https://github.com/systemd/systemd/settings/pages) and set the 'Custom domain' to 'systemd.io'
|
||||
21. [FINAL] Update version number in `meson.version` to the devel version of the next release (e.g. from `v256` to `v257~devel`)
|
||||
21. [FINAL] Update version number in `meson.version` to the devel version of the next release (e.g. from `256` to `257~devel`)
|
||||
|
||||
# Steps to a Successful Stable Release
|
||||
|
||||
|
|
|
@ -1069,6 +1069,7 @@ manpages = [
|
|||
'ENABLE_RFKILL'],
|
||||
['systemd-run-generator', '8', [], ''],
|
||||
['systemd-run', '1', [], ''],
|
||||
['systemd-sbsign', '1', [], ''],
|
||||
['systemd-sleep.conf', '5', ['sleep.conf.d'], ''],
|
||||
['systemd-socket-activate', '1', [], ''],
|
||||
['systemd-socket-proxyd', '8', [], ''],
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
|
||||
<varlistentry>
|
||||
<term><option>--private-key=<replaceable>PATH/URI</replaceable></option></term>
|
||||
<term><option>--private-key-source=<replaceable>TYPE</replaceable>[:<replaceable>NAME<replaceable>]</option></term>
|
||||
<term><option>--private-key-source=<replaceable>TYPE</replaceable>[:<replaceable>NAME</replaceable>]</option></term>
|
||||
<term><option>--certificate=<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>Set the Secure Boot private key and certificate for use with the
|
||||
|
|
|
@ -8,6 +8,7 @@ basic_dns_sources = files(
|
|||
'resolved-dns-rr.c',
|
||||
'resolved-dns-answer.c',
|
||||
'resolved-dns-question.c',
|
||||
'resolved-dns-browse-services.c',
|
||||
'resolved-util.c',
|
||||
'dns-type.c',
|
||||
)
|
||||
|
|
|
@ -0,0 +1,687 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "af-list.h"
|
||||
#include "event-util.h"
|
||||
#include "json-util.h"
|
||||
#include "random-util.h"
|
||||
#include "resolved-dns-browse-services.h"
|
||||
#include "resolved-dns-cache.h"
|
||||
#include "resolved-varlink.h"
|
||||
|
||||
/* RFC 6762 section 5.2 - The querier should plan to issue a query at 80% of
|
||||
* the record lifetime, and then if no answer is received, at 85%, 90%, and 95%. */
|
||||
static usec_t mdns_maintenance_next_time(usec_t until, uint32_t ttl, int ttl_state) {
|
||||
return usec_sub_unsigned(until, (20 - ttl_state * 5) * ttl * USEC_PER_SEC / 100);
|
||||
}
|
||||
|
||||
/* RFC 6762 section 5.2 - A random variation of 2% of the record TTL should
|
||||
* be added to maintenance queries. */
|
||||
static usec_t mdns_maintenance_jitter(uint32_t ttl) {
|
||||
return random_u64_range(100) * 2 * ttl * USEC_PER_SEC / 10000;
|
||||
}
|
||||
|
||||
#define MDNS_80_PERCENT 80
|
||||
#define MDNS_5_PERCENT 5
|
||||
|
||||
static void mdns_find_service_from_query(DnsService **service, DnsServiceBrowser *sb, DnsQuery *q) {
|
||||
assert(sb);
|
||||
|
||||
/* Find the service that owns the query. */
|
||||
LIST_FOREACH(dns_services, s, sb->dns_services) {
|
||||
if (s->query == q) {
|
||||
*service = s;
|
||||
return;
|
||||
}
|
||||
}
|
||||
*service = NULL;
|
||||
}
|
||||
|
||||
static void mdns_maintenance_query_complete(DnsQuery *q) {
|
||||
_cleanup_(dns_service_browser_unrefp) DnsServiceBrowser *sb = NULL;
|
||||
_cleanup_(dns_query_freep) DnsQuery *query = q;
|
||||
DnsService *service = NULL;
|
||||
int r;
|
||||
|
||||
assert(query);
|
||||
assert(query->manager);
|
||||
|
||||
sb = dns_service_browser_ref(hashmap_get(query->manager->dns_service_browsers, query->varlink_request));
|
||||
if (!sb)
|
||||
return;
|
||||
|
||||
if (query->state != DNS_TRANSACTION_SUCCESS) {
|
||||
r = 0;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = dns_answer_match_key(query->answer, sb->key, NULL);
|
||||
if (r <= 0)
|
||||
goto finish;
|
||||
|
||||
r = mdns_browser_lookup_cache(sb, query->answer_family);
|
||||
|
||||
finish:
|
||||
if (r < 0)
|
||||
log_error_errno(r, "mDNS maintenance query complete failed: %m");
|
||||
|
||||
mdns_find_service_from_query(&service, sb, query);
|
||||
if (service)
|
||||
service->query = NULL;
|
||||
}
|
||||
|
||||
static int mdns_maintenance_query(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
DnsService *service = NULL;
|
||||
_cleanup_(dns_query_freep) DnsQuery *q = NULL;
|
||||
int r;
|
||||
|
||||
assert(userdata);
|
||||
|
||||
service = userdata;
|
||||
|
||||
if (service->rr_ttl_state++ == MDNS_TTL_100_PERCENT)
|
||||
return mdns_browser_lookup_cache(service->sb, service->family);
|
||||
|
||||
r = dns_query_new(service->sb->m, &q, service->sb->question_utf8, service->sb->question_idna, NULL, service->sb->ifindex, service->sb->flags);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
q->complete = mdns_maintenance_query_complete;
|
||||
q->varlink_request = sd_varlink_ref(service->sb->link);
|
||||
service->query = TAKE_PTR(q);
|
||||
|
||||
usec_t next_time = mdns_maintenance_next_time(service->until, service->rr->ttl, service->rr_ttl_state);
|
||||
|
||||
/* Schedule next maintenance query for service */
|
||||
r = event_reset_time(
|
||||
service->sb->m->event, &service->schedule_event,
|
||||
CLOCK_BOOTTIME, next_time, 0, mdns_maintenance_query,
|
||||
service, 0, "mdns-next-query-schedule", true);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = dns_query_go(service->query);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
dns_query_free(service->query);
|
||||
return log_error_errno(r, "Failed mdns maintenance query: %m");
|
||||
}
|
||||
|
||||
int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_family) {
|
||||
_cleanup_(dns_service_unrefp) DnsService *s = NULL;
|
||||
int r;
|
||||
|
||||
assert(sb);
|
||||
assert(rr);
|
||||
|
||||
s = new(DnsService, 1);
|
||||
if (!s)
|
||||
return log_oom();
|
||||
|
||||
usec_t usec = now(CLOCK_BOOTTIME);
|
||||
|
||||
*s = (DnsService) {
|
||||
.n_ref = 1,
|
||||
.sb = dns_service_browser_ref(sb),
|
||||
.rr = dns_resource_record_copy(rr),
|
||||
.family = owner_family,
|
||||
.until = rr->until,
|
||||
.query = NULL,
|
||||
.rr_ttl_state = MDNS_TTL_80_PERCENT,
|
||||
};
|
||||
|
||||
LIST_PREPEND(dns_services, sb->dns_services, s);
|
||||
|
||||
/* Schedule the first cache maintenance query at 80% of the record's TTL.
|
||||
* Subsequent queries issued at 5% increments until 100% of the TTL. RFC 6762 section 5.2.
|
||||
* If service is being added after 80% of the TTL has already elapsed,
|
||||
* schedule the next query at the next 5% increment. */
|
||||
usec_t next_time = 0;
|
||||
while (s->rr_ttl_state <= MDNS_TTL_100_PERCENT) {
|
||||
next_time = mdns_maintenance_next_time(rr->until, rr->ttl, s->rr_ttl_state);
|
||||
if (next_time >= usec)
|
||||
break;
|
||||
s->rr_ttl_state++;
|
||||
}
|
||||
|
||||
if (next_time < usec) {
|
||||
/* If next_time is still in the past, the service is being added after it has already expired.
|
||||
* Just schedule a 100% maintenance query */
|
||||
next_time = usec + USEC_PER_SEC;
|
||||
s->rr_ttl_state = MDNS_TTL_100_PERCENT;
|
||||
}
|
||||
|
||||
usec_t jitter = mdns_maintenance_jitter(rr->ttl);
|
||||
|
||||
r = sd_event_add_time(
|
||||
sb->m->event,
|
||||
&s->schedule_event,
|
||||
CLOCK_BOOTTIME,
|
||||
usec_add(next_time, jitter),
|
||||
0,
|
||||
mdns_maintenance_query,
|
||||
s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
TAKE_PTR(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dns_remove_service(DnsServiceBrowser *sb, DnsService *service) {
|
||||
assert(sb);
|
||||
assert(service);
|
||||
|
||||
LIST_REMOVE(dns_services, sb->dns_services, service);
|
||||
dns_service_free(service);
|
||||
}
|
||||
|
||||
DnsService *dns_service_free(DnsService *service) {
|
||||
if (!service)
|
||||
return NULL;
|
||||
|
||||
sd_event_source_disable_unref(service->schedule_event);
|
||||
|
||||
if (service->query && DNS_TRANSACTION_IS_LIVE(service->query->state))
|
||||
dns_query_complete(service->query, DNS_TRANSACTION_ABORTED);
|
||||
|
||||
dns_service_browser_unref(service->sb);
|
||||
|
||||
dns_resource_record_unref(service->rr);
|
||||
|
||||
return mfree(service);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsService, dns_service, dns_service_free);
|
||||
|
||||
int mdns_service_update(DnsService *service, DnsResourceRecord *rr, usec_t t) {
|
||||
service->until = rr->until;
|
||||
service->rr->ttl = rr->ttl;
|
||||
|
||||
/* Update the 80% TTL maintenance event based on new record received from the network.
|
||||
* RFC 6762 section 5.2 */
|
||||
usec_t next_time = mdns_maintenance_next_time(service->until, service->rr->ttl, MDNS_TTL_80_PERCENT);
|
||||
usec_t jitter = mdns_maintenance_jitter(service->rr->ttl);
|
||||
|
||||
if (service->schedule_event)
|
||||
return sd_event_source_set_time(service->schedule_event, usec_add(next_time, jitter));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool dns_service_contains(DnsService *services, DnsResourceRecord *rr, int owner_family) {
|
||||
usec_t t = now(CLOCK_BOOTTIME);
|
||||
|
||||
LIST_FOREACH(dns_services, service, services)
|
||||
if (dns_resource_record_equal(rr, service->rr) > 0 && service->family == owner_family) {
|
||||
if (rr->ttl <= 1)
|
||||
return true;
|
||||
|
||||
if (rr->until > service->until)
|
||||
mdns_service_update(service, rr, t);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void dns_browse_services_purge(Manager *m, int family) {
|
||||
int r = 0;
|
||||
|
||||
/* Called after caches are flused.
|
||||
* Clear local service records and notify varlink client. */
|
||||
if (!(m && m->dns_service_browsers))
|
||||
return;
|
||||
|
||||
DnsServiceBrowser *sb;
|
||||
HASHMAP_FOREACH(sb, m->dns_service_browsers) {
|
||||
r = sd_event_source_set_enabled(sb->schedule_event, SD_EVENT_OFF);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
if (family == AF_UNSPEC) {
|
||||
r = mdns_browser_lookup_cache(sb, AF_INET);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
r = mdns_browser_lookup_cache(sb, AF_INET6);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
return;
|
||||
}
|
||||
|
||||
r = mdns_browser_lookup_cache(sb, family);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
finish:
|
||||
if (r < 0)
|
||||
log_error_errno(r, "mdns browse services purge failed: %m");
|
||||
return;
|
||||
}
|
||||
|
||||
int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int owner_family) {
|
||||
DnsResourceRecord *i;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
|
||||
int r;
|
||||
|
||||
assert(sb);
|
||||
|
||||
/* Check for new service added */
|
||||
DNS_ANSWER_FOREACH(i, answer) {
|
||||
_cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL;
|
||||
|
||||
if (dns_service_contains(sb->dns_services, i, owner_family))
|
||||
continue;
|
||||
|
||||
r = dns_service_split(i->ptr.name, &name, &type, &domain);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
if (!name) {
|
||||
type = mfree(type);
|
||||
domain = mfree(domain);
|
||||
r = dns_service_split(dns_resource_key_name(i->key), &name, &type, &domain);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!type)
|
||||
continue;
|
||||
|
||||
r = dns_add_new_service(sb, i, owner_family);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
log_debug("Add into the list %s, %s, %s, %s, %d",
|
||||
strna(name),
|
||||
strna(type),
|
||||
strna(domain),
|
||||
strna(af_to_ipv4_ipv6(owner_family)),
|
||||
sb->ifindex);
|
||||
|
||||
r = sd_json_buildo(&entry,
|
||||
SD_JSON_BUILD_PAIR("add_flag", SD_JSON_BUILD_BOOLEAN(true)),
|
||||
SD_JSON_BUILD_PAIR("family", SD_JSON_BUILD_INTEGER(owner_family)),
|
||||
SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(name?: "")),
|
||||
SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(type?: "")),
|
||||
SD_JSON_BUILD_PAIR("domain", SD_JSON_BUILD_STRING(domain?: "")),
|
||||
SD_JSON_BUILD_PAIR("interface", SD_JSON_BUILD_INTEGER(sb->ifindex)));
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = sd_json_variant_append_array(&array, entry);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Check for services removed */
|
||||
LIST_FOREACH(dns_services, service, sb->dns_services) {
|
||||
_cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL;
|
||||
|
||||
if (service->family != owner_family)
|
||||
continue;
|
||||
|
||||
if (dns_answer_contains(answer, service->rr))
|
||||
continue;
|
||||
|
||||
r = dns_service_split(service->rr->ptr.name, &name, &type, &domain);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
if (!name) {
|
||||
type = mfree(type);
|
||||
domain = mfree(domain);
|
||||
r = dns_service_split(dns_resource_key_name(service->rr->key), &name, &type, &domain);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
dns_remove_service(sb, service);
|
||||
|
||||
log_debug("Remove from the list %s, %s, %s, %s, %d",
|
||||
strna(name),
|
||||
strna(type),
|
||||
strna(domain),
|
||||
strna(af_to_ipv4_ipv6(owner_family)),
|
||||
sb->ifindex);
|
||||
|
||||
r = sd_json_buildo(&entry,
|
||||
SD_JSON_BUILD_PAIR("add_flag", SD_JSON_BUILD_BOOLEAN(false)),
|
||||
SD_JSON_BUILD_PAIR("family", SD_JSON_BUILD_INTEGER(owner_family)),
|
||||
SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(name?: "")),
|
||||
SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(type?: "")),
|
||||
SD_JSON_BUILD_PAIR("domain", SD_JSON_BUILD_STRING(domain?: "")),
|
||||
SD_JSON_BUILD_PAIR("interface", SD_JSON_BUILD_INTEGER(sb->ifindex)));
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = sd_json_variant_append_array(&array, entry);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!sd_json_variant_is_blank_array(array)) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *vm = NULL;
|
||||
|
||||
r = sd_json_buildo(&vm,
|
||||
SD_JSON_BUILD_PAIR("browser_service_data", SD_JSON_BUILD_VARIANT(array)));
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = sd_varlink_notify(sb->link, vm);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
log_error_errno(r, "Failed to process received services: %m");
|
||||
return sd_varlink_error_errno(sb->link, r);
|
||||
}
|
||||
|
||||
int mdns_browser_lookup_cache(DnsServiceBrowser *sb, int owner_family) {
|
||||
_cleanup_(dns_answer_unrefp) DnsAnswer *lookup_ret_answer = NULL;
|
||||
DnsScope *scope;
|
||||
int r;
|
||||
|
||||
assert(sb);
|
||||
assert(sb->m);
|
||||
|
||||
scope = manager_find_scope_from_protocol(sb->m, sb->ifindex, DNS_PROTOCOL_MDNS, owner_family);
|
||||
if (!scope)
|
||||
return 0;
|
||||
|
||||
dns_cache_prune(&scope->cache);
|
||||
|
||||
r = dns_cache_lookup(
|
||||
&scope->cache,
|
||||
sb->key,
|
||||
sb->flags,
|
||||
NULL,
|
||||
&lookup_ret_answer,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return mdns_manage_services_answer(sb, lookup_ret_answer, owner_family);
|
||||
}
|
||||
|
||||
int mdns_notify_browsers_goodbye(DnsScope *scope) {
|
||||
DnsServiceBrowser *sb = NULL;
|
||||
int r;
|
||||
|
||||
if (!scope)
|
||||
return 0;
|
||||
|
||||
HASHMAP_FOREACH(sb, scope->manager->dns_service_browsers) {
|
||||
r = mdns_browser_lookup_cache(sb, scope->family);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
return r;
|
||||
}
|
||||
|
||||
int mdns_notify_browsers_unsolicited_updates(Manager *m, DnsAnswer *answer, int owner_family) {
|
||||
DnsServiceBrowser *sb = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
if (!answer)
|
||||
return 0;
|
||||
|
||||
if (!m->dns_service_browsers)
|
||||
return 0;
|
||||
|
||||
HASHMAP_FOREACH(sb, m->dns_service_browsers) {
|
||||
r = dns_answer_match_key(answer, sb->key, NULL);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
else if (r == 0)
|
||||
continue;
|
||||
|
||||
r = mdns_browser_lookup_cache(sb, owner_family);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
return log_error_errno(r, "Failed to notify mDNS service subscribers, %m");
|
||||
}
|
||||
|
||||
static void mdns_browse_service_query_complete(DnsQuery *q) {
|
||||
_cleanup_(dns_service_browser_unrefp) DnsServiceBrowser *sb = NULL;
|
||||
_cleanup_(dns_query_freep) DnsQuery *query = q;
|
||||
int r;
|
||||
|
||||
assert(query);
|
||||
assert(query->manager);
|
||||
|
||||
if (query->state != DNS_TRANSACTION_SUCCESS)
|
||||
return;
|
||||
|
||||
sb = dns_service_browser_ref(hashmap_get(query->manager->dns_service_browsers, query->varlink_request));
|
||||
if (!sb)
|
||||
return;
|
||||
|
||||
r = dns_answer_match_key(query->answer, sb->key, NULL);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
else if (r == 0)
|
||||
return;
|
||||
|
||||
r = mdns_browser_lookup_cache(sb, query->answer_family);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
/* When the query is answered from cache, we only get answers for one answer_family
|
||||
* i.e. either ipv4 or ipv6.
|
||||
* We need to perform another cache lookup for the other answer_family */
|
||||
if (query->answer_query_flags == SD_RESOLVED_FROM_CACHE) {
|
||||
r = mdns_browser_lookup_cache(sb, query->answer_family == AF_INET? AF_INET6 : AF_INET);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
return;
|
||||
finish:
|
||||
log_error_errno(r, "mDNS browse query complete failed, %m");
|
||||
}
|
||||
|
||||
static int mdns_next_query_schedule(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
_cleanup_(dns_service_browser_unrefp) DnsServiceBrowser *sb = NULL;
|
||||
_cleanup_(dns_query_freep) DnsQuery *q = NULL;
|
||||
int r;
|
||||
|
||||
assert(userdata);
|
||||
|
||||
sb = dns_service_browser_ref(userdata);
|
||||
|
||||
r = dns_query_new(sb->m, &q, sb->question_utf8, sb->question_idna, NULL, sb->ifindex, sb->flags);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
q->complete = mdns_browse_service_query_complete;
|
||||
q->varlink_request = sd_varlink_ref(sb->link);
|
||||
sd_varlink_set_userdata(sb->link, q);
|
||||
|
||||
r = dns_query_go(q);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
/* RFC6762 5.2
|
||||
* The intervals between successive queries MUST increase by at least a factor of two.
|
||||
* When the interval between queries reaches or exceeds 60 minutes,perform
|
||||
* subsequent queries at a steady-state rate of one query per hour */
|
||||
if (sb->delay == 0) {
|
||||
sb->delay++;
|
||||
/* First query is sent wihtout SD_RESOLVED_NO_CACHE to fetch answers already in cache.
|
||||
* Set SD_RESOLVED_NO_CACHE to make all subsequent queries go to the network. */
|
||||
sb->flags |= SD_RESOLVED_NO_CACHE;
|
||||
}
|
||||
else
|
||||
sb->delay = sb->delay < 2048 ? sb->delay * 2 : 3600;
|
||||
|
||||
r = event_reset_time_relative(
|
||||
sb->m->event, &sb->schedule_event,
|
||||
CLOCK_BOOTTIME, (sb->delay * USEC_PER_SEC),
|
||||
0, mdns_next_query_schedule,
|
||||
sb, 0, "mdns-next-query-schedule", true);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
TAKE_PTR(q);
|
||||
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
return log_error_errno(r, "Failed to schedule mDNS query, %m");
|
||||
}
|
||||
|
||||
void dns_service_browser_reset(Manager *m) {
|
||||
int r;
|
||||
|
||||
if (!(m && m->dns_service_browsers))
|
||||
return;
|
||||
|
||||
DnsServiceBrowser *sb;
|
||||
|
||||
HASHMAP_FOREACH(sb, m->dns_service_browsers) {
|
||||
sb->delay = 0;
|
||||
|
||||
r = event_reset_time_relative(
|
||||
sb->m->event, &sb->schedule_event,
|
||||
CLOCK_BOOTTIME, (sb->delay * USEC_PER_SEC),
|
||||
0, mdns_next_query_schedule,
|
||||
sb, 0, "mdns-next-query-schedule", true);
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed to reset mdns service subscriber, %m");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int dns_subscribe_browse_service(
|
||||
Manager *m,
|
||||
sd_varlink *link,
|
||||
const char *domain,
|
||||
const char *name,
|
||||
const char *type,
|
||||
int ifindex,
|
||||
uint64_t flags) {
|
||||
|
||||
_cleanup_(dns_service_browser_unrefp) DnsServiceBrowser *sb = NULL;
|
||||
_cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(link);
|
||||
|
||||
if (ifindex <= 0)
|
||||
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex"));
|
||||
|
||||
if (isempty(name))
|
||||
name = NULL;
|
||||
else if (!dns_service_name_is_valid(name))
|
||||
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name"));
|
||||
|
||||
if (isempty(type))
|
||||
type = NULL;
|
||||
else if (!dnssd_srv_type_is_valid(type))
|
||||
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type"));
|
||||
|
||||
r = dns_name_is_valid(domain);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("domain"));
|
||||
|
||||
r = dns_question_new_service_type(&question_utf8, name, type, domain, false, DNS_TYPE_PTR);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dns_question_new_service_type(&question_idna, name, type, domain, true, DNS_TYPE_PTR);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
sb = new(DnsServiceBrowser, 1);
|
||||
if (!sb)
|
||||
return log_oom();
|
||||
|
||||
*sb = (DnsServiceBrowser) {
|
||||
.n_ref = 1,
|
||||
.m = m,
|
||||
.link = sd_varlink_ref(link),
|
||||
.question_utf8 = dns_question_ref(question_utf8),
|
||||
.question_idna = dns_question_ref(question_idna),
|
||||
.key = dns_question_first_key(question_utf8),
|
||||
.ifindex = ifindex,
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
/* Only mDNS continuous querying is currently supported. See RFC 6762 */
|
||||
switch (flags & SD_RESOLVED_PROTOCOLS_ALL) {
|
||||
case SD_RESOLVED_MDNS:
|
||||
r = sd_event_add_time(m->event,
|
||||
&sb->schedule_event,
|
||||
CLOCK_BOOTTIME,
|
||||
usec_add(now(CLOCK_BOOTTIME), (sb->delay * USEC_PER_SEC)),
|
||||
0,
|
||||
mdns_next_query_schedule,
|
||||
sb);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
r = hashmap_ensure_put(&m->dns_service_browsers, NULL, link, sb);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
TAKE_PTR(sb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DnsServiceBrowser *dns_service_browser_free(DnsServiceBrowser *sb) {
|
||||
DnsQuery *q;
|
||||
|
||||
if (!sb)
|
||||
return NULL;
|
||||
|
||||
LIST_FOREACH(dns_services, service, sb->dns_services)
|
||||
dns_remove_service(sb, service);
|
||||
|
||||
sd_event_source_disable_unref(sb->schedule_event);
|
||||
|
||||
q = sd_varlink_get_userdata(sb->link);
|
||||
if (q && DNS_TRANSACTION_IS_LIVE(q->state))
|
||||
dns_query_complete(q, DNS_TRANSACTION_ABORTED);
|
||||
|
||||
dns_question_unref(sb->question_idna);
|
||||
dns_question_unref(sb->question_utf8);
|
||||
|
||||
sd_varlink_unref(sb->link);
|
||||
|
||||
return mfree(sb);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsServiceBrowser, dns_service_browser, dns_service_browser_free);
|
|
@ -0,0 +1,79 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct DnsServiceBrowser DnsServiceBrowser;
|
||||
|
||||
#include "resolved-dns-query.h"
|
||||
#include "resolved-manager.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
typedef struct DnsService DnsService;
|
||||
|
||||
typedef enum DnsRecordTTLState DnsRecordTTLState;
|
||||
|
||||
enum DnsRecordTTLState {
|
||||
MDNS_TTL_80_PERCENT,
|
||||
MDNS_TTL_85_PERCENT,
|
||||
MDNS_TTL_90_PERCENT,
|
||||
MDNS_TTL_95_PERCENT,
|
||||
MDNS_TTL_100_PERCENT
|
||||
};
|
||||
|
||||
struct DnsService {
|
||||
unsigned n_ref;
|
||||
DnsServiceBrowser *sb;
|
||||
sd_event_source *schedule_event;
|
||||
DnsResourceRecord *rr;
|
||||
int family;
|
||||
usec_t until;
|
||||
DnsRecordTTLState rr_ttl_state;
|
||||
DnsQuery *query;
|
||||
LIST_FIELDS(DnsService, dns_services);
|
||||
};
|
||||
|
||||
struct DnsServiceBrowser {
|
||||
unsigned n_ref;
|
||||
Manager *m;
|
||||
sd_varlink *link;
|
||||
DnsQuestion *question_idna;
|
||||
DnsQuestion *question_utf8;
|
||||
uint64_t flags;
|
||||
sd_event_source *schedule_event;
|
||||
usec_t delay;
|
||||
DnsResourceKey *key;
|
||||
int ifindex;
|
||||
uint64_t token;
|
||||
LIST_HEAD(DnsService, dns_services);
|
||||
};
|
||||
|
||||
DnsServiceBrowser *dns_service_browser_free(DnsServiceBrowser *sb);
|
||||
void dns_remove_service(DnsServiceBrowser *sb, DnsService *service);
|
||||
DnsService *dns_service_free(DnsService *service);
|
||||
|
||||
DnsServiceBrowser* dns_service_browser_ref(DnsServiceBrowser *sb);
|
||||
DnsServiceBrowser* dns_service_browser_unref(DnsServiceBrowser *sb);
|
||||
|
||||
DnsService* dns_service_ref(DnsService *service);
|
||||
DnsService* dns_service_unref(DnsService *service);
|
||||
|
||||
void dns_browse_services_purge(Manager *m, int family);
|
||||
void dns_service_browser_reset(Manager *m);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServiceBrowser*, dns_service_browser_unref);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsService*, dns_service_unref);
|
||||
|
||||
bool dns_service_contains(DnsService *services, DnsResourceRecord *rr, int owner_family);
|
||||
int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int owner_family);
|
||||
int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_family);
|
||||
int mdns_service_update(DnsService *service, DnsResourceRecord *rr, usec_t t);
|
||||
int mdns_browser_lookup_cache(DnsServiceBrowser *sb, int owner_family);
|
||||
int dns_subscribe_browse_service(Manager *m,
|
||||
sd_varlink *link,
|
||||
const char *domain,
|
||||
const char * name,
|
||||
const char * type,
|
||||
int ifindex,
|
||||
uint64_t flags);
|
||||
int mdns_notify_browsers_unsolicited_updates(Manager *m, DnsAnswer *answer, int owner_family);
|
||||
int mdns_notify_browsers_goodbye(DnsScope *scope);
|
|
@ -1011,6 +1011,7 @@ static int answer_add_clamp_ttl(
|
|||
}
|
||||
}
|
||||
|
||||
rr->until = until;
|
||||
r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
|
|
@ -416,6 +416,66 @@ int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_
|
|||
return 0;
|
||||
}
|
||||
|
||||
int dns_question_new_service_type(
|
||||
DnsQuestion **ret,
|
||||
const char *service,
|
||||
const char *type,
|
||||
const char *domain,
|
||||
bool convert_idna,
|
||||
uint16_t record_type) {
|
||||
|
||||
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
|
||||
_cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
|
||||
_cleanup_free_ char *buf = NULL, *joined = NULL;
|
||||
const char *name;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
if (record_type == DNS_TYPE_SRV)
|
||||
return -EINVAL;
|
||||
|
||||
if (!domain)
|
||||
return -EINVAL;
|
||||
|
||||
if (type) {
|
||||
if (convert_idna) {
|
||||
r = dns_name_apply_idna(domain, &buf);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
domain = buf;
|
||||
}
|
||||
|
||||
r = dns_service_join(service, type, domain, &joined);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
name = joined;
|
||||
} else {
|
||||
if (service)
|
||||
return -EINVAL;
|
||||
|
||||
name = domain;
|
||||
}
|
||||
|
||||
q = dns_question_new(1);
|
||||
if (!q)
|
||||
return -ENOMEM;
|
||||
|
||||
key = dns_resource_key_new(DNS_CLASS_IN, record_type, name);
|
||||
if (!key)
|
||||
return -ENOMEM;
|
||||
|
||||
r = dns_question_add(q, key, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(q);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dns_question_new_service(
|
||||
DnsQuestion **ret,
|
||||
const char *service,
|
||||
|
|
|
@ -31,6 +31,7 @@ DnsQuestion *dns_question_unref(DnsQuestion *q);
|
|||
int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna);
|
||||
int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a);
|
||||
int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna);
|
||||
int dns_question_new_service_type(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool convert_idna, uint16_t record_type);
|
||||
|
||||
int dns_question_add_raw(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags);
|
||||
int dns_question_add(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags);
|
||||
|
|
|
@ -398,6 +398,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) {
|
|||
.n_ref = 1,
|
||||
.key = dns_resource_key_ref(key),
|
||||
.expiry = USEC_INFINITY,
|
||||
.until = USEC_INFINITY,
|
||||
.n_skip_labels_signer = UINT8_MAX,
|
||||
.n_skip_labels_source = UINT8_MAX,
|
||||
};
|
||||
|
@ -1704,6 +1705,7 @@ DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) {
|
|||
|
||||
copy->ttl = rr->ttl;
|
||||
copy->expiry = rr->expiry;
|
||||
copy->until = rr->until;
|
||||
copy->n_skip_labels_signer = rr->n_skip_labels_signer;
|
||||
copy->n_skip_labels_source = rr->n_skip_labels_source;
|
||||
copy->unparsable = rr->unparsable;
|
||||
|
|
|
@ -107,6 +107,8 @@ struct DnsResourceRecord {
|
|||
unsigned n_ref;
|
||||
uint32_t ttl;
|
||||
usec_t expiry; /* RRSIG signature expiry */
|
||||
usec_t until; /* Used to pass until of a record when doing a dns_cache_lookup().
|
||||
* Needed to schedule cache maintenance queries when browsing for services. */
|
||||
|
||||
DnsResourceKey *key;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "hostname-util.h"
|
||||
#include "missing_network.h"
|
||||
#include "random-util.h"
|
||||
#include "resolved-dns-browse-services.h"
|
||||
#include "resolved-dnssd.h"
|
||||
#include "resolved-dns-scope.h"
|
||||
#include "resolved-dns-synthesize.h"
|
||||
|
@ -121,6 +122,9 @@ DnsScope* dns_scope_free(DnsScope *s) {
|
|||
dns_cache_flush(&s->cache);
|
||||
dns_zone_flush(&s->zone);
|
||||
|
||||
/* Clear records of mDNS service browse subscriber, since cache bas been flushed */
|
||||
dns_browse_services_purge(s->manager, s->family);
|
||||
|
||||
LIST_REMOVE(scopes, s->manager->dns_scopes, s);
|
||||
return mfree(s);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "mkdir.h"
|
||||
#include "netif-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "resolved-dns-browse-services.h"
|
||||
#include "resolved-link.h"
|
||||
#include "resolved-llmnr.h"
|
||||
#include "resolved-mdns.h"
|
||||
|
@ -166,6 +167,7 @@ void link_allocate_scopes(Link *l) {
|
|||
r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET);
|
||||
if (r < 0)
|
||||
log_link_warning_errno(l, r, "Failed to allocate mDNS IPv4 scope, ignoring: %m");
|
||||
dns_service_browser_reset(l->manager);
|
||||
}
|
||||
} else
|
||||
l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope);
|
||||
|
@ -176,6 +178,7 @@ void link_allocate_scopes(Link *l) {
|
|||
r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6);
|
||||
if (r < 0)
|
||||
log_link_warning_errno(l, r, "Failed to allocate mDNS IPv6 scope, ignoring: %m");
|
||||
dns_service_browser_reset(l->manager);
|
||||
}
|
||||
} else
|
||||
l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope);
|
||||
|
|
|
@ -769,6 +769,7 @@ int manager_start(Manager *m) {
|
|||
Manager *manager_free(Manager *m) {
|
||||
Link *l;
|
||||
DnssdService *s;
|
||||
DnsServiceBrowser *sb;
|
||||
|
||||
if (!m)
|
||||
return NULL;
|
||||
|
@ -840,6 +841,10 @@ Manager *manager_free(Manager *m) {
|
|||
dns_trust_anchor_flush(&m->trust_anchor);
|
||||
manager_etc_hosts_flush(m);
|
||||
|
||||
while ((sb = hashmap_first(m->dns_service_browsers)))
|
||||
dns_service_browser_free(sb);
|
||||
hashmap_free(m->dns_service_browsers);
|
||||
|
||||
return mfree(m);
|
||||
}
|
||||
|
||||
|
@ -1474,29 +1479,28 @@ bool manager_packet_from_our_transaction(Manager *m, DnsPacket *p) {
|
|||
return t->sent && dns_packet_equal(t->sent, p);
|
||||
}
|
||||
|
||||
DnsScope* manager_find_scope(Manager *m, DnsPacket *p) {
|
||||
DnsScope* manager_find_scope_from_protocol(Manager *m, int ifindex, DnsProtocol protocol, int family) {
|
||||
Link *l;
|
||||
|
||||
assert(m);
|
||||
assert(p);
|
||||
|
||||
l = hashmap_get(m->links, INT_TO_PTR(p->ifindex));
|
||||
l = hashmap_get(m->links, INT_TO_PTR(ifindex));
|
||||
if (!l)
|
||||
return NULL;
|
||||
|
||||
switch (p->protocol) {
|
||||
switch (protocol) {
|
||||
case DNS_PROTOCOL_LLMNR:
|
||||
if (p->family == AF_INET)
|
||||
if (family == AF_INET)
|
||||
return l->llmnr_ipv4_scope;
|
||||
else if (p->family == AF_INET6)
|
||||
else if (family == AF_INET6)
|
||||
return l->llmnr_ipv6_scope;
|
||||
|
||||
break;
|
||||
|
||||
case DNS_PROTOCOL_MDNS:
|
||||
if (p->family == AF_INET)
|
||||
if (family == AF_INET)
|
||||
return l->mdns_ipv4_scope;
|
||||
else if (p->family == AF_INET6)
|
||||
else if (family == AF_INET6)
|
||||
return l->mdns_ipv6_scope;
|
||||
|
||||
break;
|
||||
|
@ -1706,6 +1710,9 @@ void manager_flush_caches(Manager *m, int log_level) {
|
|||
LIST_FOREACH(scopes, scope, m->dns_scopes)
|
||||
dns_cache_flush(&scope->cache);
|
||||
|
||||
dns_browse_services_purge(m, AF_UNSPEC); /* Clear records of DNS service browse subscriber, since caches are flushed */
|
||||
dns_service_browser_reset(m);
|
||||
|
||||
log_full(log_level, "Flushed all caches.");
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
typedef struct Manager Manager;
|
||||
|
||||
#include "resolved-dns-browse-services.h"
|
||||
#include "resolved-dns-query.h"
|
||||
#include "resolved-dns-search-domain.h"
|
||||
#include "resolved-dns-stream.h"
|
||||
|
@ -161,6 +162,9 @@ struct Manager {
|
|||
size_t n_socket_graveyard;
|
||||
|
||||
struct sigrtmin18_info sigrtmin18_info;
|
||||
|
||||
/* Map varlink links to DnsServiceBrowser instances. */
|
||||
Hashmap *dns_service_browsers;
|
||||
};
|
||||
|
||||
/* Manager */
|
||||
|
@ -188,7 +192,13 @@ int manager_next_hostname(Manager *m);
|
|||
bool manager_packet_from_local_address(Manager *m, DnsPacket *p);
|
||||
bool manager_packet_from_our_transaction(Manager *m, DnsPacket *p);
|
||||
|
||||
DnsScope* manager_find_scope(Manager *m, DnsPacket *p);
|
||||
DnsScope* manager_find_scope_from_protocol(Manager *m, int ifindex, DnsProtocol protocol, int family);
|
||||
|
||||
static inline DnsScope* manager_find_scope(Manager *m, DnsPacket *p) {
|
||||
assert(m);
|
||||
assert(p);
|
||||
return manager_find_scope_from_protocol(m, p->ifindex, p->protocol, p->family);
|
||||
}
|
||||
|
||||
void manager_verify_all(Manager *m);
|
||||
|
||||
|
|
|
@ -359,6 +359,10 @@ static int mdns_goodbye_callback(sd_event_source *s, uint64_t usec, void *userda
|
|||
|
||||
dns_cache_prune(&scope->cache);
|
||||
|
||||
r = mdns_notify_browsers_goodbye(scope);
|
||||
if (r < 0)
|
||||
log_error_errno(r, "mDNS: Failed to notify service subscribers of goodbyes, %m");
|
||||
|
||||
if (dns_cache_expiry_in_one_second(&scope->cache, usec)) {
|
||||
r = sd_event_add_time_relative(
|
||||
scope->manager->event,
|
||||
|
@ -380,6 +384,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
|
|||
Manager *m = userdata;
|
||||
DnsScope *scope;
|
||||
int r;
|
||||
bool unsolicited_packet = true;
|
||||
|
||||
r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p);
|
||||
if (r <= 0)
|
||||
|
@ -455,30 +460,6 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
|
|||
}
|
||||
}
|
||||
|
||||
for (bool match = true; match;) {
|
||||
match = false;
|
||||
LIST_FOREACH(transactions_by_scope, t, scope->transactions) {
|
||||
if (t->state != DNS_TRANSACTION_PENDING)
|
||||
continue;
|
||||
|
||||
r = dns_answer_match_key(p->answer, dns_transaction_key(t), NULL);
|
||||
if (r <= 0) {
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to match resource key, ignoring: %m");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* This packet matches the transaction, let's pass it on as reply */
|
||||
dns_transaction_process_reply(t, p, false);
|
||||
|
||||
/* The dns_transaction_process_reply() -> dns_transaction_complete() ->
|
||||
* dns_query_candidate_stop() may free multiple transactions. Hence, restart
|
||||
* the loop. */
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dns_cache_put(
|
||||
&scope->cache,
|
||||
scope->manager->enable_cache,
|
||||
|
@ -494,6 +475,35 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
|
|||
&p->sender,
|
||||
scope->manager->stale_retention_usec);
|
||||
|
||||
for (bool match = true; match;) {
|
||||
match = false;
|
||||
LIST_FOREACH(transactions_by_scope, t, scope->transactions) {
|
||||
if (t->state != DNS_TRANSACTION_PENDING)
|
||||
continue;
|
||||
|
||||
r = dns_answer_match_key(p->answer, dns_transaction_key(t), NULL);
|
||||
if (r <= 0) {
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to match resource key, ignoring: %m");
|
||||
continue;
|
||||
}
|
||||
|
||||
unsolicited_packet = false;
|
||||
/* This packet matches the transaction, let's pass it on as reply */
|
||||
dns_transaction_process_reply(t, p, false);
|
||||
|
||||
/* The dns_transaction_process_reply() -> dns_transaction_complete() ->
|
||||
* dns_query_candidate_stop() may free multiple transactions. Hence, restart
|
||||
* the loop. */
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check incoming packet key matches with active clients if yes update the same */
|
||||
if (unsolicited_packet)
|
||||
mdns_notify_browsers_unsolicited_updates(m, p->answer, p->family);
|
||||
|
||||
} else if (dns_packet_validate_query(p) > 0) {
|
||||
log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "glyph-util.h"
|
||||
#include "in-addr-util.h"
|
||||
#include "json-util.h"
|
||||
#include "resolved-dns-browse-services.h"
|
||||
#include "resolved-dns-synthesize.h"
|
||||
#include "resolved-varlink.h"
|
||||
#include "socket-netlink.h"
|
||||
|
@ -30,11 +31,26 @@ typedef struct LookupParametersResolveService {
|
|||
uint64_t flags;
|
||||
} LookupParametersResolveService;
|
||||
|
||||
typedef struct LookupParametersMdnsBrowse {
|
||||
char *domainName;
|
||||
char *name;
|
||||
char *type;
|
||||
int ifindex;
|
||||
uint64_t flags;
|
||||
} LookupParametersMdnsBrowse;
|
||||
|
||||
static void lookup_parameters_destroy(LookupParameters *p) {
|
||||
assert(p);
|
||||
free(p->name);
|
||||
}
|
||||
|
||||
static void lookup_parameters_mdns_destroy(LookupParametersMdnsBrowse *p) {
|
||||
assert(p);
|
||||
free(p->domainName);
|
||||
free(p->name);
|
||||
free(p->type);
|
||||
}
|
||||
|
||||
static int reply_query_state(DnsQuery *q) {
|
||||
|
||||
assert(q);
|
||||
|
@ -108,10 +124,18 @@ static int reply_query_state(DnsQuery *q) {
|
|||
|
||||
static void vl_on_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata) {
|
||||
DnsQuery *q;
|
||||
Manager *m;
|
||||
|
||||
assert(s);
|
||||
assert(link);
|
||||
|
||||
m = sd_varlink_server_get_userdata(s);
|
||||
if (!m)
|
||||
return;
|
||||
|
||||
DnsServiceBrowser *sb = hashmap_remove(m->dns_service_browsers, link);
|
||||
dns_service_browser_free(sb);
|
||||
|
||||
q = sd_varlink_get_userdata(link);
|
||||
if (!q)
|
||||
return;
|
||||
|
@ -1209,6 +1233,43 @@ static int verify_polkit(sd_varlink *link, sd_json_variant *parameters, const ch
|
|||
&m->polkit_registry);
|
||||
}
|
||||
|
||||
static int vl_method_start_browse(sd_varlink* link, sd_json_variant* parameters, sd_varlink_method_flags_t flags, void* userdata) {
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "domainName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LookupParametersMdnsBrowse, domainName), SD_JSON_MANDATORY },
|
||||
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LookupParametersMdnsBrowse, name), 0 },
|
||||
{ "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LookupParametersMdnsBrowse, type), 0 },
|
||||
{ "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParametersMdnsBrowse, ifindex), SD_JSON_MANDATORY },
|
||||
{ "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParametersMdnsBrowse, flags), SD_JSON_MANDATORY },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(lookup_parameters_mdns_destroy) LookupParametersMdnsBrowse p = {};
|
||||
Manager *m;
|
||||
int r = 0;
|
||||
|
||||
assert(link);
|
||||
|
||||
/* if the client didn't set the more flag, it is using us incorrectly */
|
||||
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
|
||||
return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL);
|
||||
|
||||
m = sd_varlink_server_get_userdata(sd_varlink_get_server(link));
|
||||
assert(m);
|
||||
|
||||
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "vl_method_start_browse json_dispatch fail: %m");
|
||||
|
||||
if (!validate_and_mangle_flags(NULL, &p.flags, 0))
|
||||
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
|
||||
|
||||
r = dns_subscribe_browse_service(m, link, p.domainName, p.name, p.type, p.ifindex, p.flags);
|
||||
if (r < 0)
|
||||
return sd_varlink_error_errno(link, r);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int vl_method_subscribe_query_results(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
Manager *m = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link)));
|
||||
int r;
|
||||
|
@ -1422,7 +1483,8 @@ static int varlink_main_server_init(Manager *m) {
|
|||
"io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname,
|
||||
"io.systemd.Resolve.ResolveAddress", vl_method_resolve_address,
|
||||
"io.systemd.Resolve.ResolveService", vl_method_resolve_service,
|
||||
"io.systemd.Resolve.ResolveRecord", vl_method_resolve_record);
|
||||
"io.systemd.Resolve.ResolveRecord", vl_method_resolve_record,
|
||||
"io.systemd.Resolve.StartBrowse", vl_method_start_browse);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to register varlink methods: %m");
|
||||
|
||||
|
|
|
@ -102,6 +102,15 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
|||
SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0));
|
||||
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
ServiceData,
|
||||
SD_VARLINK_DEFINE_FIELD(add_flag, SD_VARLINK_BOOL, 0),
|
||||
SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, 0),
|
||||
SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_DEFINE_FIELD(type, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_DEFINE_FIELD(domain, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_DEFINE_FIELD(interface, SD_VARLINK_INT, 0));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
ResolveAddress,
|
||||
SD_VARLINK_FIELD_COMMENT("The Linux interface index for the network interface to search on. Typically left unspecified, in order to search on all interfaces."),
|
||||
|
@ -159,6 +168,15 @@ static SD_VARLINK_DEFINE_METHOD(
|
|||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(rrs, ResolvedRecord, SD_VARLINK_ARRAY),
|
||||
SD_VARLINK_DEFINE_OUTPUT(flags, SD_VARLINK_INT, 0));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
StartBrowse,
|
||||
SD_VARLINK_DEFINE_INPUT(domainName, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_DEFINE_INPUT(type, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_DEFINE_INPUT(ifindex, SD_VARLINK_INT, 0),
|
||||
SD_VARLINK_DEFINE_INPUT(flags, SD_VARLINK_INT, 0),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(browser_service_data, ServiceData, SD_VARLINK_ARRAY));
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(NoNameServers);
|
||||
static SD_VARLINK_DEFINE_ERROR(NoSuchResourceRecord);
|
||||
static SD_VARLINK_DEFINE_ERROR(QueryTimedOut);
|
||||
|
@ -198,6 +216,8 @@ SD_VARLINK_DEFINE_INTERFACE(
|
|||
&vl_method_ResolveService,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Resolves a domain name to one or more DNS resource records."),
|
||||
&vl_method_ResolveRecord,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Starts browsing for mDNS services of specified type."),
|
||||
&vl_method_StartBrowse,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved address."),
|
||||
&vl_type_ResolvedAddress,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved host name."),
|
||||
|
@ -212,6 +232,7 @@ SD_VARLINK_DEFINE_INTERFACE(
|
|||
&vl_type_ResourceRecord,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Encapsulates information about a resolved DNS resource record "),
|
||||
&vl_type_ResolvedRecord,
|
||||
&vl_type_ServiceData,
|
||||
&vl_error_NoNameServers,
|
||||
&vl_error_NoSuchResourceRecord,
|
||||
&vl_error_QueryTimedOut,
|
||||
|
|
|
@ -522,6 +522,7 @@ static int reply_callback(
|
|||
if (!arg_quiet)
|
||||
sd_json_variant_dump(parameters, arg_json_format_flags, stdout, NULL);
|
||||
|
||||
fflush(stdout);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
all setup run clean clean-again:
|
||||
@TEST_BASE_DIR=../ ./test.sh --$@
|
||||
|
||||
.PHONY: all setup run clean clean-again
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
integration_tests += [
|
||||
integration_test_template + {
|
||||
'name' : fs.name(meson.current_source_dir()),
|
||||
'vm' : true,
|
||||
},
|
||||
]
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -e
|
||||
|
||||
TEST_DESCRIPTION="Test for systemd-resolved's mDNS functionality"
|
||||
IMAGE_NAME="resolved-mdns"
|
||||
TEST_NO_NSPAWN=1
|
||||
|
||||
# shellcheck source=test/test-functions
|
||||
. "${TEST_BASE_DIR:?}/test-functions"
|
||||
|
||||
do_test "$@"
|
|
@ -377,6 +377,7 @@ foreach dirname : [
|
|||
'TEST-84-STORAGETM',
|
||||
'TEST-85-NETWORK',
|
||||
'TEST-86-MULTI-PROFILE-UKI',
|
||||
'TEST-87-RESOLVED-MDNS',
|
||||
]
|
||||
subdir(dirname)
|
||||
endforeach
|
||||
|
|
|
@ -80,6 +80,7 @@ show_journal = True # When true, show journal on stopping networkd.
|
|||
|
||||
active_units = []
|
||||
protected_links = {
|
||||
'br0',
|
||||
'erspan0',
|
||||
'gre0',
|
||||
'gretap0',
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Unit]
|
||||
Description=TEST-87-RESOLVED-MDNS
|
||||
|
||||
[Service]
|
||||
ExecStartPre=rm -f /failed /testok
|
||||
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
|
||||
Type=oneshot
|
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/test-control.sh
|
||||
. "$(dirname "$0")"/test-control.sh
|
||||
|
||||
SERVICE_TYPE_COUNT=10
|
||||
SERVICE_COUNT=20
|
||||
CONTAINER_ZONE="test-$RANDOM"
|
||||
CONTAINER_1="test-mdns-1"
|
||||
CONTAINER_2="test-mdns-2"
|
||||
|
||||
# Prepare containers
|
||||
create_container() {
|
||||
local container="${1:?}"
|
||||
local stype sid svc
|
||||
|
||||
# Prepare container's /etc
|
||||
#
|
||||
# Since we also need the various test suite related dropins from the host's /etc,
|
||||
# we'll overlay our customizations on top of that
|
||||
mkdir -p "/var/lib/machines/$container/etc/systemd/dnssd"
|
||||
# Create 20 test services for each service type (_testServiceX._udp) and number them sequentially,
|
||||
# i.e. create services 0-19 for _testService0._udp, services 20-39 for _testService1._udp, and so on
|
||||
for stype in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
|
||||
for sid in $(seq 0 $((SERVICE_COUNT - 1))); do
|
||||
svc=$((stype * SERVICE_COUNT + sid))
|
||||
|
||||
cat >"/var/lib/machines/$container/etc/systemd/dnssd/test-service-$container-$svc.dnssd" <<EOF
|
||||
[Service]
|
||||
Name=Test Service $svc on %H
|
||||
Type=_testService$stype._udp
|
||||
Port=24002
|
||||
TxtText=DC=Monitor PN=867313 SN=XZ051Z0051
|
||||
EOF
|
||||
done
|
||||
done
|
||||
|
||||
# To make things fast, spawn the container with a transient version of what's currently the host's
|
||||
# rootfs, sans a couple of tweaks to make the container unique enough
|
||||
mkdir -p "/run/systemd/system/systemd-nspawn@$container.service.d"
|
||||
cat >"/run/systemd/system/systemd-nspawn@$container.service.d/override.conf" <<EOF
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=systemd-nspawn --quiet --link-journal=try-guest --keep-unit --machine=%i --boot \
|
||||
--volatile=yes --directory=/ \
|
||||
--inaccessible=/etc/machine-id \
|
||||
--inaccessible=/etc/hostname \
|
||||
--resolv-conf=replace-stub \
|
||||
--network-zone=$CONTAINER_ZONE \
|
||||
--overlay=/etc:/var/lib/machines/$container/etc::/etc \
|
||||
--hostname=$container
|
||||
EOF
|
||||
}
|
||||
|
||||
check_both() {
|
||||
local service_id="${1:?}"
|
||||
local result_file="${2:?}"
|
||||
|
||||
# We should get 20 services per container, 40 total
|
||||
if [[ "$(wc -l <"$result_file")" -ge 40 ]]; then
|
||||
# Check if the services we got are the correct ones
|
||||
for i in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
|
||||
svc=$((service_id * SERVICE_COUNT + i))
|
||||
if ! grep "Test Service $svc on $CONTAINER_1" "$result_file" || \
|
||||
! grep "Test Service $svc on $CONTAINER_2" "$result_file"; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
# We got all records and all of them are what we expect
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
check_first() {
|
||||
local service_id="${1:?}"
|
||||
local result_file="${2:?}"
|
||||
|
||||
# We should get 20 services per container
|
||||
if [[ "$(wc -l <"$result_file")" -ge 20 ]]; then
|
||||
# Check if the services we got are the correct ones
|
||||
for i in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
|
||||
svc=$((service_id * SERVICE_COUNT + i))
|
||||
if ! grep "Test Service $svc on $CONTAINER_1" "$result_file"; then
|
||||
return 1
|
||||
fi
|
||||
# This check assumes the second container is unreachable, so this shouldn't happen
|
||||
if grep "Test Service $svc on $CONTAINER_2" "$result_file"; then
|
||||
echo >&2 "Found a record from an unreachable container"
|
||||
cat "$result_file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# We got all records and all of them are what we expect
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
run_and_check_services() {
|
||||
local service_id="${1:?}"
|
||||
local check_func="${2:?}"
|
||||
local unit_name="varlinkctl-$service_id-$SRANDOM.service"
|
||||
local i out_file parameters service_type svc tmp_file
|
||||
|
||||
out_file="$(mktemp)"
|
||||
error_file="$(mktemp)"
|
||||
tmp_file="$(mktemp)"
|
||||
service_type="_testService$service_id._udp"
|
||||
parameters="{ \"domainName\": \"$service_type.local\", \"name\": \"\", \"type\": \"\", \"ifindex\": ${BRIDGE_INDEX:?}, \"flags\": 16785432 }"
|
||||
|
||||
systemd-run --unit="$unit_name" --service-type=exec -p StandardOutput="file:$out_file" -p StandardError="file:$error_file" \
|
||||
varlinkctl call --more /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.StartBrowse "$parameters"
|
||||
|
||||
# shellcheck disable=SC2064
|
||||
# Note: unregister the trap once it's fired, otherwise it'll get propagated to functions that call this
|
||||
# one, *sigh*
|
||||
|
||||
trap "trap - RETURN; systemctl stop $unit_name" RETURN
|
||||
|
||||
for _ in {0..14}; do
|
||||
# The response format, for reference (it's JSON-SEQ):
|
||||
#
|
||||
# {
|
||||
# "browser_service_data": [
|
||||
# {
|
||||
# "add_flag": true,
|
||||
# "family": 10,
|
||||
# "name": "Test Service 13 on test-mdns-1",
|
||||
# "type": "_testService0._udp",
|
||||
# "domain": "local",
|
||||
# "interface": 3
|
||||
# },
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
if [[ -s "$out_file" ]]; then
|
||||
# Extract the service name from each valid record...
|
||||
# jq --slurp --raw-output \
|
||||
# ".[].browser_service_data[] | select(.add_flag == true and .type == \"$service_type\" and .family == 10).name" "$out_file" | sort | tee "$tmp_file"
|
||||
grep -o '"name":"[^"]*"' "$out_file" | sed 's/"name":"//;s/"//g' | sort | tee "$tmp_file"
|
||||
# ...and compare them with what we expect
|
||||
if "$check_func" "$service_id" "$tmp_file"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
cat "$out_file"
|
||||
cat "$error_file"
|
||||
return 1
|
||||
}
|
||||
|
||||
testcase_all_sequential() {
|
||||
: "Test each service type (sequentially)"
|
||||
resolvectl flush-caches
|
||||
for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
|
||||
run_and_check_services "$id" check_both
|
||||
done
|
||||
|
||||
echo testcase_end
|
||||
}
|
||||
|
||||
testcase_all_parallel() {
|
||||
: "Test each service type (in parallel)"
|
||||
resolvectl flush-caches
|
||||
for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
|
||||
run_and_check_services "$id" check_both &
|
||||
done
|
||||
wait
|
||||
}
|
||||
|
||||
testcase_single_service_multiple_times() {
|
||||
: "Test one service type multiple times"
|
||||
resolvectl flush-caches
|
||||
for _ in {0..4}; do
|
||||
run_and_check_services 4 check_both
|
||||
done
|
||||
}
|
||||
|
||||
testcase_second_unreachable() {
|
||||
: "Test each service type while the second container is unreachable"
|
||||
resolvectl flush-caches
|
||||
systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl down host0
|
||||
for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
|
||||
run_and_check_services "$id" check_first
|
||||
done
|
||||
|
||||
|
||||
: "Test each service type after bringing the second container back up again"
|
||||
systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl up host0
|
||||
systemd-run -M "$CONTAINER_2" --wait --pipe -- \
|
||||
/usr/lib/systemd/systemd-networkd-wait-online --ipv4 --ipv6 --interface=host0 --operational-state=degraded --timeout=30
|
||||
for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do
|
||||
run_and_check_services "$id" check_both
|
||||
done
|
||||
}
|
||||
|
||||
: "Setup host & containers"
|
||||
# Note: create the drop-in intentionally under /run/ and copy it manually into the containers
|
||||
mkdir -p /run/systemd/resolved.conf.d/
|
||||
cat >/run/systemd/resolved.conf.d/99-mdns-llmnr.conf <<EOF
|
||||
[Resolve]
|
||||
MulticastDNS=yes
|
||||
LLMNR=yes
|
||||
EOF
|
||||
|
||||
systemctl unmask systemd-resolved.service systemd-networkd.{service,socket} systemd-machined.service
|
||||
systemctl enable --now systemd-resolved.service systemd-networkd.{socket,service} systemd-machined.service
|
||||
ln -svrf /run/systemd/resolved.conf.d/99-mdns-llmnr.conf /etc/resolv.conf
|
||||
systemctl reload systemd-resolved.service systemd-networkd.service
|
||||
|
||||
for container in "$CONTAINER_1" "$CONTAINER_2"; do
|
||||
create_container "$container"
|
||||
mkdir -p "/var/lib/machines/$container/etc/systemd/resolved.conf.d/"
|
||||
cp /run/systemd/resolved.conf.d/99-mdns-llmnr.conf "/var/lib/machines/$container/etc/systemd/resolved.conf.d/"
|
||||
touch "/var/lib/machines/$container/etc/hostname"
|
||||
systemctl daemon-reload
|
||||
machinectl start "$container"
|
||||
# Wait for the system bus to start...
|
||||
timeout 30s bash -xec "while ! systemd-run -M '$container' --wait --pipe true; do sleep 1; done"
|
||||
# ...and from there wait for the machine bootup to finish. We don't really care if the container
|
||||
# boots up in a degraded state, hence the `:`
|
||||
timeout 30s systemd-run -M "$container" --wait --pipe -- systemctl --wait is-system-running || :
|
||||
# Wait until the veth interface is configured and turn on mDNS and LLMNR
|
||||
systemd-run -M "$container" --wait --pipe -- \
|
||||
/usr/lib/systemd/systemd-networkd-wait-online --ipv4 --ipv6 --interface=host0 --operational-state=degraded --timeout=30
|
||||
systemd-run -M "$container" --wait --pipe -- resolvectl mdns host0 yes
|
||||
systemd-run -M "$container" --wait --pipe -- resolvectl llmnr host0 yes
|
||||
systemd-run -M "$container" --wait --pipe -- networkctl status --no-pager
|
||||
systemd-run -M "$container" --wait --pipe -- resolvectl status --no-pager
|
||||
[[ "$(systemd-run -M "$container" --wait --pipe -- resolvectl mdns host0)" =~ :\ yes$ ]]
|
||||
[[ "$(systemd-run -M "$container" --wait --pipe -- resolvectl llmnr host0)" =~ :\ yes$ ]]
|
||||
done
|
||||
|
||||
BRIDGE_INDEX="$(<"/sys/class/net/vz-$CONTAINER_ZONE/ifindex")"
|
||||
machinectl list
|
||||
resolvectl mdns "vz-$CONTAINER_ZONE" on
|
||||
resolvectl llmnr "vz-$CONTAINER_ZONE" on
|
||||
networkctl status
|
||||
resolvectl status
|
||||
|
||||
# Run the actual test cases (functions prefixed by testcase_)
|
||||
run_testcases
|
||||
|
||||
touch /testok
|
Loading…
Reference in New Issue