]> git.ipfire.org Git - thirdparty/systemd.git/commit
resolved: fix spurious BrowseServices add/remove flapping with ifindex=0 (#42982)
authorLennart Poettering <lennart@amutable.com>
Tue, 14 Jul 2026 21:58:34 +0000 (23:58 +0200)
committerGitHub <noreply@github.com>
Tue, 14 Jul 2026 21:58:34 +0000 (23:58 +0200)
commite6acf0bab7d64966438538ec90fb320171432cd1
tree374a15dcf147b6f9d8b483a8dcdd12897b3a7aba
parentd26de063cd481ecffb82410d8df6a025291d12ab
parent4235dea97ae36fd3d4a90fdc913468674ea5aa6b
resolved: fix spurious BrowseServices add/remove flapping with ifindex=0 (#42982)

## Problem
A `BrowseServices` subscription with `"ifindex":0` (browse all
interfaces) receives a continuous flap of `added`/`removed` events for a
service that is still present — within a second, and with no goodbye
packet involved.

## Root cause
For `ifindex==0`, `mdns_browser_revisit_cache()` looked up each mDNS
scope's cache separately and called `mdns_manage_services_answer()`
**once per scope**. That function derives `removed` events by diffing
the browser's *global* discovered-service list (all interfaces, filtered
only by owner family) against the single answer it's handed. So with ≥2
mDNS-relevant interfaces of the same family, a service present on
interface A isn't in interface B's answer and is spuriously removed
while B is reconciled, then re-added on the next revisit tick.

## Fix
Accumulate the pruned cache answers from every matching mDNS scope into
one combined `DnsAnswer` and reconcile **once**, so removals diff the
global list against the union across interfaces. Items are merged with
`dns_answer_add_full()` (not `dns_answer_extend()`, which defaults each
item's `until` to `USEC_INFINITY` and would skew the RFC 6762 §5.2
TTL-maintenance schedule). The single-interface (`ifindex>0`) path is
unchanged.

## Test
`TEST-89-RESOLVED-MDNS.sh` gains `testcase_browse_ifindex_zero_no_flap`:
it adds a service-less dummy mDNS link to guarantee ≥2 same-family
scopes (the flap precondition), browses `ifindex=0`, waits for
discovery, then asserts **zero** `removed` events while every publisher
stays up. The subscription uses `varlinkctl --timeout=infinity`, since
it sits idle after discovery and the default 45s idle timeout would
sever it (and the assertion) mid-observation.

## Testing status
Builds clean; `shellcheck -x` clean. First CI round: `TEST-89`
(including this testcase) passed on all mkosi platforms; the failing
jobs all traced to unrelated flakes/infra.
src/resolve/resolved-dns-browse-services.c