From: dongshengyuan <545258830@qq.com> Date: Fri, 14 Aug 2026 07:21:30 +0000 (+0800) Subject: resolved: match browsed mDNS services by ifindex X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9711c84c2daebfd9a67f80fbc1fbba13a92e7419;p=thirdparty%2Fsystemd.git resolved: match browsed mDNS services by ifindex BrowseServices(ifindex=0) may observe the same service on multiple links. Treat the link index as part of the discovered service identity so add and remove events remain link-scoped. Use the per-service ifindex for RFC 6762 maintenance queries. Share the service identity predicate between add and remove handling, and propagate RR comparison errors instead of reporting removals. Extend coverage for scoped-browser ifindex fallback, goodbye TTL handling, and monotonic expiry updates. --- diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 3181ace1cb2..ad6a64dd602 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -99,6 +99,9 @@ executables += [ resolve_test_template + { 'sources' : files('test-dns-answer.c'), }, + resolve_test_template + { + 'sources' : files('test-dns-browse-services.c'), + }, resolve_test_template + { 'sources' : files('test-dns-cache.c'), }, diff --git a/src/resolve/resolved-dns-browse-services.c b/src/resolve/resolved-dns-browse-services.c index 19b773abf44..445ab974f1b 100644 --- a/src/resolve/resolved-dns-browse-services.c +++ b/src/resolve/resolved-dns-browse-services.c @@ -120,7 +120,7 @@ static int mdns_maintenance_query(sd_event_source *s, uint64_t usec, void *userd service->service_browser->question_utf8, service->service_browser->question_idna, /* question_bypass= */ NULL, - service->service_browser->ifindex, + service->ifindex, service->service_browser->flags); if (r < 0) return log_error_errno(r, "Failed to create mDNS query for maintenance: %m"); @@ -269,25 +269,88 @@ int mdns_service_update(DnssdDiscoveredService *service, DnsResourceRecord *rr, return 0; } -bool dns_service_match_and_update(DnssdDiscoveredService *services, DnsResourceRecord *rr, int owner_family, usec_t until) { +static int mdns_answer_item_ifindex(DnsServiceBrowser *sb, DnsAnswerItem *item) { + assert(sb); + assert(item); + + return item->ifindex > 0 ? item->ifindex : sb->ifindex; +} + +static int dns_service_matches( + DnssdDiscoveredService *service, + DnsResourceRecord *rr, + int owner_family, + int ifindex) { + + int r; + + assert(service); + assert(rr); + + r = dns_resource_record_equal(service->rr, rr); + if (r <= 0) + return r; + + return service->family == owner_family && service->ifindex == ifindex; +} + +int dns_service_match_and_update( + DnssdDiscoveredService *services, + DnsResourceRecord *rr, + int owner_family, + int ifindex, + usec_t until) { + usec_t t = now(CLOCK_BOOTTIME); + int r; + + /* Check if a discovered service matching the given resource record, owner family, and ifindex exists + * in the list. If found, update the service's expiration time if the new 'until' is later, unless the + * TTL is <= 1 (goodbye packet). Return positive if a matching service is found, zero otherwise. */ - /* Check if a discovered service matching the given resource record and owner family exists in the list. - * If found, update the service's expiration time if the new 'until' is later, unless the TTL is <= 1 (goodbye packet). - * Return true if a matching service is found, false otherwise. */ + LIST_FOREACH(dns_services, service, services) { + r = dns_service_matches(service, rr, owner_family, ifindex); + if (r < 0) + return r; + if (r == 0) + continue; - LIST_FOREACH(dns_services, service, services) - if (dns_resource_record_equal(service->rr, rr) > 0 && service->family == owner_family) { - if (rr->ttl <= 1) - return true; + if (rr->ttl <= 1) + return 1; - if (service->until < until) - mdns_service_update(service, rr, t, until); + if (service->until < until) + mdns_service_update(service, rr, t, until); - return true; - } + return 1; + } - return false; + return 0; +} + +int mdns_answer_contains_service( + DnsServiceBrowser *sb, + DnsAnswer *answer, + DnssdDiscoveredService *service) { + + DnsAnswerItem *item; + int r; + + assert(sb); + assert(service); + + DNS_ANSWER_FOREACH_ITEM(item, answer) { + r = dns_service_matches( + service, + item->rr, + service->family, + mdns_answer_item_ifindex(sb, item)); + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; } void dns_browse_services_purge(Manager *m, int family) { @@ -329,9 +392,14 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow DNS_ANSWER_FOREACH_ITEM(item, answer) { _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; - int ifindex; + int ifindex = mdns_answer_item_ifindex(sb, item); - if (dns_service_match_and_update(sb->dns_services, item->rr, owner_family, item->until)) + r = dns_service_match_and_update(sb->dns_services, item->rr, owner_family, ifindex, item->until); + if (r < 0) { + log_error_errno(r, "Failed to match DNS service: %m"); + goto finish; + } + if (r > 0) continue; r = dns_service_split(item->rr->ptr.name, &name, &type, &domain); @@ -353,9 +421,6 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow if (!type) continue; - /* Prefer the per-item ifindex, fall back to the service browser's ifindex */ - ifindex = item->ifindex > 0 ? item->ifindex : sb->ifindex; - r = dns_add_new_service(sb, item->rr, owner_family, ifindex, item->until); if (r < 0) { log_error_errno(r, "Failed to add new DNS service: %m"); @@ -404,7 +469,12 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow if (service->family != owner_family) continue; - if (dns_answer_contains(answer, service->rr)) + r = mdns_answer_contains_service(sb, answer, service); + if (r < 0) { + log_error_errno(r, "Failed to match DNS answer against service list: %m"); + goto finish; + } + if (r > 0) continue; r = dns_service_split(service->rr->ptr.name, &name, &type, &domain); diff --git a/src/resolve/resolved-dns-browse-services.h b/src/resolve/resolved-dns-browse-services.h index 424b2855a5b..84095d55598 100644 --- a/src/resolve/resolved-dns-browse-services.h +++ b/src/resolve/resolved-dns-browse-services.h @@ -65,7 +65,16 @@ void dns_browse_services_restart(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServiceBrowser *, dns_service_browser_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdDiscoveredService *, dnssd_discovered_service_unref); -bool dns_service_match_and_update(DnssdDiscoveredService *services, DnsResourceRecord *rr, int owner_family, usec_t until); +int dns_service_match_and_update( + DnssdDiscoveredService *services, + DnsResourceRecord *rr, + int owner_family, + int ifindex, + usec_t until); +int mdns_answer_contains_service( + DnsServiceBrowser *sb, + DnsAnswer *answer, + DnssdDiscoveredService *service); 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 ifindex, usec_t until); int mdns_service_update(DnssdDiscoveredService *service, DnsResourceRecord *rr, usec_t t, usec_t until); diff --git a/src/resolve/test-dns-browse-services.c b/src/resolve/test-dns-browse-services.c new file mode 100644 index 00000000000..d9ea99d0911 --- /dev/null +++ b/src/resolve/test-dns-browse-services.c @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "dns-answer.h" +#include "dns-rr.h" +#include "resolved-dns-browse-services.h" +#include "tests.h" + +static DnsResourceRecord *new_test_service_rr(uint32_t ttl) { + DnsResourceRecord *rr; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, "_http._tcp.local"); + ASSERT_NOT_NULL(rr); + + rr->ttl = ttl; + rr->ptr.name = strdup("Same Service._http._tcp.local"); + ASSERT_NOT_NULL(rr->ptr.name); + + return rr; +} + +TEST(dns_service_match_and_update_goodbye_and_expiry) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + ASSERT_NOT_NULL(rr = new_test_service_rr(120)); + + DnssdDiscoveredService service = { + .rr = rr, + .family = AF_INET, + .ifindex = 2, + .until = 10, + }; + + ASSERT_OK_POSITIVE(dns_service_match_and_update(&service, rr, AF_INET, 2, 100)); + ASSERT_EQ(service.until, (usec_t) 100); + + ASSERT_OK_POSITIVE(dns_service_match_and_update(&service, rr, AF_INET, 2, 75)); + ASSERT_EQ(service.until, (usec_t) 100); + + rr = dns_resource_record_unref(rr); + ASSERT_NOT_NULL(rr = new_test_service_rr(1)); + service.rr = rr; + service.until = 10; + + ASSERT_OK_POSITIVE(dns_service_match_and_update(&service, rr, AF_INET, 2, 200)); + ASSERT_EQ(service.until, (usec_t) 10); +} + +TEST(dns_service_match_and_update_ifindex) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + ASSERT_NOT_NULL(rr = new_test_service_rr(120)); + + DnssdDiscoveredService service = { + .rr = rr, + .family = AF_INET, + .ifindex = 2, + }; + + ASSERT_OK_ZERO(dns_service_match_and_update(&service, rr, AF_INET, 3, 100)); + ASSERT_EQ(service.until, (usec_t) 0); + + ASSERT_OK_POSITIVE(dns_service_match_and_update(&service, rr, AF_INET, 2, 100)); + ASSERT_EQ(service.until, (usec_t) 100); +} + +TEST(dns_service_match_and_update_ifindex_list) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + DnssdDiscoveredService *services = NULL; + + ASSERT_NOT_NULL(rr = new_test_service_rr(120)); + + DnssdDiscoveredService service2 = { + .rr = rr, + .family = AF_INET, + .ifindex = 2, + .until = 10, + }; + DnssdDiscoveredService service3 = { + .rr = rr, + .family = AF_INET, + .ifindex = 3, + .until = 20, + }; + + LIST_PREPEND(dns_services, services, &service3); + LIST_PREPEND(dns_services, services, &service2); + + ASSERT_OK_POSITIVE(dns_service_match_and_update(services, rr, AF_INET, 3, 100)); + ASSERT_EQ(service2.until, (usec_t) 10); + ASSERT_EQ(service3.until, (usec_t) 100); +} + +TEST(dns_service_match_and_update_error) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL, *other = NULL; + + ASSERT_NOT_NULL(rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, "..")); + ASSERT_NOT_NULL(other = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, "..")); + + DnssdDiscoveredService service = { + .rr = rr, + .family = AF_INET, + .ifindex = 2, + }; + + ASSERT_ERROR(dns_service_match_and_update(&service, other, AF_INET, 2, 100), EINVAL); +} + +TEST(mdns_answer_contains_service_ifindex) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer2 = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer3 = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + ASSERT_NOT_NULL(answer = dns_answer_new(0)); + ASSERT_NOT_NULL(answer2 = dns_answer_new(0)); + ASSERT_NOT_NULL(answer3 = dns_answer_new(0)); + ASSERT_NOT_NULL(rr = new_test_service_rr(120)); + + DnsServiceBrowser sb_all = { + .ifindex = 0, + }; + DnsServiceBrowser sb_scoped = { + .ifindex = 2, + }; + DnssdDiscoveredService service = { + .rr = rr, + .family = AF_INET, + .ifindex = 2, + }; + + ASSERT_OK_POSITIVE(dns_answer_add(answer, rr, 3, DNS_ANSWER_CACHEABLE, /* rrsig= */ NULL)); + ASSERT_OK_ZERO(mdns_answer_contains_service(&sb_all, answer, &service)); + + ASSERT_OK_POSITIVE(dns_answer_add(answer, rr, 2, DNS_ANSWER_CACHEABLE, /* rrsig= */ NULL)); + ASSERT_OK_POSITIVE(mdns_answer_contains_service(&sb_all, answer, &service)); + + ASSERT_OK_POSITIVE(dns_answer_add(answer2, rr, 0, DNS_ANSWER_CACHEABLE, /* rrsig= */ NULL)); + ASSERT_OK_POSITIVE(mdns_answer_contains_service(&sb_scoped, answer2, &service)); + + ASSERT_OK_POSITIVE(dns_answer_add(answer3, rr, 3, DNS_ANSWER_CACHEABLE, /* rrsig= */ NULL)); + ASSERT_OK_ZERO(mdns_answer_contains_service(&sb_scoped, answer3, &service)); + + sb_scoped.ifindex = 3; + ASSERT_OK_ZERO(mdns_answer_contains_service(&sb_scoped, answer2, &service)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG);