Compare commits

...

22 Commits

Author SHA1 Message Date
Vishal Chillara a780b08488
Merge e9cfc2bd94 into b7eefa1996 2024-11-21 10:12:45 +01:00
Luca Boccassi b7eefa1996 cgroup-util: fix memory leak on error
CID#1565824

Follow-up for f6793bbcf0
2024-11-21 14:02:34 +09:00
Luca Boccassi 2e5b0412f9
network: update state files before replying bus method (#35255)
Follow-up for 2b07a3211b.

Fixes the failure found in
https://autopkgtest.ubuntu.com/results/autopkgtest-noble-upstream-systemd-ci-systemd-ci/noble/amd64/s/systemd-upstream/20241115_182040_92382@/log.gz
. Relevant logs:
```
Nov 16 02:48:36 systemd-networkd[2706]: veth99: Reconfiguring with /run/systemd/network/25-dhcp-client-ipv6-only.network.
Nov 16 02:48:36 systemd-networkd[2706]: veth99: NDISC: Started IPv6 Router Solicitation client
Nov 16 02:48:36 systemd-networkd[2706]: veth99: IPv6 Router Discovery is configured and started.
Nov 16 02:48:36 systemd-networkd[2706]: veth99: NDISC: Sent Router Solicitation, next solicitation in 3s
Nov 16 02:48:36 systemd-networkd[2706]: veth99: NDISC: Received Router Advertisement from fe80::1034:56ff:fe78:9abd: flags=0xc0(managed, other), preference=medium, lifetime=30min
Nov 16 02:48:36 systemd-networkd[2706]: veth99: NDISC: Invoking callback for 'router' event.
Nov 16 02:48:36 systemd-networkd[2706]: veth99: link_check_ready(): dynamic addressing protocols are enabled but none of them finished yet.
Nov 16 02:48:36 systemd-networkd[2706]: veth99: DHCPv6 client: Starting in Solicit mode
Nov 16 02:48:36 systemd-networkd[2706]: veth99: DHCPv6 client: State changed: stopped -> solicitation
Nov 16 02:48:36 systemd-networkd[2706]: veth99: Acquiring DHCPv6 lease on NDisc request
Nov 16 02:48:36 systemd-networkd[2706]: veth99: DHCPv6 client: Sent Solicit
Nov 16 02:48:36 systemd-networkd[2706]: veth99: DHCPv6 client: Next retransmission in 1s
Nov 16 02:48:37 systemd-networkd[2706]: veth99: DHCPv6 client: Sent Solicit
Nov 16 02:48:37 systemd-networkd[2706]: veth99: DHCPv6 client: Next retransmission in 1s
Nov 16 02:48:39 systemd-networkd[2706]: veth99: NDISC: Received Neighbor Advertisement from fe80::1034:56ff:fe78:9abd: Router=yes, Solicited=yes, Override=no
Nov 16 02:48:39 systemd-networkd[2706]: veth99: NDISC: Invoking callback for 'neighbor' event.
Nov 16 02:48:39 systemd-networkd[2706]: veth99: DHCPv6 client: Processed Reply message
Nov 16 02:48:39 systemd-networkd[2706]: veth99: DHCPv6 client: T1 expires in 50s
Nov 16 02:48:39 systemd-networkd[2706]: veth99: DHCPv6 client: T2 expires in 55s
Nov 16 02:48:39 systemd-networkd[2706]: veth99: DHCPv6 client: Valid lifetime expires in 2min
Nov 16 02:48:39 systemd-networkd[2706]: veth99: DHCPv6 client: State changed: solicitation -> bound
Nov 16 02:48:39 systemd-networkd[2706]: veth99: DHCPv6 address 2600::15/128 (valid for 1min 59s, preferred for 1min 59s)
Nov 16 02:48:41 systemd-networkd[2706]: veth99: Received updated DHCPv6 address (configured): 2600::15/128 (valid for 1min 58s, preferred for 1min 58s), flags: no-prefixroute, scope: global
Nov 16 02:48:41 systemd-networkd[2706]: veth99: DHCPv6 addresses and routes set.
Nov 16 02:48:41 systemd-networkd[2706]: veth99: link_check_ready(): IPv4LL:no DHCPv4:no DHCPv6:yes DHCP-PD:no NDisc:no
Nov 16 02:48:41 systemd-networkd[2706]: veth99: State changed: configuring -> configured
```
The interface veth99 entered the configured state after 5 seconds, but
at the same time, the `wait_online()` in the test script considered the
test failed.
The function `wait_online()` first invokes
`systemd-networkd-wait-online` with `--timeout=20`, then check setup
states of interfaces with 5 seconds timeout. So, the failure suggests
that `systemd-networkd-wait-online` finishes immediately, as the state
file was not updated when it is invoked, and thus it handles the
interface veth99 already in the configured state.
2024-11-20 23:36:35 +00:00
Martin Srebotnjak 69af4849aa po: Translated using Weblate (Slovenian)
Currently translated at 100.0% (257 of 257 strings)

Co-authored-by: Martin Srebotnjak <miles@filmsi.net>
Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/sl/
Translation: systemd/main
2024-11-21 04:17:08 +09:00
Jiri Grönroos 18d4e0be89 po: Translated using Weblate (Finnish)
Currently translated at 100.0% (257 of 257 strings)

Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/fi/
Translation: systemd/main
2024-11-21 04:17:08 +09:00
Dmytro Markevych 7d7b89a015 po: Translated using Weblate (Ukrainian)
Currently translated at 100.0% (257 of 257 strings)

Co-authored-by: Dmytro Markevych <hotr1pak@gmail.com>
Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/uk/
Translation: systemd/main
2024-11-21 04:17:08 +09:00
Léane GRASSER 8a92365f79 po: Translated using Weblate (French)
Currently translated at 100.0% (257 of 257 strings)

Co-authored-by: Léane GRASSER <leane.grasser@proton.me>
Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/fr/
Translation: systemd/main
2024-11-21 04:17:08 +09:00
Yu Watanabe 2b397d43ab test-network: actually check metric and preference
Otherwise, nexthop ID may contain e.g. 300, then
===
AssertionError: '300' unexpectedly found in
'default nhid 3860882700 via fe80::1034:56ff:fe78:9a99 proto ra metric 512 expires 1798sec pref high\n
 default nhid 2639230080 via fe80::1034:56ff:fe78:9a98 proto ra metric 2048 expires 1798sec pref low'
===
2024-11-21 03:43:35 +09:00
Yu Watanabe 9ad294efd0 network: update state files before replying bus method
Follow-up for 2b07a3211b.
2024-11-21 03:42:06 +09:00
Lennart Poettering f6793bbcf0 killall: gracefully handle processes inserted into containers via nsenter -a
"nsenter -a" doesn't migrate the specified process into the target
cgroup (it really should). Thus the cgroup will remain in a cgroup
that is (due to cgroup ns) outside our visibility. The kernel will
report the cgroup path of such cgroups as starting with "/../". Detect
that and print a reasonably error message instead of trying to resolve
that.
2024-11-20 18:11:38 +00:00
Mike Yuan f87863a8ff process-util: refuse to operate on remote PidRef
Follow-up for 7e3e540b88
2024-11-20 18:10:26 +00:00
Antonio Alvarez Feijoo 58c3c2886d cryptenroll: fix typo 2024-11-20 18:03:44 +00:00
Daan De Meyer dbbe895807 test-audit-util: Migrate to new assertion macros 2024-11-20 16:48:55 +00:00
Yu Watanabe 52b0351a15
core/exec-invoke: suppress placeholder home only in build_environment() (#35219)
Alternative to https://github.com/systemd/systemd/pull/34789
Closes #34789
2024-11-20 17:34:25 +09:00
Luca Boccassi fe077a1a58 units: add initrd directory to list of conditions for systemd-confext
systemd-sysext has the same check, but it was forgotten for confexts.
Needed to activate confexts from the ESP in the initrd.
2024-11-20 09:12:24 +01:00
Mike Yuan b718b86e1b
core/exec-invoke: suppress placeholder home only in build_environment()
Currently, get_fixed_user() employs USER_CREDS_SUPPRESS_PLACEHOLDER,
meaning home path is set to NULL if it's empty or root. However,
the path is also used for applying WorkingDirectory=~, and we'd
spuriously use the invoking user's home as fallback even if
User= is changed in that case.

Let's instead delegate such suppression to build_environment(),
so that home is proper initialized for usage at other steps.
shell doesn't actually suffer from such problem, but it's changed
too for consistency.

Alternative to #34789
2024-11-19 00:38:18 +01:00
Mike Yuan d911778877
core/exec-invoke: minor cleanup for apply_working_directory() error handling
Assign exit_status at the same site where error log is emitted,
for readability.
2024-11-19 00:38:18 +01:00
Mike Yuan eea9d3eb10
basic/user-util: split out placeholder suppression from USER_CREDS_CLEAN into its own flag
No functional change, preparation for later commits.
2024-11-19 00:38:18 +01:00
Mike Yuan 579ce77ead
basic/user-util: introduce shell_is_placeholder() helper 2024-11-19 00:38:18 +01:00
Frantisek Sumsal e9cfc2bd94 varlinkctl: flush stdout after each record in --more mode
So things work correctly even if varlinkctl's output is redirected to a
file.
2024-10-29 22:44:13 +05:30
Vishal Chillara Srinivas 08860aa147 test: resolve: add testing for mDNS browse
Co-authored-by: Frantisek Sumsal <frantisek@sumsal.cz>
Co-authored-by: Vishwanath Chandapur <vishwanath.chandapur@philips.com>
2024-10-29 22:43:02 +05:30
Vishal Chillara Srinivas 2e5bf2774e resolved: Implement continuous mDNS querying as per RFC6762 5.2
Allow for mDNS service/domain/types browsing.
A client can connect to the backend via varlink and receive updates as the
requested service becomes available.
The interval between the first two queries MUST be at least one second,
the intervals between successive queries MUST increase by at least a factor of two.
When the interval between queries reaches or exceeds 60 minutes, a querier MAY cap
the interval to a maximum of 60 minutes, and perform subsequent queries at a
steady-state rate of one query per hour.
Delete expired cache entries one second after goodbye packet received
as per RFC6762 Section 10.1

Cache maintenance:
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%.
If an answer is received, then the remaining TTL is reset to the value given
in the answer, and this process repeats for as long as the Multicast DNS querier
has an ongoing interest in the record.
If no answer is received after four queries, the record is deleted when it
reaches 100% of its lifetime.

Co-authored-by: Vishwanath Chandapur <vishwanath.chandapur@philips.com>
2024-10-29 22:38:48 +05:30
39 changed files with 1419 additions and 125 deletions

View File

@ -3,12 +3,13 @@
# Finnish translation of systemd. # Finnish translation of systemd.
# Jan Kuparinen <copper_fin@hotmail.com>, 2021, 2022, 2023. # Jan Kuparinen <copper_fin@hotmail.com>, 2021, 2022, 2023.
# Ricky Tigg <ricky.tigg@gmail.com>, 2022, 2024. # Ricky Tigg <ricky.tigg@gmail.com>, 2022, 2024.
# Jiri Grönroos <jiri.gronroos@iki.fi>, 2024.
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-06 14:42+0000\n" "POT-Creation-Date: 2024-11-06 14:42+0000\n"
"PO-Revision-Date: 2024-09-12 13:43+0000\n" "PO-Revision-Date: 2024-11-20 19:13+0000\n"
"Last-Translator: Ricky Tigg <ricky.tigg@gmail.com>\n" "Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>\n"
"Language-Team: Finnish <https://translate.fedoraproject.org/projects/systemd/" "Language-Team: Finnish <https://translate.fedoraproject.org/projects/systemd/"
"main/fi/>\n" "main/fi/>\n"
"Language: fi\n" "Language: fi\n"
@ -16,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7.2\n" "X-Generator: Weblate 5.8.2\n"
#: src/core/org.freedesktop.systemd1.policy.in:22 #: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system" msgid "Send passphrase back to system"
@ -112,14 +113,12 @@ msgid "Authentication is required to update a user's home area."
msgstr "Todennus vaaditaan käyttäjän kotialueen päivittämiseksi." msgstr "Todennus vaaditaan käyttäjän kotialueen päivittämiseksi."
#: src/home/org.freedesktop.home1.policy:53 #: src/home/org.freedesktop.home1.policy:53
#, fuzzy
msgid "Update your home area" msgid "Update your home area"
msgstr "Päivitä kotialue" msgstr "Päivitä kotialue"
#: src/home/org.freedesktop.home1.policy:54 #: src/home/org.freedesktop.home1.policy:54
#, fuzzy
msgid "Authentication is required to update your home area." msgid "Authentication is required to update your home area."
msgstr "Todennus vaaditaan käyttäjän kotialueen päivittämiseksi." msgstr "Todennus vaaditaan kotialueen päivittämiseksi."
#: src/home/org.freedesktop.home1.policy:63 #: src/home/org.freedesktop.home1.policy:63
msgid "Resize a home area" msgid "Resize a home area"
@ -1174,14 +1173,11 @@ msgstr "Todennus vaaditaan vanhojen järjestelmäpäivitysten puhdistamiseen."
#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 #: src/sysupdate/org.freedesktop.sysupdate1.policy:75
msgid "Manage optional features" msgid "Manage optional features"
msgstr "" msgstr "Hallitse valinnaisia ominaisuuksia"
#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 #: src/sysupdate/org.freedesktop.sysupdate1.policy:76
#, fuzzy
msgid "Authentication is required to manage optional features" msgid "Authentication is required to manage optional features"
msgstr "" msgstr "Todennus vaaditaan valinnaisten ominaisuuksien hallintaan"
"Todennus vaaditaan aktiivisten istuntojen, käyttäjien ja paikkojen "
"hallintaan."
#: src/timedate/org.freedesktop.timedate1.policy:22 #: src/timedate/org.freedesktop.timedate1.policy:22
msgid "Set system time" msgid "Set system time"

View File

@ -12,7 +12,7 @@ msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-06 14:42+0000\n" "POT-Creation-Date: 2024-11-06 14:42+0000\n"
"PO-Revision-Date: 2024-11-07 09:30+0000\n" "PO-Revision-Date: 2024-11-20 19:13+0000\n"
"Last-Translator: Léane GRASSER <leane.grasser@proton.me>\n" "Last-Translator: Léane GRASSER <leane.grasser@proton.me>\n"
"Language-Team: French <https://translate.fedoraproject.org/projects/systemd/" "Language-Team: French <https://translate.fedoraproject.org/projects/systemd/"
"main/fr/>\n" "main/fr/>\n"
@ -360,8 +360,8 @@ msgid ""
"Authentication is required to set the statically configured local hostname, " "Authentication is required to set the statically configured local hostname, "
"as well as the pretty hostname." "as well as the pretty hostname."
msgstr "" msgstr ""
"Une authentification est requise pour définir le nom d'hôte local de manière " "Une authentification est requise pour définir le nom d'hôte local configuré "
"statique, ainsi que le nom d'hôte familier." "de manière statique, ainsi que le nom d'hôte convivial."
#: src/hostname/org.freedesktop.hostname1.policy:41 #: src/hostname/org.freedesktop.hostname1.policy:41
msgid "Set machine information" msgid "Set machine information"

View File

@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: systemd\n" "Project-Id-Version: systemd\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-06 14:42+0000\n" "POT-Creation-Date: 2024-11-06 14:42+0000\n"
"PO-Revision-Date: 2024-08-26 19:38+0000\n" "PO-Revision-Date: 2024-11-20 19:13+0000\n"
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n" "Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
"Language-Team: Slovenian <https://translate.fedoraproject.org/projects/" "Language-Team: Slovenian <https://translate.fedoraproject.org/projects/"
"systemd/main/sl/>\n" "systemd/main/sl/>\n"
@ -17,7 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || " "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
"n%100==4 ? 2 : 3;\n" "n%100==4 ? 2 : 3;\n"
"X-Generator: Weblate 5.7\n" "X-Generator: Weblate 5.8.2\n"
#: src/core/org.freedesktop.systemd1.policy.in:22 #: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system" msgid "Send passphrase back to system"
@ -125,16 +125,13 @@ msgstr ""
"območja." "območja."
#: src/home/org.freedesktop.home1.policy:53 #: src/home/org.freedesktop.home1.policy:53
#, fuzzy
msgid "Update your home area" msgid "Update your home area"
msgstr "Posodobite domače območje" msgstr "Posodobite domače območje"
#: src/home/org.freedesktop.home1.policy:54 #: src/home/org.freedesktop.home1.policy:54
#, fuzzy
msgid "Authentication is required to update your home area." msgid "Authentication is required to update your home area."
msgstr "" msgstr ""
"Preverjanje pristnosti je potrebno za posodobitev uporabnikovega domačega " "Preverjanje pristnosti je potrebno za posodobitev vašega domačega območja."
"območja."
#: src/home/org.freedesktop.home1.policy:63 #: src/home/org.freedesktop.home1.policy:63
msgid "Resize a home area" msgid "Resize a home area"
@ -1234,14 +1231,12 @@ msgstr ""
#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 #: src/sysupdate/org.freedesktop.sysupdate1.policy:75
msgid "Manage optional features" msgid "Manage optional features"
msgstr "" msgstr "Upravljaj dodatne funkcionalnosti"
#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 #: src/sysupdate/org.freedesktop.sysupdate1.policy:76
#, fuzzy
msgid "Authentication is required to manage optional features" msgid "Authentication is required to manage optional features"
msgstr "" msgstr ""
"Preverjanje pristnosti je potrebno za upravljanje aktivnih sej, uporabnikov " "Preverjanje pristnosti je potrebno za upravljanje dodatnih funkcionalnosti."
"in delovišč."
#: src/timedate/org.freedesktop.timedate1.policy:22 #: src/timedate/org.freedesktop.timedate1.policy:22
msgid "Set system time" msgid "Set system time"

View File

@ -4,12 +4,13 @@
# Eugene Melnik <jeka7js@gmail.com>, 2014. # Eugene Melnik <jeka7js@gmail.com>, 2014.
# Daniel Korostil <ted.korostiled@gmail.com>, 2014, 2016, 2018. # Daniel Korostil <ted.korostiled@gmail.com>, 2014, 2016, 2018.
# Yuri Chornoivan <yurchor@ukr.net>, 2019, 2020, 2021, 2022, 2023, 2024. # Yuri Chornoivan <yurchor@ukr.net>, 2019, 2020, 2021, 2022, 2023, 2024.
# Dmytro Markevych <hotr1pak@gmail.com>, 2024.
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-06 14:42+0000\n" "POT-Creation-Date: 2024-11-06 14:42+0000\n"
"PO-Revision-Date: 2024-08-24 10:36+0000\n" "PO-Revision-Date: 2024-11-20 19:13+0000\n"
"Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n" "Last-Translator: Dmytro Markevych <hotr1pak@gmail.com>\n"
"Language-Team: Ukrainian <https://translate.fedoraproject.org/projects/" "Language-Team: Ukrainian <https://translate.fedoraproject.org/projects/"
"systemd/main/uk/>\n" "systemd/main/uk/>\n"
"Language: uk\n" "Language: uk\n"
@ -18,7 +19,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.7\n" "X-Generator: Weblate 5.8.2\n"
#: src/core/org.freedesktop.systemd1.policy.in:22 #: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system" msgid "Send passphrase back to system"
@ -118,14 +119,12 @@ msgid "Authentication is required to update a user's home area."
msgstr "Для оновлення домашньої теки користувача слід пройти розпізнавання." msgstr "Для оновлення домашньої теки користувача слід пройти розпізнавання."
#: src/home/org.freedesktop.home1.policy:53 #: src/home/org.freedesktop.home1.policy:53
#, fuzzy
msgid "Update your home area" msgid "Update your home area"
msgstr "Оновлення домашньої теки" msgstr "Оновіть свій домашній простір"
#: src/home/org.freedesktop.home1.policy:54 #: src/home/org.freedesktop.home1.policy:54
#, fuzzy
msgid "Authentication is required to update your home area." msgid "Authentication is required to update your home area."
msgstr "Для оновлення домашньої теки користувача слід пройти розпізнавання." msgstr "Для оновлення домашньої області потрібна автентифікація."
#: src/home/org.freedesktop.home1.policy:63 #: src/home/org.freedesktop.home1.policy:63
msgid "Resize a home area" msgid "Resize a home area"
@ -1212,14 +1211,11 @@ msgstr "Для вилучення застарілих оновлень сист
#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 #: src/sysupdate/org.freedesktop.sysupdate1.policy:75
msgid "Manage optional features" msgid "Manage optional features"
msgstr "" msgstr "Керування додатковими функціями"
#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 #: src/sysupdate/org.freedesktop.sysupdate1.policy:76
#, fuzzy
msgid "Authentication is required to manage optional features" msgid "Authentication is required to manage optional features"
msgstr "" msgstr "Для керування додатковими функціями потрібна автентифікація"
"Для того, щоб керувати сеансами, користувачами і робочими місцями, слід "
"пройти розпізнавання."
#: src/timedate/org.freedesktop.timedate1.policy:22 #: src/timedate/org.freedesktop.timedate1.policy:22
msgid "Set system time" msgid "Set system time"

View File

@ -799,16 +799,20 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **ret_path) {
continue; continue;
} }
char *path = strdup(e + 1); _cleanup_free_ char *path = strdup(e + 1);
if (!path) if (!path)
return -ENOMEM; return -ENOMEM;
/* Refuse cgroup paths from outside our cgroup namespace */
if (startswith(path, "/../"))
return -EUNATCH;
/* Truncate suffix indicating the process is a zombie */ /* Truncate suffix indicating the process is a zombie */
e = endswith(path, " (deleted)"); e = endswith(path, " (deleted)");
if (e) if (e)
*e = 0; *e = 0;
*ret_path = path; *ret_path = TAKE_PTR(path);
return 0; return 0;
} }
} }

View File

@ -102,8 +102,8 @@ int pid_get_comm(pid_t pid, char **ret) {
_cleanup_free_ char *escaped = NULL, *comm = NULL; _cleanup_free_ char *escaped = NULL, *comm = NULL;
int r; int r;
assert(ret);
assert(pid >= 0); assert(pid >= 0);
assert(ret);
if (pid == 0 || pid == getpid_cached()) { if (pid == 0 || pid == getpid_cached()) {
comm = new0(char, TASK_COMM_LEN + 1); /* Must fit in 16 byte according to prctl(2) */ comm = new0(char, TASK_COMM_LEN + 1); /* Must fit in 16 byte according to prctl(2) */
@ -143,6 +143,9 @@ int pidref_get_comm(const PidRef *pid, char **ret) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_comm(pid->pid, &comm); r = pid_get_comm(pid->pid, &comm);
if (r < 0) if (r < 0)
return r; return r;
@ -289,6 +292,9 @@ int pidref_get_cmdline(const PidRef *pid, size_t max_columns, ProcessCmdlineFlag
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_cmdline(pid->pid, max_columns, flags, &s); r = pid_get_cmdline(pid->pid, max_columns, flags, &s);
if (r < 0) if (r < 0)
return r; return r;
@ -331,6 +337,9 @@ int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char *
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_cmdline_strv(pid->pid, flags, &args); r = pid_get_cmdline_strv(pid->pid, flags, &args);
if (r < 0) if (r < 0)
return r; return r;
@ -477,6 +486,9 @@ int pidref_is_kernel_thread(const PidRef *pid) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
result = pid_is_kernel_thread(pid->pid); result = pid_is_kernel_thread(pid->pid);
if (result < 0) if (result < 0)
return result; return result;
@ -594,6 +606,9 @@ int pidref_get_uid(const PidRef *pid, uid_t *ret) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_uid(pid->pid, &uid); r = pid_get_uid(pid->pid, &uid);
if (r < 0) if (r < 0)
return r; return r;
@ -794,6 +809,9 @@ int pidref_get_start_time(const PidRef *pid, usec_t *ret) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_start_time(pid->pid, ret ? &t : NULL); r = pid_get_start_time(pid->pid, ret ? &t : NULL);
if (r < 0) if (r < 0)
return r; return r;
@ -1093,6 +1111,9 @@ int pidref_is_my_child(const PidRef *pid) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
result = pid_is_my_child(pid->pid); result = pid_is_my_child(pid->pid);
if (result < 0) if (result < 0)
return result; return result;
@ -1128,6 +1149,9 @@ int pidref_is_unwaited(const PidRef *pid) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
if (pid->pid == 1 || pidref_is_self(pid)) if (pid->pid == 1 || pidref_is_self(pid))
return true; return true;
@ -1169,6 +1193,9 @@ int pidref_is_alive(const PidRef *pidref) {
if (!pidref_is_set(pidref)) if (!pidref_is_set(pidref))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pidref))
return -EREMOTE;
result = pid_is_alive(pidref->pid); result = pid_is_alive(pidref->pid);
if (result < 0) { if (result < 0) {
assert(result != -ESRCH); assert(result != -ESRCH);

View File

@ -220,9 +220,9 @@ static int synthesize_user_creds(
if (ret_gid) if (ret_gid)
*ret_gid = GID_NOBODY; *ret_gid = GID_NOBODY;
if (ret_home) if (ret_home)
*ret_home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/"; *ret_home = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/";
if (ret_shell) if (ret_shell)
*ret_shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN; *ret_shell = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN;
return 0; return 0;
} }
@ -244,6 +244,7 @@ int get_user_creds(
assert(username); assert(username);
assert(*username); assert(*username);
assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN)));
if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) || if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
(!ret_home && !ret_shell)) { (!ret_home && !ret_shell)) {
@ -315,17 +316,14 @@ int get_user_creds(
if (ret_home) if (ret_home)
/* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
*ret_home = (FLAGS_SET(flags, USER_CREDS_CLEAN) && *ret_home = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(p->pw_dir)) ||
(empty_or_root(p->pw_dir) || (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_dir) || !path_is_absolute(p->pw_dir)))
!path_is_valid(p->pw_dir) || ? NULL : p->pw_dir;
!path_is_absolute(p->pw_dir))) ? NULL : p->pw_dir;
if (ret_shell) if (ret_shell)
*ret_shell = (FLAGS_SET(flags, USER_CREDS_CLEAN) && *ret_shell = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(p->pw_shell)) ||
(isempty(p->pw_shell) || (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_shell) || !path_is_absolute(p->pw_shell)))
!path_is_valid(p->pw_shell) || ? NULL : p->pw_shell;
!path_is_absolute(p->pw_shell) ||
is_nologin_shell(p->pw_shell))) ? NULL : p->pw_shell;
if (patch_username) if (patch_username)
*username = p->pw_name; *username = p->pw_name;

View File

@ -12,6 +12,8 @@
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include "string-util.h"
/* Users managed by systemd-homed. See https://systemd.io/UIDS-GIDS for details how this range fits into the rest of the world */ /* Users managed by systemd-homed. See https://systemd.io/UIDS-GIDS for details how this range fits into the rest of the world */
#define HOME_UID_MIN ((uid_t) 60001) #define HOME_UID_MIN ((uid_t) 60001)
#define HOME_UID_MAX ((uid_t) 60513) #define HOME_UID_MAX ((uid_t) 60513)
@ -36,10 +38,20 @@ static inline int parse_gid(const char *s, gid_t *ret_gid) {
char* getlogname_malloc(void); char* getlogname_malloc(void);
char* getusername_malloc(void); char* getusername_malloc(void);
const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
bool is_nologin_shell(const char *shell);
static inline bool shell_is_placeholder(const char *shell) {
return isempty(shell) || is_nologin_shell(shell);
}
typedef enum UserCredsFlags { typedef enum UserCredsFlags {
USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */ USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */
USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */ USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */
USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */ USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */
USER_CREDS_SUPPRESS_PLACEHOLDER = 1 << 3, /* suppress home and/or shell fields if value is placeholder (root/empty/nologin) */
} UserCredsFlags; } UserCredsFlags;
int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags);
@ -125,10 +137,6 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg);
int putsgent_sane(const struct sgrp *sg, FILE *stream); int putsgent_sane(const struct sgrp *sg, FILE *stream);
#endif #endif
bool is_nologin_shell(const char *shell);
const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
int is_this_me(const char *username); int is_this_me(const char *username);
const char* get_home_root(void); const char* get_home_root(void);

View File

@ -855,9 +855,6 @@ static int get_fixed_user(
assert(user_or_uid); assert(user_or_uid);
assert(ret_username); assert(ret_username);
/* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway
* (i.e. are "/" or "/bin/nologin"). */
r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, USER_CREDS_CLEAN); r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, USER_CREDS_CLEAN);
if (r < 0) if (r < 0)
return r; return r;
@ -1883,7 +1880,10 @@ static int build_environment(
} }
} }
if (home && set_user_login_env) { /* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway
* (i.e. are "/" or "/bin/nologin"). */
if (home && set_user_login_env && !empty_or_root(home)) {
x = strjoin("HOME=", home); x = strjoin("HOME=", home);
if (!x) if (!x)
return -ENOMEM; return -ENOMEM;
@ -1892,7 +1892,7 @@ static int build_environment(
our_env[n_env++] = x; our_env[n_env++] = x;
} }
if (shell && set_user_login_env) { if (shell && set_user_login_env && !shell_is_placeholder(shell)) {
x = strjoin("SHELL=", shell); x = strjoin("SHELL=", shell);
if (!x) if (!x)
return -ENOMEM; return -ENOMEM;
@ -3471,20 +3471,16 @@ static int apply_working_directory(
const ExecContext *context, const ExecContext *context,
const ExecParameters *params, const ExecParameters *params,
ExecRuntime *runtime, ExecRuntime *runtime,
const char *home, const char *home) {
int *exit_status) {
const char *wd; const char *wd;
int r; int r;
assert(context); assert(context);
assert(exit_status);
if (context->working_directory_home) { if (context->working_directory_home) {
if (!home) { if (!home)
*exit_status = EXIT_CHDIR;
return -ENXIO; return -ENXIO;
}
wd = home; wd = home;
} else } else
@ -3503,13 +3499,7 @@ static int apply_working_directory(
if (r >= 0) if (r >= 0)
r = RET_NERRNO(fchdir(dfd)); r = RET_NERRNO(fchdir(dfd));
} }
return context->working_directory_missing_ok ? 0 : r;
if (r < 0 && !context->working_directory_missing_ok) {
*exit_status = EXIT_CHDIR;
return r;
}
return 0;
} }
static int apply_root_directory( static int apply_root_directory(
@ -3785,7 +3775,7 @@ static int acquire_home(const ExecContext *c, const char **home, char **ret_buf)
if (!c->working_directory_home) if (!c->working_directory_home)
return 0; return 0;
if (c->dynamic_user) if (c->dynamic_user || (c->user && is_this_me(c->user) <= 0))
return -EADDRNOTAVAIL; return -EADDRNOTAVAIL;
r = get_home_dir(ret_buf); r = get_home_dir(ret_buf);
@ -4543,7 +4533,7 @@ int exec_invoke(
r = acquire_home(context, &home, &home_buffer); r = acquire_home(context, &home, &home_buffer);
if (r < 0) { if (r < 0) {
*exit_status = EXIT_CHDIR; *exit_status = EXIT_CHDIR;
return log_exec_error_errno(context, params, r, "Failed to determine $HOME for user: %m"); return log_exec_error_errno(context, params, r, "Failed to determine $HOME for the invoking user: %m");
} }
/* If a socket is connected to STDIN/STDOUT/STDERR, we must drop O_NONBLOCK */ /* If a socket is connected to STDIN/STDOUT/STDERR, we must drop O_NONBLOCK */
@ -5382,9 +5372,11 @@ int exec_invoke(
* running this service might have the correct privilege to change to the working directory. Also, it * running this service might have the correct privilege to change to the working directory. Also, it
* is absolutely 💣 crucial 💣 we applied all mount namespacing rearrangements before this, so that * is absolutely 💣 crucial 💣 we applied all mount namespacing rearrangements before this, so that
* the cwd cannot be used to pin directories outside of the sandbox. */ * the cwd cannot be used to pin directories outside of the sandbox. */
r = apply_working_directory(context, params, runtime, home, exit_status); r = apply_working_directory(context, params, runtime, home);
if (r < 0) if (r < 0) {
*exit_status = EXIT_CHDIR;
return log_exec_error_errno(context, params, r, "Changing to the requested working directory failed: %m"); return log_exec_error_errno(context, params, r, "Changing to the requested working directory failed: %m");
}
if (needs_sandboxing) { if (needs_sandboxing) {
/* Apply other MAC contexts late, but before seccomp syscall filtering, as those should really be last to /* Apply other MAC contexts late, but before seccomp syscall filtering, as those should really be last to

View File

@ -193,7 +193,7 @@ int enroll_fido2(
fflush(stdout); fflush(stdout);
fprintf(stderr, fprintf(stderr,
"\nPlease save this FIDO2 credential ID. It is required when unloocking the volume\n" "\nPlease save this FIDO2 credential ID. It is required when unlocking the volume\n"
"using the associated FIDO2 keyslot which we just created. To configure automatic\n" "using the associated FIDO2 keyslot which we just created. To configure automatic\n"
"unlocking using this FIDO2 token, add an appropriate entry to your /etc/crypttab\n" "unlocking using this FIDO2 token, add an appropriate entry to your /etc/crypttab\n"
"file, see %s for details.\n", link); "file, see %s for details.\n", link);

View File

@ -1443,6 +1443,7 @@ int link_reconfigure_impl(Link *link, LinkReconfigurationFlag flags) {
} }
typedef struct LinkReconfigurationData { typedef struct LinkReconfigurationData {
Manager *manager;
Link *link; Link *link;
LinkReconfigurationFlag flags; LinkReconfigurationFlag flags;
sd_bus_message *message; sd_bus_message *message;
@ -1473,6 +1474,12 @@ static void link_reconfiguration_data_destroy_callback(LinkReconfigurationData *
} }
if (!data->counter || *data->counter <= 0) { if (!data->counter || *data->counter <= 0) {
/* Update the state files before replying the bus method. Otherwise,
* systemd-networkd-wait-online following networkctl reload/reconfigure may read an
* outdated state file and wrongly handle an interface is already in the configured
* state. */
(void) manager_clean_all(data->manager);
r = sd_bus_reply_method_return(data->message, NULL); r = sd_bus_reply_method_return(data->message, NULL);
if (r < 0) if (r < 0)
log_warning_errno(r, "Failed to reply for DBus method, ignoring: %m"); log_warning_errno(r, "Failed to reply for DBus method, ignoring: %m");
@ -1521,6 +1528,7 @@ int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_mess
} }
*data = (LinkReconfigurationData) { *data = (LinkReconfigurationData) {
.manager = link->manager,
.link = link_ref(link), .link = link_ref(link),
.flags = flags, .flags = flags,
.message = sd_bus_message_ref(message), /* message may be NULL, but _ref() works fine. */ .message = sd_bus_message_ref(message), /* message may be NULL, but _ref() works fine. */

View File

@ -8,6 +8,7 @@ basic_dns_sources = files(
'resolved-dns-rr.c', 'resolved-dns-rr.c',
'resolved-dns-answer.c', 'resolved-dns-answer.c',
'resolved-dns-question.c', 'resolved-dns-question.c',
'resolved-dns-browse-services.c',
'resolved-util.c', 'resolved-util.c',
'dns-type.c', 'dns-type.c',
) )

View File

@ -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);

View File

@ -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);

View File

@ -1011,6 +1011,7 @@ static int answer_add_clamp_ttl(
} }
} }
rr->until = until;
r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig); r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -416,6 +416,66 @@ int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_
return 0; 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( int dns_question_new_service(
DnsQuestion **ret, DnsQuestion **ret,
const char *service, const char *service,

View File

@ -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_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_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(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_raw(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags);
int dns_question_add(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags); int dns_question_add(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags);

View File

@ -398,6 +398,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) {
.n_ref = 1, .n_ref = 1,
.key = dns_resource_key_ref(key), .key = dns_resource_key_ref(key),
.expiry = USEC_INFINITY, .expiry = USEC_INFINITY,
.until = USEC_INFINITY,
.n_skip_labels_signer = UINT8_MAX, .n_skip_labels_signer = UINT8_MAX,
.n_skip_labels_source = UINT8_MAX, .n_skip_labels_source = UINT8_MAX,
}; };
@ -1704,6 +1705,7 @@ DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) {
copy->ttl = rr->ttl; copy->ttl = rr->ttl;
copy->expiry = rr->expiry; copy->expiry = rr->expiry;
copy->until = rr->until;
copy->n_skip_labels_signer = rr->n_skip_labels_signer; copy->n_skip_labels_signer = rr->n_skip_labels_signer;
copy->n_skip_labels_source = rr->n_skip_labels_source; copy->n_skip_labels_source = rr->n_skip_labels_source;
copy->unparsable = rr->unparsable; copy->unparsable = rr->unparsable;

View File

@ -107,6 +107,8 @@ struct DnsResourceRecord {
unsigned n_ref; unsigned n_ref;
uint32_t ttl; uint32_t ttl;
usec_t expiry; /* RRSIG signature expiry */ 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; DnsResourceKey *key;

View File

@ -10,6 +10,7 @@
#include "hostname-util.h" #include "hostname-util.h"
#include "missing_network.h" #include "missing_network.h"
#include "random-util.h" #include "random-util.h"
#include "resolved-dns-browse-services.h"
#include "resolved-dnssd.h" #include "resolved-dnssd.h"
#include "resolved-dns-scope.h" #include "resolved-dns-scope.h"
#include "resolved-dns-synthesize.h" #include "resolved-dns-synthesize.h"
@ -121,6 +122,9 @@ DnsScope* dns_scope_free(DnsScope *s) {
dns_cache_flush(&s->cache); dns_cache_flush(&s->cache);
dns_zone_flush(&s->zone); 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); LIST_REMOVE(scopes, s->manager->dns_scopes, s);
return mfree(s); return mfree(s);
} }

