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.
This commit is contained in:
dongshengyuan
2026-08-16 03:07:32 +09:00
committed by Yu Watanabe
parent db7fc6cde8
commit 9711c84c2d
4 changed files with 253 additions and 21 deletions
+3
View 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'),
},
+90 -20
View 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 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. */
/* 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. */
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;
LIST_FOREACH(dns_services, service, services) {
r = dns_service_matches(service, rr, owner_family, ifindex);
if (r < 0)
return r;
if (r == 0)
continue;
if (service->until < until)
mdns_service_update(service, rr, t, until);
if (rr->ttl <= 1)
return 1;
return true;
}
if (service->until < until)
mdns_service_update(service, rr, t, until);
return false;
return 1;
}
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);
+10 -1
View 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);
+150
View File
@@ -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);