]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolve: Implement continuous mDNS querying as per RFC6762 5.2
authorVishal Chillara Srinivas <vishal.chillarasrinivas@philips.com>
Tue, 15 Jul 2025 17:25:30 +0000 (22:55 +0530)
committerVishal Chillara Srinivas <vishal.chillarasrinivas@philips.com>
Tue, 15 Jul 2025 17:25:31 +0000 (22:55 +0530)
Allow for mDNS service/domain/types browsing.

A client can connect to the backend via varlink and receive updates as the
requested service becomes available.
The interval between the first two queries MUST be at least one second,
the intervals between successive queries MUST increase by at least a factor of two.
When the interval between queries reaches or exceeds 60 minutes, a querier MAY cap
the interval to a maximum of 60 minutes, and perform subsequent queries at a
steady-state rate of one query per hour.
Delete expired cache entries one second after goodbye packet received
as per RFC6762 Section 10.1

Cache maintenance:
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%.
If an answer is received, then the remaining TTL is reset to the value given
in the answer, and this process repeats for as long as the Multicast DNS querier
has an ongoing interest in the record.
If no answer is received after four queries, the record is deleted when it
reaches 100% of its lifetime.

TODO:
Improve the DNS transaction logic when multiple clients subscribe to
the same service, ensuring that continuous queries are optimized.

Co-authored-by: Vishwanath Chandapur <vishwanath.chandapur@philips.com>
14 files changed:
src/resolve/meson.build
src/resolve/resolved-dns-browse-services.c [new file with mode: 0644]
src/resolve/resolved-dns-browse-services.h [new file with mode: 0644]
src/resolve/resolved-dns-query.c
src/resolve/resolved-dns-query.h
src/resolve/resolved-dns-question.c
src/resolve/resolved-dns-question.h
src/resolve/resolved-dns-scope.c
src/resolve/resolved-link.c
src/resolve/resolved-manager.c
src/resolve/resolved-manager.h
src/resolve/resolved-mdns.c
src/resolve/resolved-varlink.c
src/shared/varlink-io.systemd.Resolve.c

index 23dc79da5bc75ef750f56dd0b5ddcf568ae8d460..dfe4970235623290e9adb8978e1a789bcde501ae 100644 (file)
@@ -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 (file)
index 0000000..0401dfb
--- /dev/null
@@ -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 (file)
index 0000000..0262cee
--- /dev/null
@@ -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);
index 305399dfe8b9a91cbb3c3f8e966a92411efa40d1..8ad7bff398d85de8c527ca56f61fdfa56e67c372 100644 (file)
@@ -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--;
index 2aea9ad660077b63f64a834b219cadcad965e696..9473fb818210e6d406026d1a30601b8283a505d2 100644 (file)
@@ -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);
 
index ddbb663b189d4023cee2ac799804fa2d78a2d93a..0749fbcfd94196299d6ce35c81eab79c110643c3 100644 (file)
@@ -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,
index d4298a60311ce656164d2a24bf47b09abb08115f..40084f482f3c08b9c65625149150291018def6ee 100644 (file)
@@ -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);
index 6bd64b52b486662ac5845e431af85395330a9856..27ccf337d10269af1e40b0e77a0638084ee9876b 100644 (file)
@@ -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);
 }
index 89857f500249273a369287ee59278a3e75aa4b01..e3640aa9c36581a336bba74e497a338d0857cef9 100644 (file)
@@ -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);
index 1509c5b630abca4daac74f7b7bda3488cc94c55e..b2fa0e3364bc6b8fcffbb1c9682c1ce37f3fa809 100644 (file)
@@ -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.");
 }
 
index 2c0a550c936b1df30b5ba64cd59464f4ceed39ef..98e1f4e1d2a6fff9acc59c50df60da19dccb37bf 100644 (file)
@@ -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);
 
index a1c53ad71b1659307330829ecb88b55eb5a2d19b..e8c44365d1a5a16d443e6142e83ee7047c1d132c 100644 (file)
@@ -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));
 
index 43bd0b6c6f60c1b5822527fe4560730984ac3794..606babf2d8003942fd009bf82d906485aba9e2dc 100644 (file)
@@ -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");
 
index dc282d038b7236779e90e4636cf1521ca5a0a915..12c43d33f0f17bf5c30d1c362fb6a6347edb937e 100644 (file)
@@ -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,