resolved: actually unregister DNS-SD services when the D-Bus client vanishes (#43578)
Since the D-Bus DNS-SD API was introduced in c3036641f0ed (v236), `RegisterService()` has created an `sd_bus_track` object to destroy the registered service when the registering client disconnects from the bus. However, the track object is a `_cleanup_(sd_bus_track_unrefp)` local in `bus_method_register_service()` that is never stored anywhere, so it is destroyed again as soon as the method handler returns. The vanish handler (`dnssd_registered_service_on_bus_track()`) has thus been dead code for the past ~8 years. The observable effect: services registered via D-Bus silently persist after the client disconnects — unowned, advertised until resolved exits, and impossible to re-register since the identifier stays taken (`DNSSD_SERVICE_EXISTS`). At the same time, the registration doesn't survive a resolved restart, so the current behavior is neither reliably ephemeral nor reliably persistent. Noticed while reviewing a project that (unknowingly relying on this bug) registers a service with a one-shot `gdbus call` and expects it to persist. This PR: 1. splits the goodbye/zone-removal logic out of `bus_dnssd_method_unregister()` into a helper — the vanish path needs the full teardown, not just `dnssd_registered_service_free()`, which would leave the service's RRs published in the per-link mDNS zones; 2. stores the track object in `DnssdRegisteredService`, making client tracking work as originally intended; 3. documents the lifetime semantics in `org.freedesktop.resolve1(5)` (matching the equivalent Avahi D-Bus API, whose entry groups are likewise bound to the client connection) and points to `*.dnssd` files for registrations independent of a client's lifetime; 4. adds a TEST-75 case covering the connection-bound lifetime. Note this is a behavioral change for fire-and-forget clients (one-shot `busctl`/`gdbus` invocations). Arguably those were never working reliably (registrations silently vanished on any resolved restart, and stale entries blocked re-registration), but if this is deemed too risky, the alternative would be to drop the dead tracking code and codify the persist-until-exit behavior instead — I'd argue tracking as originally intended is the better contract. Verification: code compiles; the new integration test is included.
This commit is contained in:
@@ -232,7 +232,7 @@ manual_testcase_01_resolvectl() {
|
||||
ip link add hoge.foo type dummy
|
||||
|
||||
# Cleanup
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
ip link del hoge
|
||||
ip link del hoge.foo
|
||||
@@ -349,6 +349,7 @@ manual_testcase_02_mdns_llmnr() {
|
||||
ip link add hoge.foo type dummy
|
||||
|
||||
# Cleanup
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
rm -f /run/systemd/resolved.conf.d/90-mdns-llmnr.conf
|
||||
ip link del hoge
|
||||
@@ -436,6 +437,78 @@ manual_testcase_02_mdns_llmnr() {
|
||||
assert_in 'no' "$(resolvectl llmnr hoge)"
|
||||
}
|
||||
|
||||
# Test for the D-Bus RegisterService()/UnregisterService() API
|
||||
manual_testcase_03_register_service() {
|
||||
local cursor_file="" invocation_id
|
||||
|
||||
# Cleanup
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
rm -f /run/systemd/resolved.conf.d/90-mdns.conf "${cursor_file:-}"
|
||||
ip link del mdns99 2>/dev/null || :
|
||||
systemctl reload systemd-resolved.service
|
||||
}
|
||||
|
||||
trap cleanup RETURN ERR
|
||||
|
||||
mkdir -p /run/systemd/resolved.conf.d
|
||||
{
|
||||
echo "[Resolve]"
|
||||
echo "MulticastDNS=yes"
|
||||
} >/run/systemd/resolved.conf.d/90-mdns.conf
|
||||
restart_resolved
|
||||
resolvectl log-level debug
|
||||
|
||||
# resolved is restarted automatically if it crashes (Restart=always), which would let the checks below
|
||||
# pass for the wrong reason, so remember the invocation and verify it stayed the same at the end.
|
||||
invocation_id=$(systemctl show -P InvocationID systemd-resolved.service)
|
||||
|
||||
# Make sure at least one link carries mDNS scopes, so that unregistration actually runs the
|
||||
# per-scope goodbye/zone teardown.
|
||||
ip link add mdns99 type dummy
|
||||
ip link set mdns99 up
|
||||
resolvectl mdns mdns99 yes
|
||||
|
||||
cursor_file=$(mktemp)
|
||||
journalctl -n 0 --cursor-file="$cursor_file"
|
||||
|
||||
# The lifetime of a registered service is bound to the bus connection of the client that registered
|
||||
# it. busctl disconnects right after the call, so the service should be unregistered again shortly
|
||||
# after. Wait for the vanish handler log message as proof that the client tracking triggered the
|
||||
# unregistration. See testcase_15_wait_online_dns() for why journalctl -f | grep -m1 is not used here.
|
||||
assert_in '/org/freedesktop/resolve1/dnssd/testservice' \
|
||||
"$(busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager \
|
||||
RegisterService 'sssqqqaa{say}' testservice '%H' _testservice._tcp 1234 0 0 0)"
|
||||
|
||||
timeout 30 bash -c "until journalctl --after-cursor=\"\$(cat \"$cursor_file\")\" SYSLOG_IDENTIFIER=systemd-resolved --grep \"Client of DNS-SD service 'testservice' vanished, unregistering.\" >/dev/null 2>&1; do sleep .5; done"
|
||||
|
||||
assert_not_in 'dnssd/testservice' "$(busctl tree org.freedesktop.resolve1)"
|
||||
|
||||
# Unregistering it again should fail specifically because the service is gone (busctl prints the
|
||||
# D-Bus error message, not the error name), i.e. not because resolved crashed or the bus connection
|
||||
# failed.
|
||||
assert_in "DNS-SD service 'testservice' not known" \
|
||||
"$(busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager \
|
||||
UnregisterService 'o' /org/freedesktop/resolve1/dnssd/testservice 2>&1 || :)"
|
||||
|
||||
# Re-registering the same identifier must succeed: before client tracking worked, the identifier
|
||||
# stayed taken by the vanished client's service and this failed with DNSSD_SERVICE_EXISTS. Take a
|
||||
# fresh cursor so the wait below cannot match the first round's identical message.
|
||||
rm -f "$cursor_file"
|
||||
journalctl -n 0 --cursor-file="$cursor_file"
|
||||
assert_in '/org/freedesktop/resolve1/dnssd/testservice' \
|
||||
"$(busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager \
|
||||
RegisterService 'sssqqqaa{say}' testservice '%H' _testservice._tcp 1234 0 0 0)"
|
||||
|
||||
timeout 30 bash -c "until journalctl --after-cursor=\"\$(cat \"$cursor_file\")\" SYSLOG_IDENTIFIER=systemd-resolved --grep \"Client of DNS-SD service 'testservice' vanished, unregistering.\" >/dev/null 2>&1; do sleep .5; done"
|
||||
|
||||
assert_not_in 'dnssd/testservice' "$(busctl tree org.freedesktop.resolve1)"
|
||||
|
||||
# resolved must not have crashed and gotten restarted along the way.
|
||||
assert_eq "$(systemctl show -P InvocationID systemd-resolved.service)" "$invocation_id"
|
||||
systemctl is-active systemd-resolved.service
|
||||
}
|
||||
|
||||
testcase_03_23951() {
|
||||
: "--- nss-resolve/nss-myhostname tests"
|
||||
|
||||
@@ -866,7 +939,7 @@ testcase_08_resolved() {
|
||||
|
||||
testcase_09_resolvectl_showcache() {
|
||||
# Cleanup
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
rm -f /run/systemd/resolved.conf.d/90-resolved.conf
|
||||
rm -f /run/systemd/network/10-dns2.netdev
|
||||
@@ -928,7 +1001,7 @@ testcase_10_resolvectl_json() {
|
||||
local status_json
|
||||
|
||||
# Cleanup
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
rm -f /run/systemd/resolved.conf.d/90-fallback.conf
|
||||
systemctl reload systemd-resolved.service
|
||||
@@ -1099,7 +1172,7 @@ testcase_11_nft() {
|
||||
# Test resolvectl show-server-state
|
||||
testcase_12_resolvectl2() {
|
||||
# Cleanup
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
rm -f /run/systemd/resolved.conf.d/90-reload.conf
|
||||
systemctl reload systemd-resolved.service
|
||||
@@ -1201,7 +1274,7 @@ testcase_13_varlink_subscribe_dns_configuration() {
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
echo "===== io.systemd.Resolve.Monitor.SubscribeDNSConfiguration output: ====="
|
||||
cat "$tmpfile"
|
||||
@@ -1278,7 +1351,7 @@ testcase_13_varlink_subscribe_dns_configuration() {
|
||||
|
||||
# Test RefuseRecordTypes
|
||||
testcase_14_refuse_record_types() {
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
rm -f /run/systemd/resolved.conf.d/90-refuserecords.conf
|
||||
restart_resolved
|
||||
@@ -1440,7 +1513,7 @@ testcase_14_refuse_record_types() {
|
||||
# Test systemd-networkd-wait-online interactions with systemd-resolved
|
||||
testcase_15_wait_online_dns() {
|
||||
# Cleanup
|
||||
# shellcheck disable=SC2317
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
cleanup() {
|
||||
echo "===== journalctl -u $unit ====="
|
||||
journalctl -b --no-pager --no-hostname --full -u "$unit"
|
||||
@@ -1725,6 +1798,7 @@ systemctl enable --now systemd-resolved.service
|
||||
# Need to be run before SETUP, otherwise things will break
|
||||
manual_testcase_01_resolvectl
|
||||
manual_testcase_02_mdns_llmnr
|
||||
manual_testcase_03_register_service
|
||||
|
||||
# Run setup
|
||||
setup
|
||||
|
||||
Reference in New Issue
Block a user