View File

@ -14,6 +14,7 @@
#include "mkdir.h" #include "mkdir.h"
#include "netif-util.h" #include "netif-util.h"
#include "parse-util.h" #include "parse-util.h"
#include "resolved-dns-browse-services.h"
#include "resolved-link.h" #include "resolved-link.h"
#include "resolved-llmnr.h" #include "resolved-llmnr.h"
#include "resolved-mdns.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); r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET);
if (r < 0) if (r < 0)
log_link_warning_errno(l, r, "Failed to allocate mDNS IPv4 scope, ignoring: %m"); log_link_warning_errno(l, r, "Failed to allocate mDNS IPv4 scope, ignoring: %m");
dns_service_browser_reset(l->manager);
} }
} else } else
l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); 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); r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6);
if (r < 0) if (r < 0)
log_link_warning_errno(l, r, "Failed to allocate mDNS IPv6 scope, ignoring: %m"); log_link_warning_errno(l, r, "Failed to allocate mDNS IPv6 scope, ignoring: %m");
dns_service_browser_reset(l->manager);
} }
} else } else
l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope);

View File

@ -769,6 +769,7 @@ int manager_start(Manager *m) {
Manager *manager_free(Manager *m) { Manager *manager_free(Manager *m) {
Link *l; Link *l;
DnssdService *s; DnssdService *s;
DnsServiceBrowser *sb;
if (!m) if (!m)
return NULL; return NULL;
@ -840,6 +841,10 @@ Manager *manager_free(Manager *m) {
dns_trust_anchor_flush(&m->trust_anchor); dns_trust_anchor_flush(&m->trust_anchor);
manager_etc_hosts_flush(m); 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); 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); 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; Link *l;
assert(m); 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) if (!l)
return NULL; return NULL;
switch (p->protocol) { switch (protocol) {
case DNS_PROTOCOL_LLMNR: case DNS_PROTOCOL_LLMNR:
if (p->family == AF_INET) if (family == AF_INET)
return l->llmnr_ipv4_scope; return l->llmnr_ipv4_scope;
else if (p->family == AF_INET6) else if (family == AF_INET6)
return l->llmnr_ipv6_scope; return l->llmnr_ipv6_scope;
break; break;
case DNS_PROTOCOL_MDNS: case DNS_PROTOCOL_MDNS:
if (p->family == AF_INET) if (family == AF_INET)
return l->mdns_ipv4_scope; return l->mdns_ipv4_scope;
else if (p->family == AF_INET6) else if (family == AF_INET6)
return l->mdns_ipv6_scope; return l->mdns_ipv6_scope;
break; break;
@ -1706,6 +1710,9 @@ void manager_flush_caches(Manager *m, int log_level) {
LIST_FOREACH(scopes, scope, m->dns_scopes) LIST_FOREACH(scopes, scope, m->dns_scopes)
dns_cache_flush(&scope->cache); 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."); log_full(log_level, "Flushed all caches.");
} }

View File

@ -16,6 +16,7 @@
typedef struct Manager Manager; typedef struct Manager Manager;
#include "resolved-dns-browse-services.h"
#include "resolved-dns-query.h" #include "resolved-dns-query.h"
#include "resolved-dns-search-domain.h" #include "resolved-dns-search-domain.h"
#include "resolved-dns-stream.h" #include "resolved-dns-stream.h"
@ -161,6 +162,9 @@ struct Manager {
size_t n_socket_graveyard; size_t n_socket_graveyard;
struct sigrtmin18_info sigrtmin18_info; struct sigrtmin18_info sigrtmin18_info;
/* Map varlink links to DnsServiceBrowser instances. */
Hashmap *dns_service_browsers;
}; };
/* Manager */ /* 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_local_address(Manager *m, DnsPacket *p);
bool manager_packet_from_our_transaction(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); void manager_verify_all(Manager *m);

View File

@ -359,6 +359,10 @@ static int mdns_goodbye_callback(sd_event_source *s, uint64_t usec, void *userda
dns_cache_prune(&scope->cache); 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)) { if (dns_cache_expiry_in_one_second(&scope->cache, usec)) {
r = sd_event_add_time_relative( r = sd_event_add_time_relative(
scope->manager->event, 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; Manager *m = userdata;
DnsScope *scope; DnsScope *scope;
int r; int r;
bool unsolicited_packet = true;
r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p);
if (r <= 0) 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( dns_cache_put(
&scope->cache, &scope->cache,
scope->manager->enable_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, &p->sender,
scope->manager->stale_retention_usec); 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) { } else if (dns_packet_validate_query(p) > 0) {
log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));

View File

@ -4,6 +4,7 @@
#include "glyph-util.h" #include "glyph-util.h"
#include "in-addr-util.h" #include "in-addr-util.h"
#include "json-util.h" #include "json-util.h"
#include "resolved-dns-browse-services.h"
#include "resolved-dns-synthesize.h" #include "resolved-dns-synthesize.h"
#include "resolved-varlink.h" #include "resolved-varlink.h"
#include "socket-netlink.h" #include "socket-netlink.h"
@ -30,11 +31,26 @@ typedef struct LookupParametersResolveService {
uint64_t flags; uint64_t flags;
} LookupParametersResolveService; } LookupParametersResolveService;
typedef struct LookupParametersMdnsBrowse {
char *domainName;
char *name;
char *type;
int ifindex;
uint64_t flags;
} LookupParametersMdnsBrowse;
static void lookup_parameters_destroy(LookupParameters *p) { static void lookup_parameters_destroy(LookupParameters *p) {
assert(p); assert(p);
free(p->name); 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) { static int reply_query_state(DnsQuery *q) {
assert(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) { static void vl_on_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata) {
DnsQuery *q; DnsQuery *q;
Manager *m;
assert(s); assert(s);
assert(link); 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); q = sd_varlink_get_userdata(link);
if (!q) if (!q)
return; return;
@ -1209,6 +1233,43 @@ static int verify_polkit(sd_varlink *link, sd_json_variant *parameters, const ch
&m->polkit_registry); &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) { 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))); Manager *m = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link)));
int r; 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.ResolveHostname", vl_method_resolve_hostname,
"io.systemd.Resolve.ResolveAddress", vl_method_resolve_address, "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address,
"io.systemd.Resolve.ResolveService", vl_method_resolve_service, "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) if (r < 0)
return log_error_errno(r, "Failed to register varlink methods: %m"); return log_error_errno(r, "Failed to register varlink methods: %m");

View File

@ -2297,7 +2297,8 @@ static int start_transient_scope(sd_bus *bus) {
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell, USER_CREDS_CLEAN|USER_CREDS_PREFER_NSS); r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell,
USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user);

View File

@ -46,13 +46,17 @@ static bool argv_has_at(pid_t pid) {
return c == '@'; return c == '@';
} }
static bool is_survivor_cgroup(const PidRef *pid) { static bool is_in_survivor_cgroup(const PidRef *pid) {
_cleanup_free_ char *cgroup_path = NULL; _cleanup_free_ char *cgroup_path = NULL;
int r; int r;
assert(pidref_is_set(pid)); assert(pidref_is_set(pid));
r = cg_pidref_get_path(/* root= */ NULL, pid, &cgroup_path); r = cg_pidref_get_path(/* root= */ NULL, pid, &cgroup_path);
if (r == -EUNATCH) {
log_warning_errno(r, "Process " PID_FMT " appears to originate in foreign namespace, ignoring.", pid->pid);
return true;
}
if (r < 0) { if (r < 0) {
log_warning_errno(r, "Failed to get cgroup path of process " PID_FMT ", ignoring: %m", pid->pid); log_warning_errno(r, "Failed to get cgroup path of process " PID_FMT ", ignoring: %m", pid->pid);
return false; return false;
@ -86,7 +90,7 @@ static bool ignore_proc(const PidRef *pid, bool warn_rootfs) {
return true; /* also ignore processes where we can't determine this */ return true; /* also ignore processes where we can't determine this */
/* Ignore processes that are part of a cgroup marked with the user.survive_final_kill_signal xattr */ /* Ignore processes that are part of a cgroup marked with the user.survive_final_kill_signal xattr */
if (is_survivor_cgroup(pid)) if (is_in_survivor_cgroup(pid))
return true; return true;
r = pidref_get_uid(pid, &uid); r = pidref_get_uid(pid, &uid);

View File

@ -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(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0)); 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( static SD_VARLINK_DEFINE_METHOD(
ResolveAddress, 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."), 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_BY_TYPE(rrs, ResolvedRecord, SD_VARLINK_ARRAY),
SD_VARLINK_DEFINE_OUTPUT(flags, SD_VARLINK_INT, 0)); 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(NoNameServers);
static SD_VARLINK_DEFINE_ERROR(NoSuchResourceRecord); static SD_VARLINK_DEFINE_ERROR(NoSuchResourceRecord);
static SD_VARLINK_DEFINE_ERROR(QueryTimedOut); static SD_VARLINK_DEFINE_ERROR(QueryTimedOut);
@ -198,6 +216,8 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_method_ResolveService, &vl_method_ResolveService,
SD_VARLINK_SYMBOL_COMMENT("Resolves a domain name to one or more DNS resource records."), SD_VARLINK_SYMBOL_COMMENT("Resolves a domain name to one or more DNS resource records."),
&vl_method_ResolveRecord, &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."), SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved address."),
&vl_type_ResolvedAddress, &vl_type_ResolvedAddress,
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved host name."), SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved host name."),
@ -212,6 +232,7 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_type_ResourceRecord, &vl_type_ResourceRecord,
SD_VARLINK_SYMBOL_COMMENT("Encapsulates information about a resolved DNS resource record "), SD_VARLINK_SYMBOL_COMMENT("Encapsulates information about a resolved DNS resource record "),
&vl_type_ResolvedRecord, &vl_type_ResolvedRecord,
&vl_type_ServiceData,
&vl_error_NoNameServers, &vl_error_NoNameServers,
&vl_error_NoSuchResourceRecord, &vl_error_NoSuchResourceRecord,
&vl_error_QueryTimedOut, &vl_error_QueryTimedOut,

View File

@ -7,24 +7,26 @@ TEST(audit_loginuid_from_pid) {
_cleanup_(pidref_done) PidRef self = PIDREF_NULL, pid1 = PIDREF_NULL; _cleanup_(pidref_done) PidRef self = PIDREF_NULL, pid1 = PIDREF_NULL;
int r; int r;
assert_se(pidref_set_self(&self) >= 0); ASSERT_OK(pidref_set_self(&self));
assert_se(pidref_set_pid(&pid1, 1) >= 0); ASSERT_OK(pidref_set_pid(&pid1, 1));
uid_t uid; uid_t uid;
r = audit_loginuid_from_pid(&self, &uid); r = audit_loginuid_from_pid(&self, &uid);
assert_se(r >= 0 || r == -ENODATA); if (r != -ENODATA)
ASSERT_OK(r);
if (r >= 0) if (r >= 0)
log_info("self audit login uid: " UID_FMT, uid); log_info("self audit login uid: " UID_FMT, uid);
assert_se(audit_loginuid_from_pid(&pid1, &uid) == -ENODATA); ASSERT_ERROR(audit_loginuid_from_pid(&pid1, &uid), ENODATA);
uint32_t sessionid; uint32_t sessionid;
r = audit_session_from_pid(&self, &sessionid); r = audit_session_from_pid(&self, &sessionid);
assert_se(r >= 0 || r == -ENODATA); if (r != -ENODATA)
ASSERT_OK(r);
if (r >= 0) if (r >= 0)
log_info("self audit session id: %" PRIu32, sessionid); log_info("self audit session id: %" PRIu32, sessionid);
assert_se(audit_session_from_pid(&pid1, &sessionid) == -ENODATA); ASSERT_ERROR(audit_session_from_pid(&pid1, &sessionid), ENODATA);
} }
static int intro(void) { static int intro(void) {

View File

@ -522,6 +522,7 @@ static int reply_callback(
if (!arg_quiet) if (!arg_quiet)
sd_json_variant_dump(parameters, arg_json_format_flags, stdout, NULL); sd_json_variant_dump(parameters, arg_json_format_flags, stdout, NULL);
fflush(stdout);
return r; return r;
} }

View File

@ -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

View File

@ -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,
},
]

