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-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-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 6bd64b52b4..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); } diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 89857f5002..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); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 1509c5b630..b2fa0e3364 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -829,6 +829,7 @@ int manager_start(Manager *m) { Manager* manager_free(Manager *m) { Link *l; DnssdRegisteredService *s; + DnsServiceBrowser *sb; if (!m) return NULL; @@ -906,6 +907,10 @@ Manager* manager_free(Manager *m) { 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); } @@ -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."); } diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 2c0a550c93..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" @@ -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-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,