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:
committed by
Yu Watanabe
parent
db7fc6cde8
commit
9711c84c2d
@@ -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'),
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user