View File

@ -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 "$@"

View File

@ -379,6 +379,7 @@ foreach dirname : [
'TEST-84-STORAGETM', 'TEST-84-STORAGETM',
'TEST-85-NETWORK', 'TEST-85-NETWORK',
'TEST-86-MULTI-PROFILE-UKI', 'TEST-86-MULTI-PROFILE-UKI',
'TEST-87-RESOLVED-MDNS',
] ]
subdir(dirname) subdir(dirname)
endforeach endforeach

View File

@ -80,6 +80,7 @@ show_journal = True # When true, show journal on stopping networkd.
active_units = [] active_units = []
protected_links = { protected_links = {
'br0',
'erspan0', 'erspan0',
'gre0', 'gre0',
'gretap0', 'gretap0',
@ -6406,11 +6407,11 @@ class NetworkdRATests(unittest.TestCase, Utilities):
for i in [100, 200, 300, 512, 1024, 2048]: for i in [100, 200, 300, 512, 1024, 2048]:
if i not in [metric_1, metric_2]: if i not in [metric_1, metric_2]:
self.assertNotIn(f'{i}', output) self.assertNotIn(f'metric {i} ', output)
for i in ['low', 'medium', 'high']: for i in ['low', 'medium', 'high']:
if i not in [preference_1, preference_2]: if i not in [preference_1, preference_2]:
self.assertNotIn(f'{i}', output) self.assertNotIn(f'pref {i}', output)
def test_router_preference(self): def test_router_preference(self):
copy_network_unit('25-veth-client.netdev', copy_network_unit('25-veth-client.netdev',

View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
(! systemd-run --wait -p DynamicUser=yes \
-p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \
-p WorkingDirectory='~' true)
assert_eq "$(systemd-run --pipe --uid=root -p WorkingDirectory='~' pwd)" "/root"
assert_eq "$(systemd-run --pipe --uid=nobody -p WorkingDirectory='~' pwd)" "/"
assert_eq "$(systemd-run --pipe --uid=testuser -p WorkingDirectory='~' pwd)" "/home/testuser"
(! systemd-run --wait -p DynamicUser=yes -p User=testuser \
-p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \
-p WorkingDirectory='~' true)

View File

@ -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

View File

@ -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

View File

@ -16,6 +16,7 @@ ConditionDirectoryNotEmpty=|/run/confexts
ConditionDirectoryNotEmpty=|/var/lib/confexts ConditionDirectoryNotEmpty=|/var/lib/confexts
ConditionDirectoryNotEmpty=|/usr/local/lib/confexts ConditionDirectoryNotEmpty=|/usr/local/lib/confexts
ConditionDirectoryNotEmpty=|/usr/lib/confexts ConditionDirectoryNotEmpty=|/usr/lib/confexts
ConditionDirectoryNotEmpty=|/.extra/confext
DefaultDependencies=no DefaultDependencies=no
After=local-fs.target After=local-fs.target