]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: match browsed mDNS services by ifindex
authordongshengyuan <545258830@qq.com>
Fri, 14 Aug 2026 07:21:30 +0000 (15:21 +0800)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 15 Aug 2026 18:07:32 +0000 (03:07 +0900)
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.

src/resolve/meson.build
src/resolve/resolved-dns-browse-services.c
src/resolve/resolved-dns-browse-services.h
src/resolve/test-dns-browse-services.c [new file with mode: 0644]

index 3181ace1cb2cefcafab23c574634861dfd4c090b..ad6a64dd602effd29a21768d61a246b07c1040f8 100644 (file)
@@ -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'),
         },
index 19b773abf448258125288c596f962876beb70be5..445ab974f1bd95647eddff7406e119d05be22d3d 100644 (file)
@@ -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);
index 424b2855a5b750f5776551cb1b2f952a09872319..84095d55598f35a35fbdca7ff3ae7debf4c22138 100644 (file)
@@ -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 (file)
index 0000000..d9ea99d
--- /dev/null
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <string.h>
+#include <sys/socket.h>
+
+#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);