diff --git a/src/resolve/resolved-dns-browse-services.c b/src/resolve/resolved-dns-browse-services.c index 6c47197639..f08b83cf5d 100644 --- a/src/resolve/resolved-dns-browse-services.c +++ b/src/resolve/resolved-dns-browse-services.c @@ -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)) diff --git a/src/resolve/resolved-dns-browse-services.h b/src/resolve/resolved-dns-browse-services.h index 243d23cb2d..424b2855a5 100644 --- a/src/resolve/resolved-dns-browse-services.h +++ b/src/resolve/resolved-dns-browse-services.h @@ -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( diff --git a/test/units/TEST-89-RESOLVED-MDNS.sh b/test/units/TEST-89-RESOLVED-MDNS.sh index ae18cd49e8..8e8c4e38a9 100755 --- a/test/units/TEST-89-RESOLVED-MDNS.sh +++ b/test/units/TEST-89-RESOLVED-MDNS.sh @@ -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