resolved: only send goodbye for the RRs of the service being unregistered
dns_scope_announce(scope, /* goodbye= */ true) is not scoped to a specific service: it walks the whole per-link mDNS zone and flags every established RR as goodbye (TTL 0). Unregistering a single DNS-SD service via the UnregisterService() D-Bus call hence withdrew the host's own A/AAAA records and every other published service on the link from remote caches, with nothing re-announcing them afterwards. Introduce dns_scope_send_goodbye(), which sends an unsolicited response for a caller-provided RR set, and let the DNS-SD unregistration path send goodbye only for the RRs of the service actually being removed, mirroring the flag logic of dns_scope_announce(). The goodbye answer is built per scope, and only includes records actually established in the respective zone (or under re-verification, in which case they are still served from it): a per-service goodbye must take care not to withdraw records that are still being probed for uniqueness, were withdrawn again after a conflict (in which case the name belongs to the neighbor), or were never published in the scope in the first place (e.g. on MulticastDNS=resolve links, which have an mDNS scope but do not publish DNS-SD services). This becomes more important with the next commit, which promotes unregistration from an explicit method call to an automatic consequence of the registering client disconnecting from the bus.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user