diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index d48896494f..6a20fa20da 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -1533,6 +1533,42 @@ static int on_announcement_timeout(sd_event_source *s, usec_t usec, void *userda return 0; } +int dns_scope_send_goodbye(DnsScope *scope, DnsAnswer *answer) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(scope); + assert(answer); + + /* Sends an unsolicited response withdrawing the specified RRs, which are expected to be flagged as + * goodbye (TTL 0), without touching anything else published in the zone. */ + + if (scope->protocol != DNS_PROTOCOL_MDNS) + return 0; + + r = sd_event_get_state(scope->manager->event); + if (r < 0) + return log_debug_errno(r, "Failed to get event loop state: %m"); + + /* If this is called on exit, through manager_free() -> link_free(), then we cannot announce. */ + if (r == SD_EVENT_FINISHED) + return 0; + + if (dns_answer_isempty(answer)) + return 0; + + r = dns_scope_make_reply_packet(scope, /* id= */ 0, DNS_RCODE_SUCCESS, /* q= */ NULL, answer, + /* soa= */ NULL, /* tentative= */ false, &p); + if (r < 0) + return log_debug_errno(r, "Failed to build reply packet: %m"); + + r = dns_scope_emit_udp(scope, -EBADF, AF_UNSPEC, p); + if (r < 0) + return log_debug_errno(r, "Failed to send reply packet: %m"); + + return 0; +} + int dns_scope_announce(DnsScope *scope, bool goodbye) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 1367f55242..9f97a5ffcb 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -114,6 +114,7 @@ int dns_scope_ifindex(DnsScope *s); const char* dns_scope_ifname(DnsScope *s); int dns_scope_announce(DnsScope *scope, bool goodbye); +int dns_scope_send_goodbye(DnsScope *scope, DnsAnswer *answer); int dns_scope_add_dnssd_registered_services(DnsScope *scope); int dns_scope_remove_dnssd_registered_services(DnsScope *scope); diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index 39cb67fefe..5617293cb7 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -6,6 +6,7 @@ #include "conf-files.h" #include "conf-parser.h" #include "constants.h" +#include "dns-answer.h" #include "dns-domain.h" #include "dns-rr.h" #include "extract-word.h" @@ -71,6 +72,67 @@ DnssdRegisteredService *dnssd_registered_service_free(DnssdRegisteredService *se return mfree(service); } +static int dnssd_registered_service_make_goodbye( + DnssdRegisteredService *service, + DnsScope *scope, + DnsAnswer **ret) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + size_t n_rrs = 3; + int r; + + assert(service); + assert(scope); + assert(ret); + + /* Collects those of the service's RRs that are established in the scope's zone (or under + * re-verification, in which case they are still served from the zone), flagged as goodbye (TTL 0), + * so that only this service is withdrawn, and only where we actually asserted ownership of the + * records, i.e. not while a record is still being probed for uniqueness, was withdrawn again after + * a conflict, or was never published in this scope in the first place. */ + + LIST_FOREACH(items, txt_data, service->txt_data_items) + n_rrs++; + + answer = dns_answer_new(n_rrs); + if (!answer) + return -ENOMEM; + + DnsResourceRecord *rrs[] = { service->ptr_rr, service->sub_ptr_rr, service->srv_rr }; + + FOREACH_ELEMENT(rr, rrs) { + if (!*rr) + continue; + + DnsZoneItem *i = dns_zone_get(&scope->zone, *rr); + if (!i || !IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + r = dns_answer_add(answer, *rr, /* ifindex= */ 0, + dns_resource_key_is_dnssd_ptr((*rr)->key) ? + DNS_ANSWER_GOODBYE : (DNS_ANSWER_GOODBYE|DNS_ANSWER_CACHE_FLUSH), + /* rrsig= */ NULL); + if (r < 0) + return r; + } + + LIST_FOREACH(items, txt_data, service->txt_data_items) { + if (!txt_data->rr) + continue; + + DnsZoneItem *i = dns_zone_get(&scope->zone, txt_data->rr); + if (!i || !IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + r = dns_answer_add(answer, txt_data->rr, /* ifindex= */ 0, + DNS_ANSWER_GOODBYE|DNS_ANSWER_CACHE_FLUSH, /* rrsig= */ NULL); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(answer); + return 0; +} + void dnssd_registered_service_unregister(DnssdRegisteredService *service) { Link *l; int r; @@ -82,32 +144,26 @@ void dnssd_registered_service_unregister(DnssdRegisteredService *service) { /* Takes the service out of service: sends goodbye messages for it, removes its RRs from all mDNS * zones, and drops it from the manager. Note that this also frees the passed object. */ - HASHMAP_FOREACH(l, m->links) { - if (l->mdns_ipv4_scope) { - r = dns_scope_announce(l->mdns_ipv4_scope, /* goodbye= */ true); + HASHMAP_FOREACH(l, m->links) + FOREACH_ELEMENT(scope, ((DnsScope*[]) { l->mdns_ipv4_scope, l->mdns_ipv6_scope })) { + if (!*scope) + continue; + + _cleanup_(dns_answer_unrefp) DnsAnswer *goodbye = NULL; + + r = dnssd_registered_service_make_goodbye(service, *scope, &goodbye); + if (r >= 0) + r = dns_scope_send_goodbye(*scope, goodbye); if (r < 0) - log_warning_errno(r, "Failed to send goodbye messages in IPv4 scope, ignoring: %m"); + log_warning_errno(r, "Failed to send goodbye messages, ignoring: %m"); - dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, service->ptr_rr); - dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, service->sub_ptr_rr); - dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, service->srv_rr); + dns_zone_remove_rr(&(*scope)->zone, service->ptr_rr); + dns_zone_remove_rr(&(*scope)->zone, service->sub_ptr_rr); + dns_zone_remove_rr(&(*scope)->zone, service->srv_rr); LIST_FOREACH(items, txt_data, service->txt_data_items) - dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, txt_data->rr); + dns_zone_remove_rr(&(*scope)->zone, txt_data->rr); } - if (l->mdns_ipv6_scope) { - r = dns_scope_announce(l->mdns_ipv6_scope, /* goodbye= */ true); - if (r < 0) - log_warning_errno(r, "Failed to send goodbye messages in IPv6 scope, ignoring: %m"); - - dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, service->ptr_rr); - dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, service->sub_ptr_rr); - dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, service->srv_rr); - LIST_FOREACH(items, txt_data, service->txt_data_items) - dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, txt_data->rr); - } - } - /* This drops the service from the manager's hashmap, hence the subsequent refresh will not simply * add its RRs back. */ dnssd_registered_service_free(service);