From 108bd5c2970322cc3af9677f4b1d1f9c6909d440 Mon Sep 17 00:00:00 2001 From: Vishal Chillara Srinivas Date: Thu, 22 May 2025 22:46:23 +0530 Subject: [PATCH 1/5] resolve: Rename 'DnssdService', update all dependent code components Prepare for upcoming mDNS browse services. Distinguish between registered and discovered services to avoid confusion. Co-authored-by: Vishwanath Chandapur --- src/resolve/resolved-bus.c | 22 +++++++------- src/resolve/resolved-dns-scope.c | 14 ++++----- src/resolve/resolved-dns-scope.h | 4 +-- src/resolve/resolved-dnssd-bus.c | 14 ++++----- src/resolve/resolved-dnssd-gperf.gperf | 12 ++++---- src/resolve/resolved-dnssd.c | 42 +++++++++++++------------- src/resolve/resolved-dnssd.h | 20 ++++++------ src/resolve/resolved-link.c | 8 ++--- src/resolve/resolved-manager.c | 18 +++++------ src/resolve/resolved-manager.h | 2 +- 10 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index bbb6580bb0..7a6f1c55b0 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -1847,24 +1847,24 @@ static int bus_method_reset_server_features(sd_bus_message *message, void *userd return sd_bus_reply_method_return(message, NULL); } -static int dnssd_service_on_bus_track(sd_bus_track *t, void *userdata) { - DnssdService *s = ASSERT_PTR(userdata); +static int dnssd_registered_service_on_bus_track(sd_bus_track *t, void *userdata) { + DnssdRegisteredService *s = ASSERT_PTR(userdata); assert(t); log_debug("Client of active request vanished, destroying DNS-SD service."); - dnssd_service_free(s); + dnssd_registered_service_free(s); return 0; } static int bus_method_register_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - _cleanup_(dnssd_service_freep) DnssdService *service = NULL; + _cleanup_(dnssd_registered_service_freep) DnssdRegisteredService *service = NULL; _cleanup_(sd_bus_track_unrefp) sd_bus_track *bus_track = NULL; const char *id, *name_template, *type; _cleanup_free_ char *path = NULL; - DnssdService *s = NULL; + DnssdRegisteredService *s = NULL; Manager *m = ASSERT_PTR(userdata); uid_t euid; int r; @@ -1874,7 +1874,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (m->mdns_support != RESOLVE_SUPPORT_YES) return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Support for MulticastDNS is disabled"); - service = new0(DnssdService, 1); + service = new0(DnssdRegisteredService, 1); if (!service) return log_oom(); @@ -1900,7 +1900,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (!dnssd_srv_type_is_valid(type)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "DNS-SD service type '%s' is invalid", type); - s = hashmap_get(m->dnssd_services, id); + s = hashmap_get(m->dnssd_registered_services, id); if (s) return sd_bus_error_setf(error, BUS_ERROR_DNSSD_SERVICE_EXISTS, "DNS-SD service '%s' exists already", id); @@ -2013,11 +2013,11 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (r == 0) return 1; /* Polkit will call us back */ - r = hashmap_ensure_put(&m->dnssd_services, &string_hash_ops, service->id, service); + r = hashmap_ensure_put(&m->dnssd_registered_services, &string_hash_ops, service->id, service); if (r < 0) return r; - r = sd_bus_track_new(sd_bus_message_get_bus(message), &bus_track, dnssd_service_on_bus_track, service); + r = sd_bus_track_new(sd_bus_message_get_bus(message), &bus_track, dnssd_registered_service_on_bus_track, service); if (r < 0) return r; @@ -2036,7 +2036,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, static int call_dnssd_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) { _cleanup_free_ char *name = NULL; - DnssdService *s = NULL; + DnssdRegisteredService *s = NULL; const char *path; int r; @@ -2054,7 +2054,7 @@ static int call_dnssd_method(Manager *m, sd_bus_message *message, sd_bus_message if (r < 0) return r; - s = hashmap_get(m->dnssd_services, name); + s = hashmap_get(m->dnssd_registered_services, name); if (!s) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DNSSD_SERVICE, "DNS-SD service '%s' not known", name); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 867158aa71..6bd64b52b4 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -1652,18 +1652,18 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { return 0; } -int dns_scope_add_dnssd_services(DnsScope *scope) { - DnssdService *service; +int dns_scope_add_dnssd_registered_services(DnsScope *scope) { + DnssdRegisteredService *service; int r; assert(scope); - if (hashmap_isempty(scope->manager->dnssd_services)) + if (hashmap_isempty(scope->manager->dnssd_registered_services)) return 0; scope->announced = false; - HASHMAP_FOREACH(service, scope->manager->dnssd_services) { + HASHMAP_FOREACH(service, scope->manager->dnssd_registered_services) { service->withdrawn = false; r = dns_zone_put(&scope->zone, scope, service->ptr_rr, false); @@ -1690,9 +1690,9 @@ int dns_scope_add_dnssd_services(DnsScope *scope) { return 0; } -int dns_scope_remove_dnssd_services(DnsScope *scope) { +int dns_scope_remove_dnssd_registered_services(DnsScope *scope) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - DnssdService *service; + DnssdRegisteredService *service; int r; assert(scope); @@ -1706,7 +1706,7 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) { if (r < 0) return r; - HASHMAP_FOREACH(service, scope->manager->dnssd_services) { + HASHMAP_FOREACH(service, scope->manager->dnssd_registered_services) { dns_zone_remove_rr(&scope->zone, service->ptr_rr); dns_zone_remove_rr(&scope->zone, service->sub_ptr_rr); dns_zone_remove_rr(&scope->zone, service->srv_rr); diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 4e930499ec..f5f78ab65c 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -116,8 +116,8 @@ const char* dns_scope_ifname(DnsScope *s); int dns_scope_announce(DnsScope *scope, bool goodbye); -int dns_scope_add_dnssd_services(DnsScope *scope); -int dns_scope_remove_dnssd_services(DnsScope *scope); +int dns_scope_add_dnssd_registered_services(DnsScope *scope); +int dns_scope_remove_dnssd_registered_services(DnsScope *scope); bool dns_scope_is_default_route(DnsScope *scope); diff --git a/src/resolve/resolved-dnssd-bus.c b/src/resolve/resolved-dnssd-bus.c index 6b344427d7..4459b0444c 100644 --- a/src/resolve/resolved-dnssd-bus.c +++ b/src/resolve/resolved-dnssd-bus.c @@ -15,7 +15,7 @@ #include "strv.h" int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_error *error) { - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); Manager *m; Link *l; int r; @@ -63,7 +63,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ } } - dnssd_service_free(s); + dnssd_registered_service_free(s); manager_refresh_rrs(m); @@ -73,7 +73,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ static int dnssd_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { _cleanup_free_ char *name = NULL; Manager *m = ASSERT_PTR(userdata); - DnssdService *service; + DnssdRegisteredService *service; int r; assert(bus); @@ -85,7 +85,7 @@ static int dnssd_object_find(sd_bus *bus, const char *path, const char *interfac if (r <= 0) return 0; - service = hashmap_get(m->dnssd_services, name); + service = hashmap_get(m->dnssd_registered_services, name); if (!service) return 0; @@ -96,7 +96,7 @@ static int dnssd_object_find(sd_bus *bus, const char *path, const char *interfac static int dnssd_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { _cleanup_strv_free_ char **l = NULL; Manager *m = ASSERT_PTR(userdata); - DnssdService *service; + DnssdRegisteredService *service; unsigned c = 0; int r; @@ -104,11 +104,11 @@ static int dnssd_node_enumerator(sd_bus *bus, const char *path, void *userdata, assert(path); assert(nodes); - l = new0(char*, hashmap_size(m->dnssd_services) + 1); + l = new0(char*, hashmap_size(m->dnssd_registered_services) + 1); if (!l) return -ENOMEM; - HASHMAP_FOREACH(service, m->dnssd_services) { + HASHMAP_FOREACH(service, m->dnssd_registered_services) { char *p; r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->id, &p); diff --git a/src/resolve/resolved-dnssd-gperf.gperf b/src/resolve/resolved-dnssd-gperf.gperf index b31b30ec6e..bb0d8c3bf1 100644 --- a/src/resolve/resolved-dnssd-gperf.gperf +++ b/src/resolve/resolved-dnssd-gperf.gperf @@ -18,11 +18,11 @@ struct ConfigPerfItem; %struct-type %includes %% -Service.Name, config_parse_dnssd_service_name, 0, 0 -Service.Type, config_parse_dnssd_service_type, 0, 0 -Service.SubType, config_parse_dnssd_service_subtype, 0, 0 -Service.Port, config_parse_ip_port, 0, offsetof(DnssdService, port) -Service.Priority, config_parse_uint16, 0, offsetof(DnssdService, priority) -Service.Weight, config_parse_uint16, 0, offsetof(DnssdService, weight) +Service.Name, config_parse_dnssd_registered_service_name, 0, 0 +Service.Type, config_parse_dnssd_registered_service_type, 0, 0 +Service.SubType, config_parse_dnssd_registered_service_subtype, 0, 0 +Service.Port, config_parse_ip_port, 0, offsetof(DnssdRegisteredService, port) +Service.Priority, config_parse_uint16, 0, offsetof(DnssdRegisteredService, priority) +Service.Weight, config_parse_uint16, 0, offsetof(DnssdRegisteredService, weight) Service.TxtText, config_parse_dnssd_txt, DNS_TXT_ITEM_TEXT, 0 Service.TxtData, config_parse_dnssd_txt, DNS_TXT_ITEM_DATA, 0 diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index 725f823802..8a6b9bcea4 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -46,12 +46,12 @@ DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data) { return dnssd_txtdata_free_all(next); } -DnssdService *dnssd_service_free(DnssdService *service) { +DnssdRegisteredService *dnssd_registered_service_free(DnssdRegisteredService *service) { if (!service) return NULL; if (service->manager) - hashmap_remove(service->manager->dnssd_services, service->id); + hashmap_remove(service->manager->dnssd_registered_services, service->id); dns_resource_record_unref(service->ptr_rr); dns_resource_record_unref(service->sub_ptr_rr); @@ -68,13 +68,13 @@ DnssdService *dnssd_service_free(DnssdService *service) { return mfree(service); } -void dnssd_service_clear_on_reload(Hashmap *services) { - DnssdService *service; +void dnssd_registered_service_clear_on_reload(Hashmap *services) { + DnssdRegisteredService *service; HASHMAP_FOREACH(service, services) if (service->config_source == RESOLVE_CONFIG_SOURCE_FILE) { hashmap_remove(services, service->id); - dnssd_service_free(service); + dnssd_registered_service_free(service); } } @@ -99,8 +99,8 @@ static int dnssd_id_from_path(const char *path, char **ret_id) { return 0; } -static int dnssd_service_load(Manager *manager, const char *path) { - _cleanup_(dnssd_service_freep) DnssdService *service = NULL; +static int dnssd_registered_service_load(Manager *manager, const char *path) { + _cleanup_(dnssd_registered_service_freep) DnssdRegisteredService *service = NULL; _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL; _cleanup_free_ char *dropin_dirname = NULL; int r; @@ -108,7 +108,7 @@ static int dnssd_service_load(Manager *manager, const char *path) { assert(manager); assert(path); - service = new0(DnssdService, 1); + service = new0(DnssdRegisteredService, 1); if (!service) return log_oom(); @@ -158,7 +158,7 @@ static int dnssd_service_load(Manager *manager, const char *path) { TAKE_PTR(txt_data); } - r = hashmap_ensure_put(&manager->dnssd_services, &string_hash_ops, service->id, service); + r = hashmap_ensure_put(&manager->dnssd_registered_services, &string_hash_ops, service->id, service); if (r < 0) return r; @@ -181,7 +181,7 @@ static int specifier_dnssd_hostname(char specifier, const void *data, const char return strdup_to(ret, m->llmnr_hostname); } -int dnssd_render_instance_name(Manager *m, DnssdService *s, char **ret) { +int dnssd_render_instance_name(Manager *m, DnssdRegisteredService *s, char **ret) { static const Specifier specifier_table[] = { { 'a', specifier_architecture, NULL }, { 'b', specifier_boot_id, NULL }, @@ -230,7 +230,7 @@ int dnssd_load(Manager *manager) { return log_error_errno(r, "Failed to enumerate .dnssd files: %m"); STRV_FOREACH_BACKWARDS(f, files) { - r = dnssd_service_load(manager, *f); + r = dnssd_registered_service_load(manager, *f); if (r < 0) log_warning_errno(r, "Failed to load '%s': %m", *f); } @@ -238,7 +238,7 @@ int dnssd_load(Manager *manager) { return 0; } -int dnssd_update_rrs(DnssdService *s) { +int dnssd_update_rrs(DnssdRegisteredService *s) { _cleanup_free_ char *n = NULL, *service_name = NULL, *full_name = NULL, *sub_name = NULL, *selective_name = NULL; int r; @@ -379,13 +379,13 @@ int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t } int dnssd_signal_conflict(Manager *manager, const char *name) { - DnssdService *s; + DnssdRegisteredService *s; int r; if (sd_bus_is_ready(manager->bus) <= 0) return 0; - HASHMAP_FOREACH(s, manager->dnssd_services) { + HASHMAP_FOREACH(s, manager->dnssd_registered_services) { if (s->withdrawn) continue; @@ -413,7 +413,7 @@ int dnssd_signal_conflict(Manager *manager, const char *name) { return 0; } -int config_parse_dnssd_service_name( +int config_parse_dnssd_registered_service_name( const char *unit, const char *filename, unsigned line, @@ -437,7 +437,7 @@ int config_parse_dnssd_service_name( { 'W', specifier_os_variant_id, NULL }, {} }; - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); _cleanup_free_ char *name = NULL; int r; @@ -467,7 +467,7 @@ int config_parse_dnssd_service_name( return free_and_strdup_warn(&s->name_template, rvalue); } -int config_parse_dnssd_service_type( +int config_parse_dnssd_registered_service_type( const char *unit, const char *filename, unsigned line, @@ -479,7 +479,7 @@ int config_parse_dnssd_service_type( void *data, void *userdata) { - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); int r; assert(filename); @@ -503,7 +503,7 @@ int config_parse_dnssd_service_type( return 0; } -int config_parse_dnssd_service_subtype( +int config_parse_dnssd_registered_service_subtype( const char *unit, const char *filename, unsigned line, @@ -515,7 +515,7 @@ int config_parse_dnssd_service_subtype( void *data, void *userdata) { - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); assert(filename); assert(lvalue); @@ -547,7 +547,7 @@ int config_parse_dnssd_txt( void *userdata) { _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL; - DnssdService *s = ASSERT_PTR(userdata); + DnssdRegisteredService *s = ASSERT_PTR(userdata); DnsTxtItem *last = NULL; assert(filename); diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h index 98d6a54a6c..d2c2ccd133 100644 --- a/src/resolve/resolved-dnssd.h +++ b/src/resolve/resolved-dnssd.h @@ -19,7 +19,7 @@ typedef struct DnssdTxtData { LIST_FIELDS(DnssdTxtData, items); } DnssdTxtData; -typedef struct DnssdService { +typedef struct DnssdRegisteredService { char *path; char *id; char *name_template; @@ -44,26 +44,26 @@ typedef struct DnssdService { bool withdrawn:1; uid_t originator; -} DnssdService; +} DnssdRegisteredService; -DnssdService *dnssd_service_free(DnssdService *service); +DnssdRegisteredService *dnssd_registered_service_free(DnssdRegisteredService *service); DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data); DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data); -void dnssd_service_clear_on_reload(Hashmap *services); +void dnssd_registered_service_clear_on_reload(Hashmap *services); -DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdService*, dnssd_service_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdRegisteredService*, dnssd_registered_service_free); DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdTxtData*, dnssd_txtdata_free); -int dnssd_render_instance_name(Manager *m, DnssdService *s, char **ret); +int dnssd_render_instance_name(Manager *m, DnssdRegisteredService *s, char **ret); int dnssd_load(Manager *manager); int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtItem **ret_item); int dnssd_txt_item_new_from_data(const char *key, const void *value, const size_t size, DnsTxtItem **ret_item); -int dnssd_update_rrs(DnssdService *s); +int dnssd_update_rrs(DnssdRegisteredService *s); int dnssd_signal_conflict(Manager *manager, const char *name); const struct ConfigPerfItem* resolved_dnssd_gperf_lookup(const char *key, GPERF_LEN_TYPE length); -CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_name); -CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_subtype); -CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_type); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_registered_service_name); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_registered_service_subtype); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_registered_service_type); CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_txt); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 94b7e3df16..89857f5002 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -201,13 +201,13 @@ void link_add_rrs(Link *l, bool force_remove) { link_get_mdns_support(l) == RESOLVE_SUPPORT_YES) { if (l->mdns_ipv4_scope) { - r = dns_scope_add_dnssd_services(l->mdns_ipv4_scope); + r = dns_scope_add_dnssd_registered_services(l->mdns_ipv4_scope); if (r < 0) log_link_warning_errno(l, r, "Failed to add IPv4 DNS-SD services, ignoring: %m"); } if (l->mdns_ipv6_scope) { - r = dns_scope_add_dnssd_services(l->mdns_ipv6_scope); + r = dns_scope_add_dnssd_registered_services(l->mdns_ipv6_scope); if (r < 0) log_link_warning_errno(l, r, "Failed to add IPv6 DNS-SD services, ignoring: %m"); } @@ -215,13 +215,13 @@ void link_add_rrs(Link *l, bool force_remove) { } else { if (l->mdns_ipv4_scope) { - r = dns_scope_remove_dnssd_services(l->mdns_ipv4_scope); + r = dns_scope_remove_dnssd_registered_services(l->mdns_ipv4_scope); if (r < 0) log_link_warning_errno(l, r, "Failed to remove IPv4 DNS-SD services, ignoring: %m"); } if (l->mdns_ipv6_scope) { - r = dns_scope_remove_dnssd_services(l->mdns_ipv6_scope); + r = dns_scope_remove_dnssd_registered_services(l->mdns_ipv6_scope); if (r < 0) log_link_warning_errno(l, r, "Failed to remove IPv6 DNS-SD services, ignoring: %m"); } diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 335698c9b1..1509c5b630 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -653,7 +653,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa dns_server_unlink_on_reload(m->fallback_dns_servers); m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners); manager_dns_stub_stop(m); - dnssd_service_clear_on_reload(m->dnssd_services); + dnssd_registered_service_clear_on_reload(m->dnssd_registered_services); m->unicast_scope = dns_scope_free(m->unicast_scope); m->delegates = hashmap_free(m->delegates); dns_trust_anchor_flush(&m->trust_anchor); @@ -828,6 +828,7 @@ int manager_start(Manager *m) { Manager* manager_free(Manager *m) { Link *l; + DnssdRegisteredService *s; if (!m) return NULL; @@ -898,10 +899,9 @@ Manager* manager_free(Manager *m) { free(m->llmnr_hostname); free(m->mdns_hostname); - DnssdService *s; - while ((s = hashmap_first(m->dnssd_services))) - dnssd_service_free(s); - hashmap_free(m->dnssd_services); + while ((s = hashmap_first(m->dnssd_registered_services))) + dnssd_registered_service_free(s); + hashmap_free(m->dnssd_registered_services); dns_trust_anchor_flush(&m->trust_anchor); manager_etc_hosts_flush(m); @@ -1411,7 +1411,7 @@ int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_a void manager_refresh_rrs(Manager *m) { Link *l; - DnssdService *s; + DnssdRegisteredService *s; assert(m); @@ -1424,7 +1424,7 @@ void manager_refresh_rrs(Manager *m) { link_add_rrs(l, true); if (m->mdns_support == RESOLVE_SUPPORT_YES) - HASHMAP_FOREACH(s, m->dnssd_services) + HASHMAP_FOREACH(s, m->dnssd_registered_services) if (dnssd_update_rrs(s) < 0) log_warning("Failed to refresh DNS-SD service '%s'", s->id); @@ -1862,13 +1862,13 @@ void manager_cleanup_saved_user(Manager *m) { } bool manager_next_dnssd_names(Manager *m) { - DnssdService *s; + DnssdRegisteredService *s; bool tried = false; int r; assert(m); - HASHMAP_FOREACH(s, m->dnssd_services) { + HASHMAP_FOREACH(s, m->dnssd_registered_services) { _cleanup_free_ char * new_name = NULL; if (!s->withdrawn) diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index cbe635ff94..2c0a550c93 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -91,7 +91,7 @@ typedef struct Manager { sd_event_source *mdns_ipv6_event_source; /* DNS-SD */ - Hashmap *dnssd_services; + Hashmap *dnssd_registered_services; /* dbus */ sd_bus *bus; From f7d5177dd2d71c2bf1e12d6f7bb6313dcb6694fc Mon Sep 17 00:00:00 2001 From: Vishal Chillara Srinivas Date: Sun, 27 Apr 2025 22:09:35 +0530 Subject: [PATCH 2/5] reslove: Refactor DNS Answer to support 'until' in DnsAnswerItem Prepare for mDNS browse services to facilitate scheduled cache maintenance queries when browsing for services. Introduce dns_answer_add_full() and dns_answer_add_extend_full() to explicitly handle the 'until' argument, enabling fine-grained control over the time property of DNS answers. Ensure backward compatibility while providing the flexibility to specify 'until' for specific cases when needed Co-authored-by: Vishwanath Chandapur --- src/resolve/resolved-dns-answer.c | 21 +++++++++++++-------- src/resolve/resolved-dns-answer.h | 15 +++++++++++++-- src/resolve/resolved-dns-cache.c | 2 +- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 144b8c3b67..37e2ba1100 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -109,7 +109,8 @@ static int dns_answer_add_raw( DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, - DnsResourceRecord *rrsig) { + DnsResourceRecord *rrsig, + usec_t until) { _cleanup_(dns_answer_item_unrefp) DnsAnswerItem *item = NULL; int r; @@ -131,6 +132,7 @@ static int dns_answer_add_raw( .rr = dns_resource_record_ref(rr), .ifindex = ifindex, .flags = flags, + .until = until, .rrsig = dns_resource_record_ref(rrsig), }; @@ -152,7 +154,8 @@ static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { item->rr, item->ifindex, item->flags, - item->rrsig); + item->rrsig, + /* until= */ USEC_INFINITY); if (r < 0) return r; } @@ -160,12 +163,13 @@ static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { return 0; } -int dns_answer_add( +int dns_answer_add_full( DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, - DnsResourceRecord *rrsig) { + DnsResourceRecord *rrsig, + usec_t until) { DnsAnswerItem tmp, *exist; @@ -220,7 +224,7 @@ int dns_answer_add( return 0; } - return dns_answer_add_raw(a, rr, ifindex, flags, rrsig); + return dns_answer_add_raw(a, rr, ifindex, flags, rrsig, until); } static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { @@ -236,12 +240,13 @@ static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { return 0; } -int dns_answer_add_extend( +int dns_answer_add_extend_full( DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, - DnsResourceRecord *rrsig) { + DnsResourceRecord *rrsig, + usec_t until) { int r; @@ -252,7 +257,7 @@ int dns_answer_add_extend( if (r < 0) return r; - return dns_answer_add(*a, rr, ifindex, flags, rrsig); + return dns_answer_add_full(*a, rr, ifindex, flags, rrsig, until); } int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex) { diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index e4c1d2cb2a..5f4a54c556 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -31,6 +31,7 @@ typedef struct DnsAnswerItem { DnsResourceRecord *rrsig; /* Optionally, also store RRSIG RR that successfully validates this item */ int ifindex; DnsAnswerFlags flags; + usec_t until; } DnsAnswerItem; typedef struct DnsAnswer { @@ -50,8 +51,8 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a); *_a = _b; \ } while(0) -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig); -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig); +int dns_answer_add_full(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig, usec_t until); +int dns_answer_add_extend_full(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig, usec_t until); int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex); int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags); @@ -79,6 +80,16 @@ int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKe int dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); +static inline int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, + int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig) { + return dns_answer_add_extend_full(a, rr, ifindex, flags, rrsig, /* until= */ USEC_INFINITY); +} + +static inline int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, + int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig) { + return dns_answer_add_full(a, rr, ifindex, flags, rrsig, /* until= */ USEC_INFINITY); +} + static inline size_t dns_answer_size(DnsAnswer *a) { return a ? ordered_set_size(a->items) : 0; } diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 9ae3e78286..5429cefdcd 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -1022,7 +1022,7 @@ static int answer_add_clamp_ttl( } } - r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig); + r = dns_answer_add_extend_full(answer, rr, ifindex, answer_flags, rrsig, until); if (r < 0) return r; From 6ccc2b24f5f813d40da380d0e61a078b5db22f5d Mon Sep 17 00:00:00 2001 From: Vishal Chillara Srinivas Date: Mon, 7 Apr 2025 14:53:57 +0530 Subject: [PATCH 3/5] resolve: Refactor mDNS transaction max attempts Prepare for upcoming mDNS browse services. Prevent continuous queries from being attempted thrice. Co-authored-by: Vishwanath Chandapur --- src/resolve/resolved-def.h | 4 +++ src/resolve/resolved-dns-transaction.c | 2 +- src/resolve/resolved-timeouts.h | 35 +++++++++++++++++++++----- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h index de06c8ad28..d2e3d0fea6 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -76,6 +76,10 @@ #define SD_RESOLVED_RELAX_SINGLE_LABEL \ (UINT64_C(1) << 25) +/* Input: To differentiate between a probe and a continuous query. */ +#define SD_RESOLVED_QUERY_CONTINUOUS \ + (UINT64_C(1) << 26) + #define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) #define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) #define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index d6dbba546c..2e183d15fe 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1708,7 +1708,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { return 0; } - if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { + if (t->n_attempts >= dns_transaction_attempts_max(t->scope->protocol, t->query_flags)) { DnsTransactionState result; if (t->scope->protocol == DNS_PROTOCOL_LLMNR) diff --git a/src/resolve/resolved-timeouts.h b/src/resolve/resolved-timeouts.h index 717bccca4a..4028cf2919 100644 --- a/src/resolve/resolved-timeouts.h +++ b/src/resolve/resolved-timeouts.h @@ -19,12 +19,35 @@ /* Maximum attempts to send MDNS requests, see RFC 6762 Section 8.1 */ #define MDNS_TRANSACTION_ATTEMPTS_MAX 3 -#define TRANSACTION_ATTEMPTS_MAX(p) (\ - (p) == DNS_PROTOCOL_LLMNR ? \ - LLMNR_TRANSACTION_ATTEMPTS_MAX : \ - (p) == DNS_PROTOCOL_MDNS ? \ - MDNS_TRANSACTION_ATTEMPTS_MAX : \ - DNS_TRANSACTION_ATTEMPTS_MAX) +/* Maximum attempts to send an mDNS continuous query. + * + * RFC 6762 Section 5.2 does not specify a maximum number of attempts directly. + * However, it outlines two important guidelines: + * + * 1. The interval between the first two queries MUST be at least one second. + * 2. The intervals between successive queries MUST increase by at least a factor of two. + * + * To adhere to these timing requirements for continuous queries, + * the maximum number of attempts should be set to 1. + */ +#define MDNS_TRANSACTION_CONTINUOUS_QUERY_MAX 1U + +static inline unsigned dns_transaction_attempts_max(DnsProtocol p, uint64_t query_flags) { + switch (p) { + + case DNS_PROTOCOL_LLMNR: + return LLMNR_TRANSACTION_ATTEMPTS_MAX; + + case DNS_PROTOCOL_MDNS: + if (FLAGS_SET(query_flags, SD_RESOLVED_QUERY_CONTINUOUS)) + return MDNS_TRANSACTION_CONTINUOUS_QUERY_MAX; + else + return MDNS_TRANSACTION_ATTEMPTS_MAX; + + default: + return DNS_TRANSACTION_ATTEMPTS_MAX; + } +} /* After how much time to repeat classic DNS requests */ #define TRANSACTION_UDP_TIMEOUT_USEC (SD_RESOLVED_QUERY_TIMEOUT_USEC / DNS_TRANSACTION_ATTEMPTS_MAX) From 8458b7fb91ea5e5109b6f3c94f8a781a120c798b Mon Sep 17 00:00:00 2001 From: Vishal Chillara Srinivas Date: Tue, 15 Jul 2025 22:55:30 +0530 Subject: [PATCH 4/5] resolve: Implement continuous mDNS querying as per RFC6762 5.2 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 --- src/resolve/meson.build | 1 + src/resolve/resolved-dns-browse-services.c | 757 +++++++++++++++++++++ src/resolve/resolved-dns-browse-services.h | 82 +++ src/resolve/resolved-dns-query.c | 4 + src/resolve/resolved-dns-query.h | 5 + src/resolve/resolved-dns-question.c | 47 ++ src/resolve/resolved-dns-question.h | 1 + src/resolve/resolved-dns-scope.c | 4 + src/resolve/resolved-link.c | 3 + src/resolve/resolved-manager.c | 23 +- src/resolve/resolved-manager.h | 12 +- src/resolve/resolved-mdns.c | 58 +- src/resolve/resolved-varlink.c | 54 +- src/shared/varlink-io.systemd.Resolve.c | 41 ++ 14 files changed, 1057 insertions(+), 35 deletions(-) create mode 100644 src/resolve/resolved-dns-browse-services.c create mode 100644 src/resolve/resolved-dns-browse-services.h 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, From 7eb276dced3743f4c5a48c9c29f8f373294adb2f Mon Sep 17 00:00:00 2001 From: Vishal Chillara Srinivas Date: Thu, 17 Jul 2025 19:41:21 +0530 Subject: [PATCH 5/5] test: resolve: add integration tests for browsing services Co-authored-by: Frantisek Sumsal Co-authored-by: Vishwanath Chandapur --- .../TEST-89-RESOLVED-MDNS/meson.build | 7 + test/integration-tests/meson.build | 1 + test/units/TEST-89-RESOLVED-MDNS.service | 8 + test/units/TEST-89-RESOLVED-MDNS.sh | 254 ++++++++++++++++++ 4 files changed, 270 insertions(+) create mode 100644 test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build create mode 100644 test/units/TEST-89-RESOLVED-MDNS.service create mode 100755 test/units/TEST-89-RESOLVED-MDNS.sh diff --git a/test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build b/test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build new file mode 100644 index 0000000000..9cca5c79a3 --- /dev/null +++ b/test/integration-tests/TEST-89-RESOLVED-MDNS/meson.build @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : 'TEST-89-RESOLVED-MDNS', + }, +] diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 5fb92b172a..19cd5b5310 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -101,6 +101,7 @@ foreach dirname : [ 'TEST-86-MULTI-PROFILE-UKI', 'TEST-87-AUX-UTILS-VM', 'TEST-88-UPGRADE', + 'TEST-89-RESOLVED-MDNS', ] subdir(dirname) endforeach diff --git a/test/units/TEST-89-RESOLVED-MDNS.service b/test/units/TEST-89-RESOLVED-MDNS.service new file mode 100644 index 0000000000..e7ed92c99a --- /dev/null +++ b/test/units/TEST-89-RESOLVED-MDNS.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-89-RESOLVED-MDNS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/TEST-89-RESOLVED-MDNS.sh b/test/units/TEST-89-RESOLVED-MDNS.sh new file mode 100755 index 0000000000..b6e200582f --- /dev/null +++ b/test/units/TEST-89-RESOLVED-MDNS.sh @@ -0,0 +1,254 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +SERVICE_TYPE_COUNT=10 +SERVICE_COUNT=20 +CONTAINER_ZONE="test-$RANDOM" +CONTAINER_1="test-mdns-1" +CONTAINER_2="test-mdns-2" + +# Prepare containers +create_container() { + local container="${1:?}" + local stype sid svc + + # Prepare container's /etc + # + # Since we also need the various test suite related dropins from the host's /etc, + # we'll overlay our customizations on top of that + mkdir -p "/var/lib/machines/$container/etc/systemd/dnssd" + # Create 20 test services for each service type (_testServiceX._udp) and number them sequentially, + # i.e. create services 0-19 for _testService0._udp, services 20-39 for _testService1._udp, and so on + for stype in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + for sid in $(seq 0 $((SERVICE_COUNT - 1))); do + svc=$((stype * SERVICE_COUNT + sid)) + + cat >"/var/lib/machines/$container/etc/systemd/dnssd/test-service-$container-$svc.dnssd" <"/run/systemd/system/systemd-nspawn@$container.service.d/override.conf" <&2 "Found a record from an unreachable container" + cat "$result_file" + exit 1 + fi + done + + # We got all records and all of them are what we expect + return 0 + fi + + return 1 +} + +run_and_check_services() { + local service_id="${1:?}" + local check_func="${2:?}" + local unit_name="varlinkctl-$service_id-$SRANDOM.service" + local i out_file parameters service_type svc tmp_file + + out_file="$(mktemp)" + error_file="$(mktemp)" + tmp_file="$(mktemp)" + service_type="_testService$service_id._udp" + parameters="{ \"domain\": \"$service_type.local\", \"type\": \"\", \"ifindex\": ${BRIDGE_INDEX:?}, \"flags\": 16785432 }" + + systemd-run --unit="$unit_name" --service-type=exec -p StandardOutput="file:$out_file" -p StandardError="file:$error_file" \ + varlinkctl call --more /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.BrowseServices "$parameters" + + # shellcheck disable=SC2064 + # Note: unregister the trap once it's fired, otherwise it'll get propagated to functions that call this + # one, *sigh* + + trap "trap - RETURN; systemctl stop $unit_name" RETURN + + for _ in {0..14}; do + # The response format, for reference (it's JSON-SEQ): + # + # { + # "browser_service_data": [ + # { + # "updateFlag": true, + # "family": 10, + # "name": "Test Service 13 on test-mdns-1", + # "type": "_testService0._udp", + # "domain": "local", + # "interface": 3 + # }, + # ... + # ] + # } + if [[ -s "$out_file" ]]; then + # Extract the service name from each valid record... + # jq --slurp --raw-output \ + # ".[].browser_service_data[] | select(.updateFlag == true and .type == \"$service_type\" and .family == 10).name" "$out_file" | sort | tee "$tmp_file" + grep -o '"name":"[^"]*"' "$out_file" | sed 's/"name":"//;s/"//g' | sort | tee "$tmp_file" + # ...and compare them with what we expect + if "$check_func" "$service_id" "$tmp_file"; then + return 0 + fi + fi + + sleep 2 + done + + cat "$out_file" + cat "$error_file" + return 1 +} + +testcase_all_sequential() { + : "Test each service type (sequentially)" + resolvectl flush-caches + for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + run_and_check_services "$id" check_both + done + + echo testcase_end +} + +testcase_all_parallel() { + : "Test each service type (in parallel)" + resolvectl flush-caches + for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + run_and_check_services "$id" check_both & + done + wait +} + +testcase_single_service_multiple_times() { + : "Test one service type multiple times" + resolvectl flush-caches + for _ in {0..4}; do + run_and_check_services 4 check_both + done +} + +testcase_second_unreachable() { + : "Test each service type while the second container is unreachable" + systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl down host0 + resolvectl flush-caches + for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + run_and_check_services "$id" check_first + done + + + : "Test each service type after bringing the second container back up again" + systemd-run -M "$CONTAINER_2" --wait --pipe -- networkctl up host0 + systemd-run -M "$CONTAINER_2" --wait --pipe -- \ + /usr/lib/systemd/systemd-networkd-wait-online --ipv4 --ipv6 --interface=host0 --operational-state=degraded --timeout=30 + for id in $(seq 0 $((SERVICE_TYPE_COUNT - 1))); do + run_and_check_services "$id" check_both + done +} + +: "Setup host & containers" +# Note: create the drop-in intentionally under /run/ and copy it manually into the containers +mkdir -p /run/systemd/resolved.conf.d/ +cat >/run/systemd/resolved.conf.d/99-mdns-llmnr.conf <