diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 23dc79da5b..dfe4970235 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -12,6 +12,7 @@ systemd_resolved_extract_sources = files( 'resolved-bus.c', 'resolved-conf.c', 'resolved-dns-answer.c', + 'resolved-dns-browse-services.c', 'resolved-dns-cache.c', 'resolved-dns-delegate.c', 'resolved-dns-delegate-bus.c', diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index bbb6580bb0..7a6f1c55b0 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -1847,24 +1847,24 @@ static int bus_method_reset_server_features(sd_bus_message *message, void *userd return sd_bus_reply_method_return(message, NULL); } -static int dnssd_service_on_bus_track(sd_bus_track *t, void *userdata) { - DnssdService *s = ASSERT_PTR(userdata); +static int dnssd_registered_service_on_bus_track(sd_bus_track *t, void *userdata) { + DnssdRegisteredService *s = ASSERT_PTR(userdata); assert(t); log_debug("Client of active request vanished, destroying DNS-SD service."); - dnssd_service_free(s); + dnssd_registered_service_free(s); return 0; } static int bus_method_register_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - _cleanup_(dnssd_service_freep) DnssdService *service = NULL; + _cleanup_(dnssd_registered_service_freep) DnssdRegisteredService *service = NULL; _cleanup_(sd_bus_track_unrefp) sd_bus_track *bus_track = NULL; const char *id, *name_template, *type; _cleanup_free_ char *path = NULL; - DnssdService *s = NULL; + DnssdRegisteredService *s = NULL; Manager *m = ASSERT_PTR(userdata); uid_t euid; int r; @@ -1874,7 +1874,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (m->mdns_support != RESOLVE_SUPPORT_YES) return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Support for MulticastDNS is disabled"); - service = new0(DnssdService, 1); + service = new0(DnssdRegisteredService, 1); if (!service) return log_oom(); @@ -1900,7 +1900,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (!dnssd_srv_type_is_valid(type)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "DNS-SD service type '%s' is invalid", type); - s = hashmap_get(m->dnssd_services, id); + s = hashmap_get(m->dnssd_registered_services, id); if (s) return sd_bus_error_setf(error, BUS_ERROR_DNSSD_SERVICE_EXISTS, "DNS-SD service '%s' exists already", id); @@ -2013,11 +2013,11 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (r == 0) return 1; /* Polkit will call us back */ - r = hashmap_ensure_put(&m->dnssd_services, &string_hash_ops, service->id, service); + r = hashmap_ensure_put(&m->dnssd_registered_services, &string_hash_ops, service->id, service); if (r < 0) return r; - r = sd_bus_track_new(sd_bus_message_get_bus(message), &bus_track, dnssd_service_on_bus_track, service); + r = sd_bus_track_new(sd_bus_message_get_bus(message), &bus_track, dnssd_registered_service_on_bus_track, service); if (r < 0) return r; @@ -2036,7 +2036,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, static int call_dnssd_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) { _cleanup_free_ char *name = NULL; - DnssdService *s = NULL; + DnssdRegisteredService *s = NULL; const char *path; int r; @@ -2054,7 +2054,7 @@ static int call_dnssd_method(Manager *m, sd_bus_message *message, sd_bus_message if (r < 0) return r; - s = hashmap_get(m->dnssd_services, name); + s = hashmap_get(m->dnssd_registered_services, name); if (!s) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DNSSD_SERVICE, "DNS-SD service '%s' not known", name); diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h index de06c8ad28..d2e3d0fea6 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -76,6 +76,10 @@ #define SD_RESOLVED_RELAX_SINGLE_LABEL \ (UINT64_C(1) << 25) +/* Input: To differentiate between a probe and a continuous query. */ +#define SD_RESOLVED_QUERY_CONTINUOUS \ + (UINT64_C(1) << 26) + #define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) #define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) #define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 144b8c3b67..37e2ba1100 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -109,7 +109,8 @@ static int dns_answer_add_raw( DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, - DnsResourceRecord *rrsig) { + DnsResourceRecord *rrsig, + usec_t until) { _cleanup_(dns_answer_item_unrefp) DnsAnswerItem *item = NULL; int r; @@ -131,6 +132,7 @@ static int dns_answer_add_raw( .rr = dns_resource_record_ref(rr), .ifindex = ifindex, .flags = flags, + .until = until, .rrsig = dns_resource_record_ref(rrsig), }; @@ -152,7 +154,8 @@ static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { item->rr, item->ifindex, item->flags, - item->rrsig); + item->rrsig, + /* until= */ USEC_INFINITY); if (r < 0) return r; } @@ -160,12 +163,13 @@ static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { return 0; } -int dns_answer_add( +int dns_answer_add_full( DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, - DnsResourceRecord *rrsig) { + DnsResourceRecord *rrsig, + usec_t until) { DnsAnswerItem tmp, *exist; @@ -220,7 +224,7 @@ int dns_answer_add( return 0; } - return dns_answer_add_raw(a, rr, ifindex, flags, rrsig); + return dns_answer_add_raw(a, rr, ifindex, flags, rrsig, until); } static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { @@ -236,12 +240,13 @@ static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { return 0; } -int dns_answer_add_extend( +int dns_answer_add_extend_full( DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, - DnsResourceRecord *rrsig) { + DnsResourceRecord *rrsig, + usec_t until) { int r; @@ -252,7 +257,7 @@ int dns_answer_add_extend( if (r < 0) return r; - return dns_answer_add(*a, rr, ifindex, flags, rrsig); + return dns_answer_add_full(*a, rr, ifindex, flags, rrsig, until); } int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex) { diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index e4c1d2cb2a..5f4a54c556 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -31,6 +31,7 @@ typedef struct DnsAnswerItem { DnsResourceRecord *rrsig; /* Optionally, also store RRSIG RR that successfully validates this item */ int ifindex; DnsAnswerFlags flags; + usec_t until; } DnsAnswerItem; typedef struct DnsAnswer { @@ -50,8 +51,8 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a); *_a = _b; \ } while(0) -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig); -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig); +int dns_answer_add_full(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig, usec_t until); +int dns_answer_add_extend_full(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig, usec_t until); int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex); int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags); @@ -79,6 +80,16 @@ int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKe int dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); +static inline int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, + int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig) { + return dns_answer_add_extend_full(a, rr, ifindex, flags, rrsig, /* until= */ USEC_INFINITY); +} + +static inline int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, + int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig) { + return dns_answer_add_full(a, rr, ifindex, flags, rrsig, /* until= */ USEC_INFINITY); +} + static inline size_t dns_answer_size(DnsAnswer *a) { return a ? ordered_set_size(a->items) : 0; } diff --git a/src/resolve/resolved-dns-browse-services.c b/src/resolve/resolved-dns-browse-services.c new file mode 100644 index 0000000000..0401dfb754 --- /dev/null +++ b/src/resolve/resolved-dns-browse-services.c @@ -0,0 +1,757 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "af-list.h" +#include "event-util.h" +#include "dns-domain.h" +#include "json-util.h" +#include "random-util.h" +#include "resolved-dns-browse-services.h" +#include "resolved-dns-cache.h" +#include "resolved-dns-question.h" +#include "resolved-dns-query.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-scope.h" +#include "resolved-manager.h" +#include "resolved-varlink.h" +#include "string-table.h" + +typedef enum BrowseServiceUpdateEvent { + BROWSE_SERVICE_UPDATE_ADDED, + BROWSE_SERVICE_UPDATE_REMOVED, + _BROWSE_SERVICE_UPDATE_MAX, + _BROWSE_SERVICE_UPDATE_INVALID = -EINVAL, +} BrowseServiceUpdateEvent; + +static const char * const browse_service_update_event_table[_BROWSE_SERVICE_UPDATE_MAX] = { + [BROWSE_SERVICE_UPDATE_ADDED] = "added", + [BROWSE_SERVICE_UPDATE_REMOVED] = "removed", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(browse_service_update_event, BrowseServiceUpdateEvent); + +/* RFC6762 5.2 + * The intervals between successive queries MUST increase by at least a + * factor of two. When the interval between queries reaches or exceeds + * 60 minutes, perform subsequent queries at a steady-state rate of one + * query per hour. */ +static usec_t mdns_calculate_next_query_delay(usec_t current_delay) { + assert(current_delay <= 60 * 60 * USEC_PER_SEC); + + if (current_delay == 0) + return USEC_PER_SEC; + + return current_delay < 60 * 60 / 2 * USEC_PER_SEC ? current_delay * 2 : 60 * 60 * USEC_PER_SEC; +} + +/* RFC 6762 section 5.2 + * The querier should plan to issue a query at 80% of + * the record lifetime, and then if no answer is received, at 85%, 90%, and 95%. */ +static inline int DNS_RECORD_TTL_STATE_TO_PERCENT(DnsRecordTTLState ttl_state) { + static const int ttl_percent_table[_DNS_RECORD_TTL_STATE_MAX] = { + [DNS_RECORD_TTL_STATE_80_PERCENT] = 80, + [DNS_RECORD_TTL_STATE_85_PERCENT] = 85, + [DNS_RECORD_TTL_STATE_90_PERCENT] = 90, + [DNS_RECORD_TTL_STATE_95_PERCENT] = 95, + [DNS_RECORD_TTL_STATE_100_PERCENT] = 100, + }; + if (ttl_state < 0 || ttl_state >= _DNS_RECORD_TTL_STATE_MAX) + return -EINVAL; + return ttl_percent_table[ttl_state]; +} + +static usec_t mdns_maintenance_next_time(usec_t until, uint32_t ttl, DnsRecordTTLState ttl_state) { + assert(ttl_state >= DNS_RECORD_TTL_STATE_80_PERCENT); + assert(ttl_state < _DNS_RECORD_TTL_STATE_MAX); + + int percent = DNS_RECORD_TTL_STATE_TO_PERCENT(ttl_state); + assert(percent > 0); + assert(percent <= 100); + + return usec_sub_unsigned(until, (100 - percent) * ttl * USEC_PER_SEC / 100); +} + +/* RFC 6762 section 5.2 + * A random variation of 2% of the record TTL should + * be added to maintenance queries. */ +static usec_t mdns_maintenance_jitter(uint32_t ttl) { + return random_u64_range(2 * ttl * USEC_PER_SEC / 100); +} + +static void mdns_maintenance_query_complete(DnsQuery *q) { + _cleanup_(dns_service_browser_unrefp) DnsServiceBrowser *sb = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = q; + DnssdDiscoveredService *service = NULL; + int r; + + assert(query); + assert(query->manager); + + if (query->state != DNS_TRANSACTION_SUCCESS) + return; + + service = dnssd_discovered_service_ref(query->dnsservice_request); + if (!service) + return; + + sb = dns_service_browser_ref(service->service_browser); + if (!sb) + return; + + r = mdns_browser_revisit_cache(sb, query->answer_family); + if (r < 0) + return (void) log_error_errno(r, "Failed to revisit cache for family %s: %m", af_to_name(query->answer_family)); +} + +static int mdns_maintenance_query(sd_event_source *s, uint64_t usec, void *userdata) { + DnssdDiscoveredService *service = ASSERT_PTR(userdata); + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + int r; + + /* Check if the TTL state has reached the maximum value, then revisit + * cache */ + if (service->rr_ttl_state++ == DNS_RECORD_TTL_STATE_100_PERCENT) + return mdns_browser_revisit_cache(service->service_browser, service->family); + + /* Create a new DNS query */ + r = dns_query_new( + service->service_browser->manager, + &q, + service->service_browser->question_utf8, + service->service_browser->question_idna, + /* question_bypass= */ NULL, + service->service_browser->ifindex, + service->service_browser->flags); + if (r < 0) + return log_error_errno(r, "Failed to create mDNS query for maintenance: %m"); + + q->complete = mdns_maintenance_query_complete; + q->varlink_request = sd_varlink_ref(service->service_browser->link); + q->dnsservice_request = dnssd_discovered_service_ref(service); + + /* Schedule the next maintenance query based on the TTL */ + usec_t next_time = mdns_maintenance_next_time(service->until, service->rr->ttl, service->rr_ttl_state); + + r = event_reset_time( + service->service_browser->manager->event, + &service->schedule_event, + CLOCK_BOOTTIME, + next_time, + /* accuracy= */ 0, + mdns_maintenance_query, + service, + /* priority= */ 0, + "mdns-next-query-schedule", + /* force_reset= */ true); + if (r < 0) + return log_error_errno(r, "Failed to schedule next mDNS maintenance query: %m"); + + /* Perform the query */ + r = dns_query_go(q); + if (r < 0) + return log_error_errno(r, "Failed to send mDNS maintenance query: %m"); + + TAKE_PTR(q); + return 0; +} + +int dns_add_new_service(DnsServiceBrowser *sb, DnsResourceRecord *rr, int owner_family, usec_t until) { + _cleanup_(dnssd_discovered_service_unrefp) DnssdDiscoveredService *s = NULL; + int r; + + assert(sb); + assert(rr); + + s = new(DnssdDiscoveredService, 1); + if (!s) + return log_oom(); + + usec_t usec = now(CLOCK_BOOTTIME); + + *s = (DnssdDiscoveredService) { + .n_ref = 1, + .service_browser = sb, + .rr = dns_resource_record_copy(rr), + .family = owner_family, + .until = until, + .query = NULL, + .rr_ttl_state = DNS_RECORD_TTL_STATE_80_PERCENT, + }; + + LIST_PREPEND(dns_services, sb->dns_services, s); + + /* Schedule the first cache maintenance query at 80% of the record's + * TTL. Subsequent queries issued at 5% increments until 100% of the + * TTL. RFC 6762 section 5.2. If service is being added after 80% of the + * TTL has already elapsed, schedule the next query at the next 5% + * increment. */ + usec_t next_time = 0; + while (s->rr_ttl_state >= DNS_RECORD_TTL_STATE_80_PERCENT && + s->rr_ttl_state < _DNS_RECORD_TTL_STATE_MAX) { + next_time = mdns_maintenance_next_time(s->until, s->rr->ttl, s->rr_ttl_state); + if (next_time >= usec) + break; + + s->rr_ttl_state++; + } + + if (next_time < usec) { + /* If next_time is still in the past, the service is being added + * after it has already expired. Just schedule a 100% + * maintenance query. */ + next_time = usec + USEC_PER_SEC; + s->rr_ttl_state = DNS_RECORD_TTL_STATE_100_PERCENT; + } + + usec_t jitter = mdns_maintenance_jitter(rr->ttl); + + r = sd_event_add_time( + sb->manager->event, + &s->schedule_event, + CLOCK_BOOTTIME, + usec_add(next_time, jitter), + /* accuracy= */ 0, + mdns_maintenance_query, + s); + if (r < 0) + return log_error_errno( + r, + "Failed to schedule mDNS maintenance query for DNS service: %m"); + + TAKE_PTR(s); + return 0; +} + +void dns_remove_service(DnsServiceBrowser *sb, DnssdDiscoveredService *service) { + assert(sb); + assert(service); + + LIST_REMOVE(dns_services, sb->dns_services, service); + dnssd_discovered_service_unref(service); +} + +DnssdDiscoveredService *dns_service_free(DnssdDiscoveredService *service) { + if (!service) + return NULL; + + service->schedule_event = sd_event_source_disable_unref(service->schedule_event); + + if (service->query && DNS_TRANSACTION_IS_LIVE(service->query->state)) + dns_query_complete(service->query, DNS_TRANSACTION_ABORTED); + + service->rr = dns_resource_record_unref(service->rr); + + return mfree(service); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(DnssdDiscoveredService, dnssd_discovered_service, dns_service_free); + +int mdns_service_update(DnssdDiscoveredService *service, DnsResourceRecord *rr, usec_t t, usec_t until) { + assert(service); + assert(rr); + + service->until = until; + service->rr->ttl = rr->ttl; + + /* Update the 80% TTL maintenance event based on new record received + * from the network. RFC 6762 section 5.2 */ + if (service->schedule_event) { + usec_t next_time = mdns_maintenance_next_time( + service->until, service->rr->ttl, DNS_RECORD_TTL_STATE_80_PERCENT); + usec_t jitter = mdns_maintenance_jitter(service->rr->ttl); + + return sd_event_source_set_time(service->schedule_event, usec_add(next_time, jitter)); + } + + return 0; +} + +bool dns_service_match_and_update(DnssdDiscoveredService *services, DnsResourceRecord *rr, int owner_family, usec_t until) { + usec_t t = now(CLOCK_BOOTTIME); + + /* 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. */ + + 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; + + if (service->until < until) + mdns_service_update(service, rr, t, until); + + return true; + } + + return false; +} + +void dns_browse_services_purge(Manager *m, int family) { + int r = 0; + + /* Called after caches are flushed. + * Clear local service records and notify varlink client. */ + if (!m) + return; + + DnsServiceBrowser *sb; + HASHMAP_FOREACH(sb, m->dns_service_browsers) { + r = sd_event_source_set_enabled(sb->schedule_event, SD_EVENT_OFF); + if (r < 0) + log_error_errno(r, "Failed to disable event source for service browser, ignoring: %m"); + + if (IN_SET(family, AF_INET, AF_UNSPEC)) { + r = mdns_browser_revisit_cache(sb, AF_INET); + if (r < 0) + log_error_errno(r, "Failed to revisit cache for IPv4, ignoring: %m"); + } + + if (IN_SET(family, AF_INET6, AF_UNSPEC)) { + r = mdns_browser_revisit_cache(sb, AF_INET6); + if (r < 0) + log_error_errno(r, "Failed to revisit cache for IPv6, ignoring: %m"); + } + } +} + +int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int owner_family) { + DnsAnswerItem *item; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + int r; + + assert(sb); + + /* Check for new service added */ + 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; + + if (dns_service_match_and_update(sb->dns_services, item->rr, owner_family, item->until)) + continue; + + r = dns_service_split(item->rr->ptr.name, &name, &type, &domain); + if (r < 0) { + log_error_errno(r, "Failed to split DNS service name: %m"); + goto finish; + } + + if (!name) { + type = mfree(type); + domain = mfree(domain); + r = dns_service_split(dns_resource_key_name(item->rr->key), &name, &type, &domain); + if (r < 0) { + log_error_errno(r, "Failed to split DNS service name (fallback): %m"); + goto finish; + } + } + + if (!type) + continue; + + r = dns_add_new_service(sb, item->rr, owner_family, item->until); + if (r < 0) { + log_error_errno(r, "Failed to add new DNS service: %m"); + goto finish; + } + + log_debug("Add into the list %s, %s, %s, %s, %d", + strna(name), + strna(type), + strna(domain), + strna(af_to_ipv4_ipv6(owner_family)), + sb->ifindex); + + r = sd_json_buildo( + &entry, + SD_JSON_BUILD_PAIR( + "updateFlag", + SD_JSON_BUILD_STRING(browse_service_update_event_to_string( + BROWSE_SERVICE_UPDATE_ADDED))), + SD_JSON_BUILD_PAIR("family", SD_JSON_BUILD_INTEGER(owner_family)), + SD_JSON_BUILD_PAIR_CONDITION( + !isempty(name), "name", SD_JSON_BUILD_STRING(name)), + SD_JSON_BUILD_PAIR_CONDITION( + !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("ifindex", SD_JSON_BUILD_INTEGER(sb->ifindex))); + if (r < 0) { + log_error_errno(r, "Failed to build JSON for new service: %m"); + goto finish; + } + + r = sd_json_variant_append_array(&array, entry); + if (r < 0) { + log_error_errno(r, "Failed to append JSON entry to array: %m"); + goto finish; + } + } + + /* Check for services removed */ + 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; + + if (service->family != owner_family) + continue; + + if (dns_answer_contains(answer, service->rr)) + continue; + + r = dns_service_split(service->rr->ptr.name, &name, &type, &domain); + if (r < 0) { + log_error_errno(r, "Failed to split DNS service name from list: %m"); + goto finish; + } + + if (!name) { + type = mfree(type); + domain = mfree(domain); + r = dns_service_split(dns_resource_key_name(service->rr->key), &name, &type, &domain); + if (r < 0) { + log_error_errno(r, + "Failed to split DNS service name (fallback) from list: %m"); + goto finish; + } + } + + dns_remove_service(sb, service); + + log_debug("Remove from the list %s, %s, %s, %s, %d", + strna(name), + strna(type), + strna(domain), + strna(af_to_ipv4_ipv6(owner_family)), + sb->ifindex); + + r = sd_json_buildo( + &entry, + SD_JSON_BUILD_PAIR( + "updateFlag", + SD_JSON_BUILD_STRING(browse_service_update_event_to_string( + BROWSE_SERVICE_UPDATE_REMOVED))), + SD_JSON_BUILD_PAIR("family", SD_JSON_BUILD_INTEGER(owner_family)), + SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(name ?: "")), + SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(type ?: "")), + SD_JSON_BUILD_PAIR("domain", SD_JSON_BUILD_STRING(domain ?: "")), + SD_JSON_BUILD_PAIR("ifindex", SD_JSON_BUILD_INTEGER(sb->ifindex))); + if (r < 0) { + log_error_errno(r, "Failed to build JSON for removed service: %m"); + goto finish; + } + + r = sd_json_variant_append_array(&array, entry); + if (r < 0) { + log_error_errno(r, "Failed to append JSON entry to array: %m"); + goto finish; + } + } + + if (!sd_json_variant_is_blank_array(array)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *vm = NULL; + + r = sd_json_buildo(&vm, SD_JSON_BUILD_PAIR("browserServiceData", SD_JSON_BUILD_VARIANT(array))); + if (r < 0) { + log_error_errno(r, + "Failed to build JSON object for browser service data: %m"); + goto finish; + } + + r = sd_varlink_notify(sb->link, vm); + if (r < 0) { + log_error_errno(r, "Failed to notify via varlink: %m"); + goto finish; + } + } + + return 0; + +finish: + return sd_varlink_error_errno(sb->link, r); +} + +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); + 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); + if (r < 0) + return log_error_errno(r, "Failed to look up DNS cache for service browser key: %m"); + + r = mdns_manage_services_answer(sb, lookup_ret_answer, owner_family); + if (r < 0) + return log_error_errno(r, "Failed to manage mDNS services after cache lookup: %m"); + + return 0; +} + +int mdns_notify_browsers_goodbye(DnsScope *scope) { + DnsServiceBrowser *sb; + int r; + + if (!scope) + return 0; + + HASHMAP_FOREACH(sb, scope->manager->dns_service_browsers) { + r = mdns_browser_revisit_cache(sb, scope->family); + if (r < 0) + return log_error_errno( + r, + "Failed to revisit cache for service browser with family %d: %m", + scope->family); + } + + return 0; +} + +int mdns_notify_browsers_unsolicited_updates(Manager *m, DnsAnswer *answer, int owner_family) { + DnsServiceBrowser *sb; + int r; + + assert(m); + + if (!answer) + return 0; + + HASHMAP_FOREACH(sb, m->dns_service_browsers) { + + r = dns_answer_match_key(answer, sb->key, NULL); + if (r < 0) + return log_error_errno( + r, + "Failed to match answer key with service browser's key: %m"); + if (r == 0) + continue; + + r = mdns_browser_revisit_cache(sb, owner_family); + if (r < 0) + return log_error_errno(r, "Failed to revisit cache for service browser: %m"); + } + + return 0; +} + +static void mdns_browse_service_query_complete(DnsQuery *q) { + _cleanup_(dns_service_browser_unrefp) DnsServiceBrowser *sb = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = q; + int r; + + assert(query); + assert(query->manager); + + if (query->state != DNS_TRANSACTION_SUCCESS) + return; + + sb = dns_service_browser_ref(query->service_browser_request); + if (!sb) + return; + + r = mdns_browser_revisit_cache(sb, query->answer_family); + if (r < 0) + return (void) log_error_errno(r, "Failed to revisit cache for service browser: %m"); + + /* When the query is answered from cache, we only get answers for one + * answer_family i.e. either ipv4 or ipv6. We need to perform another + * cache lookup for the other answer_family */ + if (query->answer_query_flags == SD_RESOLVED_FROM_CACHE) { + r = mdns_browser_revisit_cache(sb, query->answer_family == AF_INET ? AF_INET6 : AF_INET); + if (r < 0) + return (void) log_error_errno(r, "Failed to revisit cache for service browser: %m"); + } +} + +static int mdns_next_query_schedule(sd_event_source *s, uint64_t usec, void *userdata) { + _cleanup_(dns_service_browser_unrefp) DnsServiceBrowser *sb = NULL; + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + int r; + + assert(userdata); + assert_se(sb = dns_service_browser_ref(userdata)); + + /* Enable the answer from the cache for the very first query */ + if (sb->delay == 0) + SET_FLAG(sb->flags, SD_RESOLVED_NO_CACHE, false); + + /* Set the flag indicating that the query is continuous. + * RFC 6762 Section 5.2 outlines timing requirements for continuous queries. + */ + sb->flags |= SD_RESOLVED_QUERY_CONTINUOUS; + + r = dns_query_new(sb->manager, &q, sb->question_utf8, sb->question_idna, NULL, sb->ifindex, sb->flags); + if (r < 0) + return log_error_errno(r, "Failed to create new DNS query: %m"); + + q->complete = mdns_browse_service_query_complete; + q->service_browser_request = dns_service_browser_ref(sb); + q->varlink_request = sd_varlink_ref(sb->link); + sd_varlink_set_userdata(sb->link, q); + + r = dns_query_go(q); + if (r < 0) + return log_error_errno(r, "Failed to send DNS query: %m"); + + /* Calculate the next query delay */ + sb->delay = mdns_calculate_next_query_delay(sb->delay); + + SET_FLAG(sb->flags, SD_RESOLVED_NO_CACHE, true); + + r = event_reset_time_relative( + sb->manager->event, + &sb->schedule_event, + CLOCK_BOOTTIME, + sb->delay, + /* accuracy= */ 0, + mdns_next_query_schedule, + sb, + /* priority= */ 0, + "mdns-next-query-schedule", + /* force_reset= */ true); + if (r < 0) + return log_error_errno(r, "Failed to reset event time for next query schedule: %m"); + + TAKE_PTR(q); + + return 0; +} + +void dns_browse_services_restart(Manager *m) { + int r; + + if (!(m && m->dns_service_browsers)) + return; + + DnsServiceBrowser *sb; + + HASHMAP_FOREACH(sb, m->dns_service_browsers) { + sb->delay = 0; + + r = event_reset_time_relative( + sb->manager->event, + &sb->schedule_event, + CLOCK_BOOTTIME, + (sb->delay * USEC_PER_SEC), + /* accuracy= */ 0, + mdns_next_query_schedule, + sb, + /* priority= */ 0, + "mdns-next-query-schedule", + /* force_reset= */ true); + + if (r < 0) + log_error_errno(r, + "Failed to reset mDNS service subscriber event for service browser: %m"); + } +} + +int dns_subscribe_browse_service( + Manager *m, sd_varlink *link, const char *domain, const char *type, int ifindex, uint64_t flags) { + + _cleanup_(dns_service_browser_unrefp) DnsServiceBrowser *sb = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + int r; + + assert(m); + assert(link); + + if (ifindex < 0) + return sd_varlink_error_invalid_parameter_name(link, "ifindex"); + + if (isempty(type)) + type = NULL; + else if (!dnssd_srv_type_is_valid(type)) + return sd_varlink_error_invalid_parameter_name(link, "type"); + + if (isempty(domain)) + domain = "local"; + else { + r = dns_name_is_valid(domain); + if (r < 0) + return r; + if (r == 0) + return sd_varlink_error_invalid_parameter_name(link, "domain"); + } + + r = dns_question_new_service_pointer( + &question_utf8, type, domain, /* convert_idna= */ false); + if (r < 0) + return log_error_errno(r, "Failed to create DNS question for UTF8 version: %m"); + + r = dns_question_new_service_pointer( + &question_idna, type, domain, /* convert_idna= */ true); + if (r < 0) + return log_error_errno(r, "Failed to create DNS question for IDNA version: %m"); + + sb = new(DnsServiceBrowser, 1); + if (!sb) + return log_oom(); + + *sb = (DnsServiceBrowser) { + .n_ref = 1, + .manager = m, + .link = sd_varlink_ref(link), + .question_utf8 = dns_question_ref(question_utf8), + .question_idna = dns_question_ref(question_idna), + .key = dns_question_first_key(question_utf8), + .ifindex = ifindex, + .flags = flags, + .delay = 0, + }; + + /* Only mDNS continuous querying is currently supported. See RFC 6762 */ + if (!FLAGS_SET(flags, SD_RESOLVED_MDNS)) + return -EINVAL; + + r = sd_event_add_time_relative( + m->event, + &sb->schedule_event, + CLOCK_BOOTTIME, + sb->delay, + /* accuracy= */ 0, + mdns_next_query_schedule, + sb); + if (r < 0) + return r; + + r = hashmap_ensure_put(&m->dns_service_browsers, NULL, link, sb); + if (r < 0) + return log_error_errno(r, "Failed to add service browser to the hashmap: %m"); + + TAKE_PTR(sb); + + return 0; +} + +DnsServiceBrowser *dns_service_browser_free(DnsServiceBrowser *sb) { + DnsQuery *q; + + if (!sb) + return NULL; + + while (sb->dns_services) + dns_remove_service(sb, sb->dns_services); + + sb->schedule_event = sd_event_source_disable_unref(sb->schedule_event); + + q = sd_varlink_get_userdata(sb->link); + if (q && DNS_TRANSACTION_IS_LIVE(q->state)) + dns_query_complete(q, DNS_TRANSACTION_ABORTED); + + sb->question_idna = dns_question_unref(sb->question_idna); + sb->question_utf8 = dns_question_unref(sb->question_utf8); + + sb->link = sd_varlink_unref(sb->link); + + return mfree(sb); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsServiceBrowser, dns_service_browser, dns_service_browser_free); diff --git a/src/resolve/resolved-dns-browse-services.h b/src/resolve/resolved-dns-browse-services.h new file mode 100644 index 0000000000..0262cee714 --- /dev/null +++ b/src/resolve/resolved-dns-browse-services.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" + +typedef struct DnsServiceBrowser DnsServiceBrowser; +typedef struct DnssdDiscoveredService DnssdDiscoveredService; +typedef struct DnsQuery DnsQuery; +typedef struct DnsScope DnsScope; +typedef struct Manager Manager; +typedef enum DnsRecordTTLState DnsRecordTTLState; + +enum DnsRecordTTLState { + DNS_RECORD_TTL_STATE_80_PERCENT, + DNS_RECORD_TTL_STATE_85_PERCENT, + DNS_RECORD_TTL_STATE_90_PERCENT, + DNS_RECORD_TTL_STATE_95_PERCENT, + DNS_RECORD_TTL_STATE_100_PERCENT, + _DNS_RECORD_TTL_STATE_MAX, + _DNS_RECORD_TTL_STATE_INVALID = -EINVAL +}; + +struct DnssdDiscoveredService { + unsigned n_ref; + DnsServiceBrowser *service_browser; + sd_event_source *schedule_event; + DnsResourceRecord *rr; + int family; + usec_t until; + DnsRecordTTLState rr_ttl_state; + DnsQuery *query; + LIST_FIELDS(DnssdDiscoveredService, dns_services); +}; + +struct DnsServiceBrowser { + unsigned n_ref; + Manager *manager; + sd_varlink *link; + DnsQuestion *question_idna; + DnsQuestion *question_utf8; + uint64_t flags; + sd_event_source *schedule_event; + usec_t delay; + DnsResourceKey *key; + int ifindex; + uint64_t token; + LIST_HEAD(DnssdDiscoveredService, dns_services); +}; + +DnsServiceBrowser *dns_service_browser_free(DnsServiceBrowser *sb); +void dns_remove_service(DnsServiceBrowser *sb, DnssdDiscoveredService *service); +DnssdDiscoveredService *dns_service_free(DnssdDiscoveredService *service); + +DnsServiceBrowser *dns_service_browser_ref(DnsServiceBrowser *sb); +DnsServiceBrowser *dns_service_browser_unref(DnsServiceBrowser *sb); + +DnssdDiscoveredService *dnssd_discovered_service_ref(DnssdDiscoveredService *service); +DnssdDiscoveredService *dnssd_discovered_service_unref(DnssdDiscoveredService *service); + +void dns_browse_services_purge(Manager *m, int family); +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 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 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( + Manager *m, + sd_varlink *link, + const char *domain, + const char *type, + int ifindex, + uint64_t flags); +int mdns_notify_browsers_unsolicited_updates(Manager *m, DnsAnswer *answer, int owner_family); +int mdns_notify_browsers_goodbye(DnsScope *scope); diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 9ae3e78286..5429cefdcd 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -1022,7 +1022,7 @@ static int answer_add_clamp_ttl( } } - r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig); + r = dns_answer_add_extend_full(answer, rr, ifindex, answer_flags, rrsig, until); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 305399dfe8..8ad7bff398 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -520,6 +520,10 @@ DnsQuery *dns_query_free(DnsQuery *q) { free(q->request_address_string); + dnssd_discovered_service_unref(q->dnsservice_request); + + dns_service_browser_unref(q->service_browser_request); + if (q->manager) { LIST_REMOVE(queries, q->manager->dns_queries, q); q->manager->n_dns_queries--; diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 2aea9ad660..9473fb8182 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -3,6 +3,7 @@ #include "in-addr-util.h" #include "list.h" +#include "resolved-dns-browse-services.h" #include "resolved-dns-packet.h" #include "resolved-dns-transaction.h" #include "resolved-forward.h" @@ -105,6 +106,10 @@ typedef struct DnsQuery { DnsAnswer *reply_additional; DnsStubListenerExtra *stub_listener_extra; + /* Browser Service and Dnssd Discovered Service Information */ + DnssdDiscoveredService *dnsservice_request; + DnsServiceBrowser *service_browser_request; + /* Completion callback */ void (*complete)(DnsQuery* q); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index ddbb663b18..0749fbcfd9 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -420,6 +420,53 @@ int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_ return 0; } +int dns_question_new_service_pointer(DnsQuestion **ret, const char *type, const char *domain, bool convert_idna) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL, *joined = NULL; + const char *name; + int r; + + assert(ret); + + if (!domain) + return -EINVAL; + + if (type) { + if (convert_idna) { + r = dns_name_apply_idna(domain, &buf); + if (r < 0) + return r; + if (r > 0) + domain = buf; + } + + r = dns_service_join(NULL, type, domain, &joined); + if (r < 0) + return r; + + name = joined; + } else + name = domain; + + + q = dns_question_new(1); + if (!q) + return -ENOMEM; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_PTR, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key, 0); + if (r < 0) + return r; + + *ret = TAKE_PTR(q); + + return 0; +} + int dns_question_new_service( DnsQuestion **ret, const char *service, diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h index d4298a6031..40084f482f 100644 --- a/src/resolve/resolved-dns-question.h +++ b/src/resolve/resolved-dns-question.h @@ -27,6 +27,7 @@ DnsQuestion *dns_question_unref(DnsQuestion *q); int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna); int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a); int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna); +int dns_question_new_service_pointer(DnsQuestion **ret, const char *type, const char *domain, bool convert_idna); int dns_question_add_raw(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags); int dns_question_add(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 867158aa71..27ccf337d1 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -15,6 +15,7 @@ #include "log.h" #include "random-util.h" #include "resolved-dns-answer.h" +#include "resolved-dns-browse-services.h" #include "resolved-dns-delegate.h" #include "resolved-dns-packet.h" #include "resolved-dns-query.h" @@ -163,6 +164,9 @@ DnsScope* dns_scope_free(DnsScope *s) { dns_cache_flush(&s->cache); dns_zone_flush(&s->zone); + /* Clear records of mDNS service browse subscriber, since cache bas been flushed */ + dns_browse_services_purge(s->manager, s->family); + LIST_REMOVE(scopes, s->manager->dns_scopes, s); return mfree(s); } @@ -1652,18 +1656,18 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { return 0; } -int dns_scope_add_dnssd_services(DnsScope *scope) { - DnssdService *service; +int dns_scope_add_dnssd_registered_services(DnsScope *scope) { + DnssdRegisteredService *service; int r; assert(scope); - if (hashmap_isempty(scope->manager->dnssd_services)) + if (hashmap_isempty(scope->manager->dnssd_registered_services)) return 0; scope->announced = false; - HASHMAP_FOREACH(service, scope->manager->dnssd_services) { + HASHMAP_FOREACH(service, scope->manager->dnssd_registered_services) { service->withdrawn = false; r = dns_zone_put(&scope->zone, scope, service->ptr_rr, false); @@ -1690,9 +1694,9 @@ int dns_scope_add_dnssd_services(DnsScope *scope) { return 0; } -int dns_scope_remove_dnssd_services(DnsScope *scope) { +int dns_scope_remove_dnssd_registered_services(DnsScope *scope) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - DnssdService *service; + DnssdRegisteredService *service; int r; assert(scope); @@ -1706,7 +1710,7 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) { if (r < 0) return r; - HASHMAP_FOREACH(service, scope->manager->dnssd_services) { + HASHMAP_FOREACH(service, scope->manager->dnssd_registered_services) { 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); diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 4e930499ec..f5f78ab65c 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -116,8 +116,8 @@ const char* dns_scope_ifname(DnsScope *s); int dns_scope_announce(DnsScope *scope, bool goodbye); -int dns_scope_add_dnssd_services(DnsScope *scope); -int dns_scope_remove_dnssd_services(DnsScope *scope); +int dns_scope_add_dnssd_registered_services(DnsScope *scope); +int dns_scope_remove_dnssd_registered_services(DnsScope *scope); bool dns_scope_is_default_route(DnsScope *scope); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index d6dbba546c..2e183d15fe 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1708,7 +1708,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { return 0; } - if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { + if (t->n_attempts >= dns_transaction_attempts_max(t->scope->protocol, t->query_flags)) { DnsTransactionState result; if (t->scope->protocol == DNS_PROTOCOL_LLMNR) diff --git a/src/resolve/resolved-dnssd-bus.c b/src/resolve/resolved-dnssd-bus.c index 6b344427d7..4459b0444c 100644 --- a/src/resolve/resolved-dnssd-bus.c +++ b/src/resolve/resolved-dnssd-bus.c @@ -15,7 +15,7 @@ #include "strv.h" int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_error *error) { - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); Manager *m; Link *l; int r; @@ -63,7 +63,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ } } - dnssd_service_free(s); + dnssd_registered_service_free(s); manager_refresh_rrs(m); @@ -73,7 +73,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ static int dnssd_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { _cleanup_free_ char *name = NULL; Manager *m = ASSERT_PTR(userdata); - DnssdService *service; + DnssdRegisteredService *service; int r; assert(bus); @@ -85,7 +85,7 @@ static int dnssd_object_find(sd_bus *bus, const char *path, const char *interfac if (r <= 0) return 0; - service = hashmap_get(m->dnssd_services, name); + service = hashmap_get(m->dnssd_registered_services, name); if (!service) return 0; @@ -96,7 +96,7 @@ static int dnssd_object_find(sd_bus *bus, const char *path, const char *interfac static int dnssd_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { _cleanup_strv_free_ char **l = NULL; Manager *m = ASSERT_PTR(userdata); - DnssdService *service; + DnssdRegisteredService *service; unsigned c = 0; int r; @@ -104,11 +104,11 @@ static int dnssd_node_enumerator(sd_bus *bus, const char *path, void *userdata, assert(path); assert(nodes); - l = new0(char*, hashmap_size(m->dnssd_services) + 1); + l = new0(char*, hashmap_size(m->dnssd_registered_services) + 1); if (!l) return -ENOMEM; - HASHMAP_FOREACH(service, m->dnssd_services) { + HASHMAP_FOREACH(service, m->dnssd_registered_services) { char *p; r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->id, &p); diff --git a/src/resolve/resolved-dnssd-gperf.gperf b/src/resolve/resolved-dnssd-gperf.gperf index b31b30ec6e..bb0d8c3bf1 100644 --- a/src/resolve/resolved-dnssd-gperf.gperf +++ b/src/resolve/resolved-dnssd-gperf.gperf @@ -18,11 +18,11 @@ struct ConfigPerfItem; %struct-type %includes %% -Service.Name, config_parse_dnssd_service_name, 0, 0 -Service.Type, config_parse_dnssd_service_type, 0, 0 -Service.SubType, config_parse_dnssd_service_subtype, 0, 0 -Service.Port, config_parse_ip_port, 0, offsetof(DnssdService, port) -Service.Priority, config_parse_uint16, 0, offsetof(DnssdService, priority) -Service.Weight, config_parse_uint16, 0, offsetof(DnssdService, weight) +Service.Name, config_parse_dnssd_registered_service_name, 0, 0 +Service.Type, config_parse_dnssd_registered_service_type, 0, 0 +Service.SubType, config_parse_dnssd_registered_service_subtype, 0, 0 +Service.Port, config_parse_ip_port, 0, offsetof(DnssdRegisteredService, port) +Service.Priority, config_parse_uint16, 0, offsetof(DnssdRegisteredService, priority) +Service.Weight, config_parse_uint16, 0, offsetof(DnssdRegisteredService, weight) Service.TxtText, config_parse_dnssd_txt, DNS_TXT_ITEM_TEXT, 0 Service.TxtData, config_parse_dnssd_txt, DNS_TXT_ITEM_DATA, 0 diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index 725f823802..8a6b9bcea4 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -46,12 +46,12 @@ DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data) { return dnssd_txtdata_free_all(next); } -DnssdService *dnssd_service_free(DnssdService *service) { +DnssdRegisteredService *dnssd_registered_service_free(DnssdRegisteredService *service) { if (!service) return NULL; if (service->manager) - hashmap_remove(service->manager->dnssd_services, service->id); + hashmap_remove(service->manager->dnssd_registered_services, service->id); dns_resource_record_unref(service->ptr_rr); dns_resource_record_unref(service->sub_ptr_rr); @@ -68,13 +68,13 @@ DnssdService *dnssd_service_free(DnssdService *service) { return mfree(service); } -void dnssd_service_clear_on_reload(Hashmap *services) { - DnssdService *service; +void dnssd_registered_service_clear_on_reload(Hashmap *services) { + DnssdRegisteredService *service; HASHMAP_FOREACH(service, services) if (service->config_source == RESOLVE_CONFIG_SOURCE_FILE) { hashmap_remove(services, service->id); - dnssd_service_free(service); + dnssd_registered_service_free(service); } } @@ -99,8 +99,8 @@ static int dnssd_id_from_path(const char *path, char **ret_id) { return 0; } -static int dnssd_service_load(Manager *manager, const char *path) { - _cleanup_(dnssd_service_freep) DnssdService *service = NULL; +static int dnssd_registered_service_load(Manager *manager, const char *path) { + _cleanup_(dnssd_registered_service_freep) DnssdRegisteredService *service = NULL; _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL; _cleanup_free_ char *dropin_dirname = NULL; int r; @@ -108,7 +108,7 @@ static int dnssd_service_load(Manager *manager, const char *path) { assert(manager); assert(path); - service = new0(DnssdService, 1); + service = new0(DnssdRegisteredService, 1); if (!service) return log_oom(); @@ -158,7 +158,7 @@ static int dnssd_service_load(Manager *manager, const char *path) { TAKE_PTR(txt_data); } - r = hashmap_ensure_put(&manager->dnssd_services, &string_hash_ops, service->id, service); + r = hashmap_ensure_put(&manager->dnssd_registered_services, &string_hash_ops, service->id, service); if (r < 0) return r; @@ -181,7 +181,7 @@ static int specifier_dnssd_hostname(char specifier, const void *data, const char return strdup_to(ret, m->llmnr_hostname); } -int dnssd_render_instance_name(Manager *m, DnssdService *s, char **ret) { +int dnssd_render_instance_name(Manager *m, DnssdRegisteredService *s, char **ret) { static const Specifier specifier_table[] = { { 'a', specifier_architecture, NULL }, { 'b', specifier_boot_id, NULL }, @@ -230,7 +230,7 @@ int dnssd_load(Manager *manager) { return log_error_errno(r, "Failed to enumerate .dnssd files: %m"); STRV_FOREACH_BACKWARDS(f, files) { - r = dnssd_service_load(manager, *f); + r = dnssd_registered_service_load(manager, *f); if (r < 0) log_warning_errno(r, "Failed to load '%s': %m", *f); } @@ -238,7 +238,7 @@ int dnssd_load(Manager *manager) { return 0; } -int dnssd_update_rrs(DnssdService *s) { +int dnssd_update_rrs(DnssdRegisteredService *s) { _cleanup_free_ char *n = NULL, *service_name = NULL, *full_name = NULL, *sub_name = NULL, *selective_name = NULL; int r; @@ -379,13 +379,13 @@ int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t } int dnssd_signal_conflict(Manager *manager, const char *name) { - DnssdService *s; + DnssdRegisteredService *s; int r; if (sd_bus_is_ready(manager->bus) <= 0) return 0; - HASHMAP_FOREACH(s, manager->dnssd_services) { + HASHMAP_FOREACH(s, manager->dnssd_registered_services) { if (s->withdrawn) continue; @@ -413,7 +413,7 @@ int dnssd_signal_conflict(Manager *manager, const char *name) { return 0; } -int config_parse_dnssd_service_name( +int config_parse_dnssd_registered_service_name( const char *unit, const char *filename, unsigned line, @@ -437,7 +437,7 @@ int config_parse_dnssd_service_name( { 'W', specifier_os_variant_id, NULL }, {} }; - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); _cleanup_free_ char *name = NULL; int r; @@ -467,7 +467,7 @@ int config_parse_dnssd_service_name( return free_and_strdup_warn(&s->name_template, rvalue); } -int config_parse_dnssd_service_type( +int config_parse_dnssd_registered_service_type( const char *unit, const char *filename, unsigned line, @@ -479,7 +479,7 @@ int config_parse_dnssd_service_type( void *data, void *userdata) { - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); int r; assert(filename); @@ -503,7 +503,7 @@ int config_parse_dnssd_service_type( return 0; } -int config_parse_dnssd_service_subtype( +int config_parse_dnssd_registered_service_subtype( const char *unit, const char *filename, unsigned line, @@ -515,7 +515,7 @@ int config_parse_dnssd_service_subtype( void *data, void *userdata) { - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); assert(filename); assert(lvalue); @@ -547,7 +547,7 @@ int config_parse_dnssd_txt( void *userdata) { _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL; - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); DnsTxtItem *last = NULL; assert(filename); diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h index 98d6a54a6c..d2c2ccd133 100644 --- a/src/resolve/resolved-dnssd.h +++ b/src/resolve/resolved-dnssd.h @@ -19,7 +19,7 @@ typedef struct DnssdTxtData { LIST_FIELDS(DnssdTxtData, items); } DnssdTxtData; -typedef struct DnssdService { +typedef struct DnssdRegisteredService { char *path; char *id; char *name_template; @@ -44,26 +44,26 @@ typedef struct DnssdService { bool withdrawn:1; uid_t originator; -} DnssdService; +} DnssdRegisteredService; -DnssdService *dnssd_service_free(DnssdService *service); +DnssdRegisteredService *dnssd_registered_service_free(DnssdRegisteredService *service); DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data); DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data); -void dnssd_service_clear_on_reload(Hashmap *services); +void dnssd_registered_service_clear_on_reload(Hashmap *services); -DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdService*, dnssd_service_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdRegisteredService*, dnssd_registered_service_free); DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdTxtData*, dnssd_txtdata_free); -int dnssd_render_instance_name(Manager *m, DnssdService *s, char **ret); +int dnssd_render_instance_name(Manager *m, DnssdRegisteredService *s, char **ret); int dnssd_load(Manager *manager); int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtItem **ret_item); int dnssd_txt_item_new_from_data(const char *key, const void *value, const size_t size, DnsTxtItem **ret_item); -int dnssd_update_rrs(DnssdService *s); +int dnssd_update_rrs(DnssdRegisteredService *s); int dnssd_signal_conflict(Manager *manager, const char *name); const struct ConfigPerfItem* resolved_dnssd_gperf_lookup(const char *key, GPERF_LEN_TYPE length); -CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_name); -CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_subtype); -CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_type); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_registered_service_name); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_registered_service_subtype); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_registered_service_type); CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_txt); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 94b7e3df16..e3640aa9c3 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -17,6 +17,7 @@ #include "mkdir.h" #include "netif-util.h" #include "parse-util.h" +#include "resolved-dns-browse-services.h" #include "resolved-dns-packet.h" #include "resolved-dns-rr.h" #include "resolved-dns-scope.h" @@ -176,6 +177,7 @@ void link_allocate_scopes(Link *l) { r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, DNS_SCOPE_LINK, l, /* delegate= */ NULL, DNS_PROTOCOL_MDNS, AF_INET); if (r < 0) log_link_warning_errno(l, r, "Failed to allocate mDNS IPv4 scope, ignoring: %m"); + dns_browse_services_restart(l->manager); } } else l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); @@ -186,6 +188,7 @@ void link_allocate_scopes(Link *l) { r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, DNS_SCOPE_LINK, l, /* delegate= */ NULL, DNS_PROTOCOL_MDNS, AF_INET6); if (r < 0) log_link_warning_errno(l, r, "Failed to allocate mDNS IPv6 scope, ignoring: %m"); + dns_browse_services_restart(l->manager); } } else l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); @@ -201,13 +204,13 @@ void link_add_rrs(Link *l, bool force_remove) { link_get_mdns_support(l) == RESOLVE_SUPPORT_YES) { if (l->mdns_ipv4_scope) { - r = dns_scope_add_dnssd_services(l->mdns_ipv4_scope); + r = dns_scope_add_dnssd_registered_services(l->mdns_ipv4_scope); if (r < 0) log_link_warning_errno(l, r, "Failed to add IPv4 DNS-SD services, ignoring: %m"); } if (l->mdns_ipv6_scope) { - r = dns_scope_add_dnssd_services(l->mdns_ipv6_scope); + r = dns_scope_add_dnssd_registered_services(l->mdns_ipv6_scope); if (r < 0) log_link_warning_errno(l, r, "Failed to add IPv6 DNS-SD services, ignoring: %m"); } @@ -215,13 +218,13 @@ void link_add_rrs(Link *l, bool force_remove) { } else { if (l->mdns_ipv4_scope) { - r = dns_scope_remove_dnssd_services(l->mdns_ipv4_scope); + r = dns_scope_remove_dnssd_registered_services(l->mdns_ipv4_scope); if (r < 0) log_link_warning_errno(l, r, "Failed to remove IPv4 DNS-SD services, ignoring: %m"); } if (l->mdns_ipv6_scope) { - r = dns_scope_remove_dnssd_services(l->mdns_ipv6_scope); + r = dns_scope_remove_dnssd_registered_services(l->mdns_ipv6_scope); if (r < 0) log_link_warning_errno(l, r, "Failed to remove IPv6 DNS-SD services, ignoring: %m"); } diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 335698c9b1..b2fa0e3364 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -653,7 +653,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa dns_server_unlink_on_reload(m->fallback_dns_servers); m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners); manager_dns_stub_stop(m); - dnssd_service_clear_on_reload(m->dnssd_services); + dnssd_registered_service_clear_on_reload(m->dnssd_registered_services); m->unicast_scope = dns_scope_free(m->unicast_scope); m->delegates = hashmap_free(m->delegates); dns_trust_anchor_flush(&m->trust_anchor); @@ -828,6 +828,8 @@ int manager_start(Manager *m) { Manager* manager_free(Manager *m) { Link *l; + DnssdRegisteredService *s; + DnsServiceBrowser *sb; if (!m) return NULL; @@ -898,14 +900,17 @@ Manager* manager_free(Manager *m) { free(m->llmnr_hostname); free(m->mdns_hostname); - DnssdService *s; - while ((s = hashmap_first(m->dnssd_services))) - dnssd_service_free(s); - hashmap_free(m->dnssd_services); + while ((s = hashmap_first(m->dnssd_registered_services))) + dnssd_registered_service_free(s); + hashmap_free(m->dnssd_registered_services); dns_trust_anchor_flush(&m->trust_anchor); manager_etc_hosts_flush(m); + while ((sb = hashmap_first(m->dns_service_browsers))) + dns_service_browser_free(sb); + hashmap_free(m->dns_service_browsers); + return mfree(m); } @@ -1411,7 +1416,7 @@ int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_a void manager_refresh_rrs(Manager *m) { Link *l; - DnssdService *s; + DnssdRegisteredService *s; assert(m); @@ -1424,7 +1429,7 @@ void manager_refresh_rrs(Manager *m) { link_add_rrs(l, true); if (m->mdns_support == RESOLVE_SUPPORT_YES) - HASHMAP_FOREACH(s, m->dnssd_services) + HASHMAP_FOREACH(s, m->dnssd_registered_services) if (dnssd_update_rrs(s) < 0) log_warning("Failed to refresh DNS-SD service '%s'", s->id); @@ -1540,29 +1545,28 @@ bool manager_packet_from_our_transaction(Manager *m, DnsPacket *p) { return t->sent && dns_packet_equal(t->sent, p); } -DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { +DnsScope* manager_find_scope_from_protocol(Manager *m, int ifindex, DnsProtocol protocol, int family) { Link *l; assert(m); - assert(p); - l = hashmap_get(m->links, INT_TO_PTR(p->ifindex)); + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); if (!l) return NULL; - switch (p->protocol) { + switch (protocol) { case DNS_PROTOCOL_LLMNR: - if (p->family == AF_INET) + if (family == AF_INET) return l->llmnr_ipv4_scope; - else if (p->family == AF_INET6) + else if (family == AF_INET6) return l->llmnr_ipv6_scope; break; case DNS_PROTOCOL_MDNS: - if (p->family == AF_INET) + if (family == AF_INET) return l->mdns_ipv4_scope; - else if (p->family == AF_INET6) + else if (family == AF_INET6) return l->mdns_ipv6_scope; break; @@ -1795,6 +1799,9 @@ void manager_flush_caches(Manager *m, int log_level) { LIST_FOREACH(scopes, scope, m->dns_scopes) dns_cache_flush(&scope->cache); + dns_browse_services_purge(m, AF_UNSPEC); /* Clear records of DNS service browse subscriber, since caches are flushed */ + dns_browse_services_restart(m); + log_full(log_level, "Flushed all caches."); } @@ -1862,13 +1869,13 @@ void manager_cleanup_saved_user(Manager *m) { } bool manager_next_dnssd_names(Manager *m) { - DnssdService *s; + DnssdRegisteredService *s; bool tried = false; int r; assert(m); - HASHMAP_FOREACH(s, m->dnssd_services) { + HASHMAP_FOREACH(s, m->dnssd_registered_services) { _cleanup_free_ char * new_name = NULL; if (!s->withdrawn) diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index cbe635ff94..98e1f4e1d2 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -7,6 +7,7 @@ #include "forward.h" #include "list.h" #include "resolve-util.h" +#include "resolved-dns-browse-services.h" #include "resolved-dns-dnssec.h" #include "resolved-dns-stream.h" #include "resolved-dns-stub.h" @@ -91,7 +92,7 @@ typedef struct Manager { sd_event_source *mdns_ipv6_event_source; /* DNS-SD */ - Hashmap *dnssd_services; + Hashmap *dnssd_registered_services; /* dbus */ sd_bus *bus; @@ -156,6 +157,9 @@ typedef struct Manager { size_t n_socket_graveyard; struct sigrtmin18_info sigrtmin18_info; + + /* Map varlink links to DnsServiceBrowser instances. */ + Hashmap *dns_service_browsers; } Manager; /* Manager */ @@ -183,7 +187,13 @@ int manager_next_hostname(Manager *m); bool manager_packet_from_local_address(Manager *m, DnsPacket *p); bool manager_packet_from_our_transaction(Manager *m, DnsPacket *p); -DnsScope* manager_find_scope(Manager *m, DnsPacket *p); +DnsScope* manager_find_scope_from_protocol(Manager *m, int ifindex, DnsProtocol protocol, int family); + +static inline DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { + assert(m); + assert(p); + return manager_find_scope_from_protocol(m, p->ifindex, p->protocol, p->family); +} void manager_verify_all(Manager *m); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index a1c53ad71b..e8c44365d1 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -382,17 +382,21 @@ static int mdns_goodbye_callback(sd_event_source *s, uint64_t usec, void *userda dns_cache_prune(&scope->cache); + r = mdns_notify_browsers_goodbye(scope); + if (r < 0) + log_warning_errno(r, "mDNS: Failed to notify service subscribers of goodbyes, ignoring: %m"); + if (dns_cache_expiry_in_one_second(&scope->cache, usec)) { r = sd_event_add_time_relative( - scope->manager->event, - &scope->mdns_goodbye_event_source, - CLOCK_BOOTTIME, - USEC_PER_SEC, - 0, - mdns_goodbye_callback, - scope); + scope->manager->event, + &scope->mdns_goodbye_event_source, + CLOCK_BOOTTIME, + USEC_PER_SEC, + /* accuracy= */ 0, + mdns_goodbye_callback, + scope); if (r < 0) - return log_error_errno(r, "mDNS: Failed to re-schedule goodbye callback: %m"); + return log_warning_errno(r, "mDNS: Failed to re-schedule goodbye callback, ignoring: %m"); } return 0; @@ -403,6 +407,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us Manager *m = userdata; DnsScope *scope; int r; + bool unsolicited_packet = true; r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); if (r <= 0) @@ -480,7 +485,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us &scope->mdns_goodbye_event_source, CLOCK_BOOTTIME, USEC_PER_SEC, - 0, + /* accuracy= */ 0, mdns_goodbye_callback, scope); if (r < 0) @@ -489,6 +494,21 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us } } + dns_cache_put( + &scope->cache, + scope->manager->enable_cache, + DNS_PROTOCOL_MDNS, + /* key= */ NULL, + dns_packet_rcode(p), + p->answer, + /* full_packet= */ NULL, + /* query_flags= */ false, + _DNSSEC_RESULT_INVALID, + /* nsec_ttl= */ UINT32_MAX, + p->family, + &p->sender, + scope->manager->stale_retention_usec); + for (bool match = true; match;) { match = false; LIST_FOREACH(transactions_by_scope, t, scope->transactions) { @@ -502,6 +522,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us continue; } + unsolicited_packet = false; /* This packet matches the transaction, let's pass it on as reply */ dns_transaction_process_reply(t, p, false); @@ -512,22 +533,9 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us break; } } - - dns_cache_put( - &scope->cache, - scope->manager->enable_cache, - DNS_PROTOCOL_MDNS, - NULL, - dns_packet_rcode(p), - p->answer, - NULL, - false, - _DNSSEC_RESULT_INVALID, - UINT32_MAX, - p->family, - &p->sender, - scope->manager->stale_retention_usec); - + /* Check if incoming packet key matches with active browse clients. If yes, update the same */ + if (unsolicited_packet) + mdns_notify_browsers_unsolicited_updates(m, p->answer, p->family); } else if (dns_packet_validate_query(p) > 0) { log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); diff --git a/src/resolve/resolved-timeouts.h b/src/resolve/resolved-timeouts.h index 717bccca4a..4028cf2919 100644 --- a/src/resolve/resolved-timeouts.h +++ b/src/resolve/resolved-timeouts.h @@ -19,12 +19,35 @@ /* Maximum attempts to send MDNS requests, see RFC 6762 Section 8.1 */ #define MDNS_TRANSACTION_ATTEMPTS_MAX 3 -#define TRANSACTION_ATTEMPTS_MAX(p) (\ - (p) == DNS_PROTOCOL_LLMNR ? \ - LLMNR_TRANSACTION_ATTEMPTS_MAX : \ - (p) == DNS_PROTOCOL_MDNS ? \ - MDNS_TRANSACTION_ATTEMPTS_MAX : \ - DNS_TRANSACTION_ATTEMPTS_MAX) +/* Maximum attempts to send an mDNS continuous query. + * + * RFC 6762 Section 5.2 does not specify a maximum number of attempts directly. + * However, it outlines two important guidelines: + * + * 1. The interval between the first two queries MUST be at least one second. + * 2. The intervals between successive queries MUST increase by at least a factor of two. + * + * To adhere to these timing requirements for continuous queries, + * the maximum number of attempts should be set to 1. + */ +#define MDNS_TRANSACTION_CONTINUOUS_QUERY_MAX 1U + +static inline unsigned dns_transaction_attempts_max(DnsProtocol p, uint64_t query_flags) { + switch (p) { + + case DNS_PROTOCOL_LLMNR: + return LLMNR_TRANSACTION_ATTEMPTS_MAX; + + case DNS_PROTOCOL_MDNS: + if (FLAGS_SET(query_flags, SD_RESOLVED_QUERY_CONTINUOUS)) + return MDNS_TRANSACTION_CONTINUOUS_QUERY_MAX; + else + return MDNS_TRANSACTION_ATTEMPTS_MAX; + + default: + return DNS_TRANSACTION_ATTEMPTS_MAX; + } +} /* After how much time to repeat classic DNS requests */ #define TRANSACTION_UDP_TIMEOUT_USEC (SD_RESOLVED_QUERY_TIMEOUT_USEC / DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 43bd0b6c6f..606babf2d8 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -11,6 +11,7 @@ #include "in-addr-util.h" #include "json-util.h" #include "resolved-dns-answer.h" +#include "resolved-dns-browse-services.h" #include "resolved-dns-dnssec.h" #include "resolved-dns-packet.h" #include "resolved-dns-query.h" @@ -52,6 +53,13 @@ typedef struct LookupParametersResolveService { uint64_t flags; } LookupParametersResolveService; +typedef struct LookupParamatersBrowseServices { + const char *domain; + const char *type; + int ifindex; + uint64_t flags; +} LookupParamatersBrowseServices; + static void lookup_parameters_destroy(LookupParameters *p) { assert(p); free(p->name); @@ -148,10 +156,18 @@ static int reply_query_state(DnsQuery *q) { static void vl_on_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata) { DnsQuery *q; + Manager *m; assert(s); assert(link); + m = sd_varlink_server_get_userdata(s); + if (!m) + return; + + DnsServiceBrowser *sb = hashmap_remove(m->dns_service_browsers, link); + dns_service_browser_unref(sb); + q = sd_varlink_get_userdata(link); if (!q) return; @@ -1211,6 +1227,41 @@ static int verify_polkit(sd_varlink *link, sd_json_variant *parameters, const ch &m->polkit_registry); } +static int vl_method_browse_services(sd_varlink* link, sd_json_variant* parameters, sd_varlink_method_flags_t flags, void* userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "domain", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParamatersBrowseServices, domain), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParamatersBrowseServices, type), 0 }, + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParamatersBrowseServices, ifindex), 0 }, + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParamatersBrowseServices, flags), 0 }, + {} + }; + + LookupParamatersBrowseServices p = {}; + Manager *m; + int r = 0; + + assert(link); + + /* if the client didn't set the more flag, it is using us incorrectly */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + m = ASSERT_PTR(sd_varlink_server_get_userdata(sd_varlink_get_server(link))); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (validate_and_mangle_query_flags(m, &p.flags, /* name = */ NULL, /* ok = */ 0)) + return sd_varlink_error_invalid_parameter_name(link, "flags"); + + r = dns_subscribe_browse_service(m, link, p.domain, p.type, p.ifindex, p.flags); + if (r < 0) + return sd_varlink_error_errno(link, r); + + return 1; +} + static int vl_method_subscribe_query_results(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *m = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link))); int r; @@ -1470,7 +1521,8 @@ static int varlink_main_server_init(Manager *m) { "io.systemd.Resolve.ResolveRecord", vl_method_resolve_record, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, - "io.systemd.service.GetEnvironment", varlink_method_get_environment); + "io.systemd.service.GetEnvironment", varlink_method_get_environment, + "io.systemd.Resolve.BrowseServices", vl_method_browse_services); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c index dc282d038b..12c43d33f0 100644 --- a/src/shared/varlink-io.systemd.Resolve.c +++ b/src/shared/varlink-io.systemd.Resolve.c @@ -102,6 +102,27 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0)); +static SD_VARLINK_DEFINE_ENUM_TYPE( + BrowseServiceUpdateFlag, + SD_VARLINK_FIELD_COMMENT("Indicates that the service was added."), + SD_VARLINK_DEFINE_ENUM_VALUE(added), + SD_VARLINK_FIELD_COMMENT("Indicates that the service was removed."), + SD_VARLINK_DEFINE_ENUM_VALUE(removed)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ServiceData, + SD_VARLINK_DEFINE_FIELD_BY_TYPE(updateFlag, BrowseServiceUpdateFlag, 0), + SD_VARLINK_FIELD_COMMENT("The address family of the service, one of AF_INET or AF_INET6."), + SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("The name of the service, e.g., 'My Service'. May be null if not specified."), + SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The type of service, e.g., '_http._tcp'."), + SD_VARLINK_DEFINE_FIELD(type, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The domain in which the service resides, e.g., 'local'."), + SD_VARLINK_DEFINE_FIELD(domain, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The Linux interface index for the network interface associated with this service."), + SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, 0)); + static SD_VARLINK_DEFINE_METHOD( ResolveAddress, SD_VARLINK_FIELD_COMMENT("The Linux interface index for the network interface to search on. Typically left unspecified, in order to search on all interfaces."), @@ -159,6 +180,20 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(rrs, ResolvedRecord, SD_VARLINK_ARRAY), SD_VARLINK_DEFINE_OUTPUT(flags, SD_VARLINK_INT, 0)); +static SD_VARLINK_DEFINE_METHOD_FULL( + BrowseServices, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("The domain to browse for services. If null, the default browsing domain local is used."), + SD_VARLINK_DEFINE_INPUT(domain, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The service type to browse for (e.g., '_http._tcp')."), + SD_VARLINK_DEFINE_INPUT(type, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The Linux interface index for the network interface to search on."), + SD_VARLINK_DEFINE_INPUT(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Various browsing flags to modify the operation."), + SD_VARLINK_DEFINE_INPUT(flags, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("An array of service data containing information about discovered services."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(browserServiceData, ServiceData, SD_VARLINK_ARRAY)); + static SD_VARLINK_DEFINE_ERROR(NoNameServers); static SD_VARLINK_DEFINE_ERROR(NoSuchResourceRecord); static SD_VARLINK_DEFINE_ERROR(QueryTimedOut); @@ -199,6 +234,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_ResolveService, SD_VARLINK_SYMBOL_COMMENT("Resolves a domain name to one or more DNS resource records."), &vl_method_ResolveRecord, + SD_VARLINK_SYMBOL_COMMENT("Starts browsing for DNS-SD services of specified type."), + &vl_method_BrowseServices, SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved address."), &vl_type_ResolvedAddress, SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved host name."), @@ -213,6 +250,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ResourceRecord, SD_VARLINK_SYMBOL_COMMENT("Encapsulates information about a resolved DNS resource record "), &vl_type_ResolvedRecord, + SD_VARLINK_SYMBOL_COMMENT("Describes the update flag for browsing services, indicating whether a service was added or removed during browsing."), + &vl_type_BrowseServiceUpdateFlag, + SD_VARLINK_SYMBOL_COMMENT("Encapsulates the service data obtained from browsing."), + &vl_type_ServiceData, &vl_error_NoNameServers, &vl_error_NoSuchResourceRecord, &vl_error_QueryTimedOut, diff --git a/test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build b/test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build new file mode 100644 index 0000000000..9cca5c79a3 --- /dev/null +++ b/test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : 'TEST-89-RESOLVED-MDNS', + }, +] diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 5fb92b172a..19cd5b5310 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -101,6 +101,7 @@ foreach dirname : [ 'TEST-86-MULTI-PROFILE-UKI', 'TEST-87-AUX-UTILS-VM', 'TEST-88-UPGRADE', + 'TEST-89-RESOLVED-MDNS', ] subdir(dirname) endforeach diff --git a/test/units/TEST-89-RESOLVED-MDNS.service b/test/units/TEST-89-RESOLVED-MDNS.service new file mode 100644 index 0000000000..e7ed92c99a --- /dev/null +++ b/test/units/TEST-89-RESOLVED-MDNS.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-89-RESOLVED-MDNS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/TEST-89-RESOLVED-MDNS.sh b/test/units/TEST-89-RESOLVED-MDNS.sh new file mode 100755 index 0000000000..b6e200582f --- /dev/null +++ b/test/units/TEST-89-RESOLVED-MDNS.sh @@ -0,0 +1,254 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +SERVICE_TYPE_COUNT=10 +SERVICE_COUNT=20 +CONTAINER_ZONE="test-$RANDOM" +CONTAINER_1="test-mdns-1" +CONTAINER_2="test-mdns-2" + +# Prepare containers +create_container() { + local container="${1:?}" + local stype sid svc + + # Prepare container's /etc + # + # Since we also need the various test suite related dropins from the host's /etc, + # we'll overlay our customizations on top of that + mkdir -p "/var/lib/machines/$container/etc/systemd/dnssd" + # Create 20 test services for each service type (_testServiceX._udp) and number them sequentially, + # i.e. create services 0-19 for _testService0._udp, services 20-39 for _testService1._udp, and so on + for stype in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + for sid in $(seq 0 $((SERVICE_COUNT - 1))); do + svc=$((stype * SERVICE_COUNT + sid)) + + cat >"/var/lib/machines/$container/etc/systemd/dnssd/test-service-$container-$svc.dnssd" <"/run/systemd/system/systemd-nspawn@$container.service.d/override.conf" <&2 "Found a record from an unreachable container" + cat "$result_file" + exit 1 + fi + done + + # We got all records and all of them are what we expect + return 0 + fi + + return 1 +} + +run_and_check_services() { + local service_id="${1:?}" + local check_func="${2:?}" + 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\": ${BRIDGE_INDEX:?}, \"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: unregister the trap once it's fired, otherwise it'll get propagated to functions that call this + # one, *sigh* + + trap "trap - RETURN; systemctl stop $unit_name" RETURN + + for _ in {0..14}; do + # The response format, for reference (it's JSON-SEQ): + # + # { + # "browser_service_data": [ + # { + # "updateFlag": true, + # "family": 10, + # "name": "Test Service 13 on test-mdns-1", + # "type": "_testService0._udp", + # "domain": "local", + # "interface": 3 + # }, + # ... + # ] + # } + if [[ -s "$out_file" ]]; then + # Extract the service name from each valid record... + # jq --slurp --raw-output \ + # ".[].browser_service_data[] | select(.updateFlag == true and .type == \"$service_type\" and .family == 10).name" "$out_file" | sort | tee "$tmp_file" + grep -o '"name":"[^"]*"' "$out_file" | sed 's/"name":"//;s/"//g' | sort | tee "$tmp_file" + # ...and compare them with what we expect + if "$check_func" "$service_id" "$tmp_file"; then + return 0 + fi + fi + + sleep 2 + done + + cat "$out_file" + cat "$error_file" + return 1 +} + +testcase_all_sequential() { + : "Test each service type (sequentially)" + resolvectl flush-caches + for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + run_and_check_services "$id" check_both + done + + echo testcase_end +} + +testcase_all_parallel() { + : "Test each service type (in parallel)" + resolvectl flush-caches + for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + run_and_check_services "$id" check_both & + done + wait +} + +testcase_single_service_multiple_times() { + : "Test one service type multiple times" + resolvectl flush-caches + for _ in {0..4}; do + run_and_check_services 4 check_both + done +} + +testcase_second_unreachable() { + : "Test each service type while the second container is unreachable" + systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl down host0 + resolvectl flush-caches + for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + run_and_check_services "$id" check_first + done + + + : "Test each service type after bringing the second container back up again" + systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl up host0 + systemd-run -M "$CONTAINER_2" --wait --pipe -- \ + /usr/lib/systemd/systemd-networkd-wait-online --ipv4 --ipv6 --interface=host0 --operational-state=degraded --timeout=30 + for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + run_and_check_services "$id" check_both + done +} + +: "Setup host & containers" +# Note: create the drop-in intentionally under /run/ and copy it manually into the containers +mkdir -p /run/systemd/resolved.conf.d/ +cat >/run/systemd/resolved.conf.d/99-mdns-llmnr.conf <