resolved: Add ifindex=0 support for BrowseServices to browse all mDNS interfaces (#40133)

With v259, resolved can be used to replace Avahi for mDNS service
discovery and announcement. One key difference in the API is that Avahi
by default browses on all available interfaces by passing a special
`AVAHI_IF_UNSPEC` (-1) interface index value. With this change, passing
ifindex value `0` can be used to achieve the same , so this will work:
```
# Browse on all available interfaces
varlinkctl call --more unix:/run/systemd/resolve/io.systemd.Resolve \
    io.systemd.Resolve.BrowseServices \
    '{"domain":"local","type":"_display._tcp","ifindex":0,"flags":0}'
```
Not having to iterate through all interfaces on the client side then
also makes it more straight forward to build a resolve-avahi-compat
library for painlessly replacing Avahi with systemd-resolved.

A PoC compat lib requiring this change is available at
https://github.com/LorbusChris/resolve-avahi-compat
This commit is contained in:
Luca Boccassi
2026-02-24 11:53:47 +00:00
committed by GitHub
3 changed files with 109 additions and 10 deletions
+63 -9
View File
@@ -155,7 +155,7 @@ static int mdns_maintenance_query(sd_event_source *s, uint64_t usec, void *userd
return 0;
}
int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_family, usec_t until) {
int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_family, int ifindex, usec_t until) {
_cleanup_(dnssd_discovered_service_unrefp) DnssdDiscoveredService *s = NULL;
int r;
@@ -173,6 +173,7 @@ int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_
.service_browser = sb,
.rr = dns_resource_record_copy(rr),
.family = owner_family,
.ifindex = ifindex,
.until = until,
.query = NULL,
.rr_ttl_state = DNS_RECORD_TTL_STATE_80_PERCENT,
@@ -326,6 +327,7 @@ 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;
if (dns_service_match_and_update(sb->dns_services, item->rr, owner_family, item->until))
continue;
@@ -349,7 +351,10 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow
if (!type)
continue;
r = dns_add_new_service(sb, item->rr, owner_family, item->until);
/* 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");
goto finish;
@@ -360,7 +365,7 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow
strna(type),
strna(domain),
strna(af_to_ipv4_ipv6(owner_family)),
sb->ifindex);
ifindex);
r = sd_json_buildo(
&entry,
@@ -375,7 +380,7 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow
!isempty(type), "type", SD_JSON_BUILD_STRING(type)),
SD_JSON_BUILD_PAIR_CONDITION(
!isempty(domain), "domain", SD_JSON_BUILD_STRING(domain)),
SD_JSON_BUILD_PAIR_INTEGER("ifindex", sb->ifindex));
SD_JSON_BUILD_PAIR_INTEGER("ifindex", ifindex));
if (r < 0) {
log_error_errno(r, "Failed to build JSON for new service: %m");
goto finish;
@@ -392,6 +397,7 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow
LIST_FOREACH(dns_services, service, sb->dns_services) {
_cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL;
int ifindex;
if (service->family != owner_family)
continue;
@@ -416,6 +422,9 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow
}
}
/* Capture ifindex before removing the service */
ifindex = service->ifindex;
dns_remove_service(sb, service);
log_debug("Remove from the list %s, %s, %s, %s, %d",
@@ -423,7 +432,7 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow
strna(type),
strna(domain),
strna(af_to_ipv4_ipv6(owner_family)),
sb->ifindex);
ifindex);
r = sd_json_buildo(
&entry,
@@ -435,7 +444,7 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow
SD_JSON_BUILD_PAIR_STRING("name", name ?: ""),
SD_JSON_BUILD_PAIR_STRING("type", type ?: ""),
SD_JSON_BUILD_PAIR_STRING("domain", domain ?: ""),
SD_JSON_BUILD_PAIR_INTEGER("ifindex", sb->ifindex));
SD_JSON_BUILD_PAIR_INTEGER("ifindex", ifindex));
if (r < 0) {
log_error_errno(r, "Failed to build JSON for removed service: %m");
goto finish;
@@ -473,19 +482,61 @@ finish:
int mdns_browser_revisit_cache(DnsServiceBrowser *sb, int owner_family) {
_cleanup_(dns_answer_unrefp) DnsAnswer *lookup_ret_answer = NULL;
DnsScope *scope;
int r;
assert(sb);
assert(sb->manager);
scope = manager_find_scope_from_protocol(sb->manager, sb->ifindex, DNS_PROTOCOL_MDNS, owner_family);
/* ifindex=0 means "all interfaces" */
if (sb->ifindex == 0) {
LIST_FOREACH(scopes, scope, sb->manager->dns_scopes) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
if (scope->protocol != DNS_PROTOCOL_MDNS)
continue;
if (scope->family != owner_family)
continue;
dns_cache_prune(&scope->cache);
r = dns_cache_lookup(
&scope->cache,
sb->key,
sb->flags,
/* ret_rcode= */ NULL,
&answer,
/* ret_full_packet= */ NULL,
/* ret_query_flags= */ NULL,
/* ret_dnssec_result= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to look up DNS cache for service browser key on scope %s: %m",
dns_scope_ifname(scope) ?: "global");
r = mdns_manage_services_answer(sb, answer, owner_family);
if (r < 0)
return log_error_errno(r, "Failed to manage mDNS services after cache lookup on scope %s: %m",
dns_scope_ifname(scope) ?: "global");
}
return 0;
}
/* Single scope for specifically requested interface */
DnsScope *scope = manager_find_scope_from_protocol(sb->manager, sb->ifindex, DNS_PROTOCOL_MDNS, owner_family);
if (!scope)
return 0;
dns_cache_prune(&scope->cache);
r = dns_cache_lookup(&scope->cache, sb->key, sb->flags, NULL, &lookup_ret_answer, NULL, NULL, NULL);
r = dns_cache_lookup(
&scope->cache,
sb->key,
sb->flags,
/* ret_rcode= */ NULL,
&lookup_ret_answer,
/* ret_full_packet= */ NULL,
/* ret_query_flags= */ NULL,
/* ret_dnssec_result= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to look up DNS cache for service browser key: %m");
@@ -667,6 +718,9 @@ int dns_subscribe_browse_service(
if (ifindex < 0)
return sd_varlink_error_invalid_parameter_name(link, "ifindex");
if (ifindex == 0)
log_debug("BrowseServices: browsing all mDNS interfaces");
if (isempty(type))
type = NULL;
else if (!dnssd_srv_type_is_valid(type))
+2 -1
View File
@@ -30,6 +30,7 @@ struct DnssdDiscoveredService {
sd_event_source *schedule_event;
DnsResourceRecord *rr;
int family;
int ifindex;
usec_t until;
DnsRecordTTLState rr_ttl_state;
DnsQuery *query;
@@ -66,7 +67,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdDiscoveredService *, dnssd_discovered_service_u
bool dns_service_match_and_update(DnssdDiscoveredService *services, DnsResourceRecord *rr, int owner_family, usec_t until);
int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int owner_family);
int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_family, usec_t until);
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);
int mdns_browser_revisit_cache(DnsServiceBrowser *sb, int owner_family);
int dns_subscribe_browse_service(
+44
View File
@@ -193,6 +193,50 @@ testcase_single_service_multiple_times() {
done
}
# Helper function to run browse services with a custom ifindex
run_and_check_services_with_ifindex() {
local service_id="${1:?}"
local check_func="${2:?}"
local ifindex="${3:?}"
local unit_name="varlinkctl-$service_id-$SRANDOM.service"
local i out_file parameters service_type svc tmp_file
out_file="$(mktemp)"
error_file="$(mktemp)"
tmp_file="$(mktemp)"
service_type="_testService$service_id._udp"
parameters="{ \"domain\": \"$service_type.local\", \"type\": \"\", \"ifindex\": $ifindex, \"flags\": 16785432 }"
systemd-run --unit="$unit_name" --service-type=exec -p StandardOutput="file:$out_file" -p StandardError="file:$error_file" \
varlinkctl call --more /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.BrowseServices "$parameters"
# shellcheck disable=SC2064
# Note: same as above about unregistering the trap once it's fired
trap "trap - RETURN; systemctl stop $unit_name" RETURN
for _ in {0..14}; do
if [[ -s "$out_file" ]]; then
grep -o '"name":"[^"]*"' "$out_file" | sed 's/"name":"//;s/"//g' | sort | tee "$tmp_file"
if "$check_func" "$service_id" "$tmp_file"; then
return 0
fi
fi
sleep 2
done
cat "$out_file"
cat "$error_file"
return 1
}
testcase_browse_all_interfaces_ifindex_zero() {
: "Test browsing all interfaces with ifindex=0"
resolvectl flush-caches
# Using ifindex=0 should discover services on all mDNS interfaces
run_and_check_services_with_ifindex 0 check_both 0
}
testcase_second_unreachable() {
: "Test each service type while the second container is unreachable"
systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl down host0