From 994b35086026d5f385f48053bea698ca3ae878b2 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:33 -0400 Subject: [PATCH 01/13] resolve: add {global,link}_dns_configuration_json_append() helpers No functional change, just add these helpers to improve readability in dns_configuration_json_append(). This is preparation for later commits. --- src/resolve/resolved-manager.c | 46 ++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 7e6c074c11..ba9402e926 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2112,6 +2112,34 @@ static int dns_configuration_json_append( JSON_BUILD_PAIR_VARIANT_NON_NULL("searchDomains", search_domains_json)); } +static int global_dns_configuration_json_append(Manager *m, sd_json_variant **configuration) { + assert(m); + assert(configuration); + + return dns_configuration_json_append( + /* ifname = */ NULL, + /* ifindex = */ 0, + /* default_route = */ 0, + manager_get_dns_server(m), + m->dns_servers, + m->search_domains, + configuration); +} + +static int link_dns_configuration_json_append(Link *l, sd_json_variant **configuration) { + assert(l); + assert(configuration); + + return dns_configuration_json_append( + l->ifname, + l->ifindex, + link_get_default_route(l), + link_get_dns_server(l), + l->dns_servers, + l->search_domains, + configuration); +} + int manager_dump_dns_configuration_json(Manager *m, sd_json_variant **ret) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL; Link *l; @@ -2121,27 +2149,13 @@ int manager_dump_dns_configuration_json(Manager *m, sd_json_variant **ret) { assert(ret); /* Global DNS configuration */ - r = dns_configuration_json_append( - /* ifname = */ NULL, - /* ifindex = */ 0, - /* default_route = */ 0, - manager_get_dns_server(m), - m->dns_servers, - m->search_domains, - &configuration); + r = global_dns_configuration_json_append(m, &configuration); if (r < 0) return r; /* Append configuration for each link */ HASHMAP_FOREACH(l, m->links) { - r = dns_configuration_json_append( - l->ifname, - l->ifindex, - link_get_default_route(l), - link_get_dns_server(l), - l->dns_servers, - l->search_domains, - &configuration); + r = link_dns_configuration_json_append(l, &configuration); if (r < 0) return r; } From 34be1fadded12833addb512efecd14fbd448c7ae Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:33 -0400 Subject: [PATCH 02/13] resolve: add delegate info to DNSConfiguration This is one of several commits to expand the DNSConfiguration varlink type to include the necessary information for resolvectl status output. --- src/resolve/resolved-manager.c | 31 ++++++++++++++++++- .../varlink-io.systemd.Resolve.Monitor.c | 2 ++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index ba9402e926..d88102ac83 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2043,6 +2043,7 @@ void dns_manager_reset_statistics(Manager *m) { static int dns_configuration_json_append( const char *ifname, int ifindex, + const char *delegate, int default_route, DnsServer *current_dns_server, DnsServer *dns_servers, @@ -2106,7 +2107,10 @@ static int dns_configuration_json_append( configuration, JSON_BUILD_PAIR_STRING_NON_EMPTY("ifname", ifname), SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", SD_JSON_BUILD_UNSIGNED(ifindex)), - SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "defaultRoute", SD_JSON_BUILD_BOOLEAN(default_route > 0)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("delegate", delegate), + JSON_BUILD_PAIR_CONDITION_BOOLEAN(ifindex > 0 || !!delegate, + "defaultRoute", + default_route > 0), JSON_BUILD_PAIR_VARIANT_NON_NULL("currentServer", current_dns_server_json), JSON_BUILD_PAIR_VARIANT_NON_NULL("servers", dns_servers_json), JSON_BUILD_PAIR_VARIANT_NON_NULL("searchDomains", search_domains_json)); @@ -2119,6 +2123,7 @@ static int global_dns_configuration_json_append(Manager *m, sd_json_variant **co return dns_configuration_json_append( /* ifname = */ NULL, /* ifindex = */ 0, + /* delegate = */ NULL, /* default_route = */ 0, manager_get_dns_server(m), m->dns_servers, @@ -2133,6 +2138,7 @@ static int link_dns_configuration_json_append(Link *l, sd_json_variant **configu return dns_configuration_json_append( l->ifname, l->ifindex, + /* delegate = */ NULL, link_get_default_route(l), link_get_dns_server(l), l->dns_servers, @@ -2140,9 +2146,25 @@ static int link_dns_configuration_json_append(Link *l, sd_json_variant **configu configuration); } +static int delegate_dns_configuration_json_append(DnsDelegate *d, sd_json_variant **configuration) { + assert(d); + assert(configuration); + + return dns_configuration_json_append( + /* ifname = */ NULL, + /* ifindex = */ 0, + d->id, + d->default_route, + dns_delegate_get_dns_server(d), + d->dns_servers, + d->search_domains, + configuration); +} + int manager_dump_dns_configuration_json(Manager *m, sd_json_variant **ret) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL; Link *l; + DnsDelegate *d; int r; assert(m); @@ -2160,6 +2182,13 @@ int manager_dump_dns_configuration_json(Manager *m, sd_json_variant **ret) { return r; } + /* Append configuration for each delegate */ + HASHMAP_FOREACH(d, m->delegates) { + r = delegate_dns_configuration_json_append(d, &configuration); + if (r < 0) + return r; + } + return sd_json_buildo(ret, SD_JSON_BUILD_PAIR_VARIANT("configuration", configuration)); } diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index 2861368a45..d78ea7bb97 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -144,6 +144,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ifname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Interface index, if any, associated with this configuration. Empty for global configuration."), SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Delegate name, if any, associated with this configuration. Empty for global or link configurations."), + SD_VARLINK_DEFINE_FIELD(delegate, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Indicates whether or not this link's DNS servers will be used for resolving domain names that do not match any link's configured domains."), SD_VARLINK_DEFINE_FIELD(defaultRoute, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("DNS server currently selected to use for lookups."), From a80f9291cc5b7203b3d7dfbfd42423542382b442 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:33 -0400 Subject: [PATCH 03/13] resolve: add negative trust anchors to DNSConfiguration This is one of several commits to expand the DNSConfiguration varlink type to include the necessary information for resolvectl status output. --- src/resolve/resolved-manager.c | 9 ++++++++- src/shared/varlink-io.systemd.Resolve.Monitor.c | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index d88102ac83..f6147b595c 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2048,6 +2048,7 @@ static int dns_configuration_json_append( DnsServer *current_dns_server, DnsServer *dns_servers, DnsSearchDomain *search_domains, + Set *negative_trust_anchors, sd_json_variant **configuration) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *dns_servers_json = NULL, @@ -2113,7 +2114,10 @@ static int dns_configuration_json_append( default_route > 0), JSON_BUILD_PAIR_VARIANT_NON_NULL("currentServer", current_dns_server_json), JSON_BUILD_PAIR_VARIANT_NON_NULL("servers", dns_servers_json), - JSON_BUILD_PAIR_VARIANT_NON_NULL("searchDomains", search_domains_json)); + JSON_BUILD_PAIR_VARIANT_NON_NULL("searchDomains", search_domains_json), + SD_JSON_BUILD_PAIR_CONDITION(!set_isempty(negative_trust_anchors), + "negativeTrustAnchors", + JSON_BUILD_STRING_SET(negative_trust_anchors))); } static int global_dns_configuration_json_append(Manager *m, sd_json_variant **configuration) { @@ -2128,6 +2132,7 @@ static int global_dns_configuration_json_append(Manager *m, sd_json_variant **co manager_get_dns_server(m), m->dns_servers, m->search_domains, + m->trust_anchor.negative_by_name, configuration); } @@ -2143,6 +2148,7 @@ static int link_dns_configuration_json_append(Link *l, sd_json_variant **configu link_get_dns_server(l), l->dns_servers, l->search_domains, + l->dnssec_negative_trust_anchors, configuration); } @@ -2158,6 +2164,7 @@ static int delegate_dns_configuration_json_append(DnsDelegate *d, sd_json_varian dns_delegate_get_dns_server(d), d->dns_servers, d->search_domains, + /* negative_trust_anchors = */ NULL, configuration); } diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index d78ea7bb97..bbca42a9e7 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -153,7 +153,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Array of configured DNS servers."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(servers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Array of configured search domains."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Array of configured DNSSEC negative trust anchors."), + SD_VARLINK_DEFINE_FIELD(negativeTrustAnchors, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( SubscribeDNSConfiguration, From 306375c36804c5c85cd9b77b353f40edf116521d Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:34 -0400 Subject: [PATCH 04/13] resolve: add DNS scope info to DNSConfiguration This is one of several commits to expand the DNSConfiguration varlink type to include the necessary information for resolvectl status output. --- src/resolve/resolved-manager.c | 75 ++++++++++++++++++- .../varlink-io.systemd.Resolve.Monitor.c | 21 +++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index f6147b595c..36c78eaf04 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2049,11 +2049,14 @@ static int dns_configuration_json_append( DnsServer *dns_servers, DnsSearchDomain *search_domains, Set *negative_trust_anchors, + Set *dns_scopes, sd_json_variant **configuration) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *dns_servers_json = NULL, *search_domains_json = NULL, - *current_dns_server_json = NULL; + *current_dns_server_json = NULL, + *scopes_json = NULL; + DnsScope *scope; int r; assert(configuration); @@ -2076,6 +2079,23 @@ static int dns_configuration_json_append( return r; } + SET_FOREACH(scope, dns_scopes) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + r = dns_scope_dump_cache_to_json(scope, &v); + if (r < 0) + return r; + + /* The cache is not relevant to the configuration of the scope. */ + r = sd_json_variant_filter(&v, STRV_MAKE("cache")); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&scopes_json, v); + if (r < 0) + return r; + } + LIST_FOREACH(servers, s, dns_servers) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; @@ -2117,13 +2137,21 @@ static int dns_configuration_json_append( JSON_BUILD_PAIR_VARIANT_NON_NULL("searchDomains", search_domains_json), SD_JSON_BUILD_PAIR_CONDITION(!set_isempty(negative_trust_anchors), "negativeTrustAnchors", - JSON_BUILD_STRING_SET(negative_trust_anchors))); + JSON_BUILD_STRING_SET(negative_trust_anchors)), + JSON_BUILD_PAIR_VARIANT_NON_NULL("scopes", scopes_json)); } static int global_dns_configuration_json_append(Manager *m, sd_json_variant **configuration) { + _cleanup_set_free_ Set *scopes = NULL; + int r; + assert(m); assert(configuration); + r = set_ensure_put(&scopes, NULL, m->unicast_scope); + if (r < 0) + return r; + return dns_configuration_json_append( /* ifname = */ NULL, /* ifindex = */ 0, @@ -2133,13 +2161,47 @@ static int global_dns_configuration_json_append(Manager *m, sd_json_variant **co m->dns_servers, m->search_domains, m->trust_anchor.negative_by_name, + scopes, configuration); } static int link_dns_configuration_json_append(Link *l, sd_json_variant **configuration) { + _cleanup_set_free_ Set *scopes = NULL; + int r; + assert(l); assert(configuration); + if (l->unicast_scope) { + r = set_ensure_put(&scopes, NULL, l->unicast_scope); + if (r < 0) + return r; + } + + if (l->llmnr_ipv4_scope) { + r = set_ensure_put(&scopes, NULL, l->llmnr_ipv4_scope); + if (r < 0) + return r; + } + + if (l->llmnr_ipv6_scope) { + r = set_ensure_put(&scopes, NULL, l->llmnr_ipv6_scope); + if (r < 0) + return r; + } + + if (l->mdns_ipv4_scope) { + r = set_ensure_put(&scopes, NULL, l->mdns_ipv4_scope); + if (r < 0) + return r; + } + + if (l->mdns_ipv6_scope) { + r = set_ensure_put(&scopes, NULL, l->mdns_ipv6_scope); + if (r < 0) + return r; + } + return dns_configuration_json_append( l->ifname, l->ifindex, @@ -2149,13 +2211,21 @@ static int link_dns_configuration_json_append(Link *l, sd_json_variant **configu l->dns_servers, l->search_domains, l->dnssec_negative_trust_anchors, + scopes, configuration); } static int delegate_dns_configuration_json_append(DnsDelegate *d, sd_json_variant **configuration) { + _cleanup_set_free_ Set *scopes = NULL; + int r; + assert(d); assert(configuration); + r = set_ensure_put(&scopes, NULL, d->scope); + if (r < 0) + return r; + return dns_configuration_json_append( /* ifname = */ NULL, /* ifindex = */ 0, @@ -2165,6 +2235,7 @@ static int delegate_dns_configuration_json_append(DnsDelegate *d, sd_json_varian d->dns_servers, d->search_domains, /* negative_trust_anchors = */ NULL, + scopes, configuration); } diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index bbca42a9e7..df76510cf6 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -138,6 +138,21 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Interface index for which this search domain is configured."), SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_STRUCT_TYPE( + DNSScope, + SD_VARLINK_FIELD_COMMENT("Protocol associated with this scope."), + SD_VARLINK_DEFINE_FIELD(protocol, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Address family associated with this scope."), + SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Interface index associated with this scope."), + SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Interface name associated with this scope."), + SD_VARLINK_DEFINE_FIELD(ifname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNSSEC mode associated with this scope."), + SD_VARLINK_DEFINE_FIELD(dnssec, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNSOverTLS mode associated with this scope."), + SD_VARLINK_DEFINE_FIELD(dnsOverTLS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( DNSConfiguration, SD_VARLINK_FIELD_COMMENT("Interface name, if any, associated with this configuration. Empty for global configuration."), @@ -155,7 +170,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Array of configured search domains."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Array of configured DNSSEC negative trust anchors."), - SD_VARLINK_DEFINE_FIELD(negativeTrustAnchors, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD(negativeTrustAnchors, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Array of current DNS scopes."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(scopes, DNSScope, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( SubscribeDNSConfiguration, @@ -188,5 +205,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_SearchDomain, SD_VARLINK_SYMBOL_COMMENT("Encapsulates a global or per-link DNS configuration, including configured DNS servers, search domains, and more."), &vl_type_DNSConfiguration, + SD_VARLINK_SYMBOL_COMMENT("Encapsulates a DNS scope specification."), + &vl_type_DNSScope, SD_VARLINK_SYMBOL_COMMENT("Sends the complete global and per-link DNS configurations when any changes are made to them. The current configurations are given immediately when this method is invoked."), &vl_method_SubscribeDNSConfiguration); From ed4d43f88fdf71db242fe22bdf6afaa25608a148 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:34 -0400 Subject: [PATCH 05/13] resolve: add all protocol modes to DNSConfiguration This is one of several commits to expand the DNSConfiguration varlink type to include the necessary information for resolvectl status output. --- src/resolve/resolved-manager.c | 20 +++++++++++++++++++ .../varlink-io.systemd.Resolve.Monitor.c | 8 ++++++++ 2 files changed, 28 insertions(+) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 36c78eaf04..b3af9ce4e3 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2050,6 +2050,10 @@ static int dns_configuration_json_append( DnsSearchDomain *search_domains, Set *negative_trust_anchors, Set *dns_scopes, + DnssecMode dnssec_mode, + DnsOverTlsMode dns_over_tls_mode, + ResolveSupport llmnr_support, + ResolveSupport mdns_support, sd_json_variant **configuration) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *dns_servers_json = NULL, @@ -2138,6 +2142,10 @@ static int dns_configuration_json_append( SD_JSON_BUILD_PAIR_CONDITION(!set_isempty(negative_trust_anchors), "negativeTrustAnchors", JSON_BUILD_STRING_SET(negative_trust_anchors)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("dnssec", dnssec_mode_to_string(dnssec_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("dnsOverTLS", dns_over_tls_mode_to_string(dns_over_tls_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("llmnr", resolve_support_to_string(llmnr_support)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("mDNS", resolve_support_to_string(mdns_support)), JSON_BUILD_PAIR_VARIANT_NON_NULL("scopes", scopes_json)); } @@ -2162,6 +2170,10 @@ static int global_dns_configuration_json_append(Manager *m, sd_json_variant **co m->search_domains, m->trust_anchor.negative_by_name, scopes, + manager_get_dnssec_mode(m), + manager_get_dns_over_tls_mode(m), + m->llmnr_support, + m->mdns_support, configuration); } @@ -2212,6 +2224,10 @@ static int link_dns_configuration_json_append(Link *l, sd_json_variant **configu l->search_domains, l->dnssec_negative_trust_anchors, scopes, + link_get_dnssec_mode(l), + link_get_dns_over_tls_mode(l), + link_get_llmnr_support(l), + link_get_mdns_support(l), configuration); } @@ -2236,6 +2252,10 @@ static int delegate_dns_configuration_json_append(DnsDelegate *d, sd_json_varian d->search_domains, /* negative_trust_anchors = */ NULL, scopes, + /* dnssec_mode = */ _DNSSEC_MODE_INVALID, + /* dns_over_tls_mode = */ _DNS_OVER_TLS_MODE_INVALID, + /* llmnr_support = */ _RESOLVE_SUPPORT_INVALID, + /* mdns_support = */ _RESOLVE_SUPPORT_INVALID, configuration); } diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index df76510cf6..622975e5de 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -171,6 +171,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Array of configured DNSSEC negative trust anchors."), SD_VARLINK_DEFINE_FIELD(negativeTrustAnchors, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNSSEC mode."), + SD_VARLINK_DEFINE_FIELD(dnssec, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNSOverTLS mode."), + SD_VARLINK_DEFINE_FIELD(dnsOverTLS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("LLMNR support."), + SD_VARLINK_DEFINE_FIELD(llmnr, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("mDNS support."), + SD_VARLINK_DEFINE_FIELD(mDNS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Array of current DNS scopes."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(scopes, DNSScope, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); From 9f9264e3f6db22c6951828bb3571f42aeb97aa8b Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:34 -0400 Subject: [PATCH 06/13] resolve: add resolv.conf mode to DNSConfiguration This is one of several commits to expand the DNSConfiguration varlink type to include the necessary information for resolvectl status output. --- src/resolve/resolved-manager.c | 5 +++++ src/shared/varlink-io.systemd.Resolve.Monitor.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index b3af9ce4e3..259886ff04 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2054,6 +2054,7 @@ static int dns_configuration_json_append( DnsOverTlsMode dns_over_tls_mode, ResolveSupport llmnr_support, ResolveSupport mdns_support, + ResolvConfMode resolv_conf_mode, sd_json_variant **configuration) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *dns_servers_json = NULL, @@ -2146,6 +2147,7 @@ static int dns_configuration_json_append( JSON_BUILD_PAIR_STRING_NON_EMPTY("dnsOverTLS", dns_over_tls_mode_to_string(dns_over_tls_mode)), JSON_BUILD_PAIR_STRING_NON_EMPTY("llmnr", resolve_support_to_string(llmnr_support)), JSON_BUILD_PAIR_STRING_NON_EMPTY("mDNS", resolve_support_to_string(mdns_support)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("resolvConfMode", resolv_conf_mode_to_string(resolv_conf_mode)), JSON_BUILD_PAIR_VARIANT_NON_NULL("scopes", scopes_json)); } @@ -2174,6 +2176,7 @@ static int global_dns_configuration_json_append(Manager *m, sd_json_variant **co manager_get_dns_over_tls_mode(m), m->llmnr_support, m->mdns_support, + resolv_conf_mode(), configuration); } @@ -2228,6 +2231,7 @@ static int link_dns_configuration_json_append(Link *l, sd_json_variant **configu link_get_dns_over_tls_mode(l), link_get_llmnr_support(l), link_get_mdns_support(l), + /* resolv_conf_mode = */ _RESOLV_CONF_MODE_INVALID, configuration); } @@ -2256,6 +2260,7 @@ static int delegate_dns_configuration_json_append(DnsDelegate *d, sd_json_varian /* dns_over_tls_mode = */ _DNS_OVER_TLS_MODE_INVALID, /* llmnr_support = */ _RESOLVE_SUPPORT_INVALID, /* mdns_support = */ _RESOLVE_SUPPORT_INVALID, + /* resolv_conf_mode = */ _RESOLV_CONF_MODE_INVALID, configuration); } diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index 622975e5de..389da45a5e 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -179,6 +179,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(llmnr, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("mDNS support."), SD_VARLINK_DEFINE_FIELD(mDNS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("resolv.conf mode, set for global configuration only."), + SD_VARLINK_DEFINE_FIELD(resolvConfMode, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Array of current DNS scopes."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(scopes, DNSScope, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); From f5d5ef9cb47c1d6795b172661a8e78382de117a1 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:35 -0400 Subject: [PATCH 07/13] resolve: add fallback servers list to DNSConfiguration This is one of several commits to expand the DNSConfiguration varlink type to include the necessary information for resolvectl status output. --- src/resolve/resolved-manager.c | 26 +++++++++++++++++++ .../varlink-io.systemd.Resolve.Monitor.c | 2 ++ 2 files changed, 28 insertions(+) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 259886ff04..29c5f4488e 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2047,6 +2047,7 @@ static int dns_configuration_json_append( int default_route, DnsServer *current_dns_server, DnsServer *dns_servers, + DnsServer *fallback_dns_servers, DnsSearchDomain *search_domains, Set *negative_trust_anchors, Set *dns_scopes, @@ -2058,6 +2059,7 @@ static int dns_configuration_json_append( sd_json_variant **configuration) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *dns_servers_json = NULL, + *fallback_dns_servers_json = NULL, *search_domains_json = NULL, *current_dns_server_json = NULL, *scopes_json = NULL; @@ -2084,6 +2086,12 @@ static int dns_configuration_json_append( return r; } + if (fallback_dns_servers) { + r = sd_json_variant_new_array(&fallback_dns_servers_json, NULL, 0); + if (r < 0) + return r; + } + SET_FOREACH(scope, dns_scopes) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; @@ -2129,6 +2137,20 @@ static int dns_configuration_json_append( return r; } + LIST_FOREACH(servers, s, fallback_dns_servers) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + assert(fallback_dns_servers_json); + + r = dns_server_dump_configuration_to_json(s, &v); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&fallback_dns_servers_json, v); + if (r < 0) + return r; + } + return sd_json_variant_append_arraybo( configuration, JSON_BUILD_PAIR_STRING_NON_EMPTY("ifname", ifname), @@ -2139,6 +2161,7 @@ static int dns_configuration_json_append( default_route > 0), JSON_BUILD_PAIR_VARIANT_NON_NULL("currentServer", current_dns_server_json), JSON_BUILD_PAIR_VARIANT_NON_NULL("servers", dns_servers_json), + JSON_BUILD_PAIR_VARIANT_NON_NULL("fallbackServers", fallback_dns_servers_json), JSON_BUILD_PAIR_VARIANT_NON_NULL("searchDomains", search_domains_json), SD_JSON_BUILD_PAIR_CONDITION(!set_isempty(negative_trust_anchors), "negativeTrustAnchors", @@ -2169,6 +2192,7 @@ static int global_dns_configuration_json_append(Manager *m, sd_json_variant **co /* default_route = */ 0, manager_get_dns_server(m), m->dns_servers, + m->fallback_dns_servers, m->search_domains, m->trust_anchor.negative_by_name, scopes, @@ -2224,6 +2248,7 @@ static int link_dns_configuration_json_append(Link *l, sd_json_variant **configu link_get_default_route(l), link_get_dns_server(l), l->dns_servers, + /* fallback_dns_servers = */ NULL, l->search_domains, l->dnssec_negative_trust_anchors, scopes, @@ -2253,6 +2278,7 @@ static int delegate_dns_configuration_json_append(DnsDelegate *d, sd_json_varian d->default_route, dns_delegate_get_dns_server(d), d->dns_servers, + /* fallback_dns_servers = */ NULL, d->search_domains, /* negative_trust_anchors = */ NULL, scopes, diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index 389da45a5e..a7ab279b5c 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -167,6 +167,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(currentServer, DNSServer, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Array of configured DNS servers."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(servers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Array of configured fallback DNS servers, set for global configuration only."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(fallbackServers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Array of configured search domains."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Array of configured DNSSEC negative trust anchors."), From 0d9e5b39d36e5954dc80e63bdbefbdfe59d7535e Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 16:06:47 -0400 Subject: [PATCH 08/13] resolve: add formatted address string to DNSServer Although the JSON output is mostly intended to be machine readable, humans also consume the output through logs and scripts. Add an addressString field to DNSServer to improve human-readability. --- src/resolve/resolved-dns-server.c | 1 + src/shared/varlink-io.systemd.Resolve.Monitor.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 787ffccc78..f8123bbb9f 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -1373,6 +1373,7 @@ int dns_server_dump_configuration_to_json(DnsServer *server, sd_json_variant **r return sd_json_buildo( ret, + JSON_BUILD_PAIR_STRING_NON_EMPTY("addressString", dns_server_string(server)), JSON_BUILD_PAIR_IN_ADDR("address", &server->address, server->family), SD_JSON_BUILD_PAIR_INTEGER("family", server->family), SD_JSON_BUILD_PAIR_UNSIGNED("port", dns_server_port(server)), diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index a7ab279b5c..bd514145fc 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -118,6 +118,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( DNSServer, SD_VARLINK_FIELD_COMMENT("IPv4 or IPv6 address of the server."), SD_VARLINK_DEFINE_FIELD(address, SD_VARLINK_INT, SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("IPv4 or IPv6 address of the server, formatted as a human-readable string."), + SD_VARLINK_DEFINE_FIELD(addressString, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Address family of the server, one of AF_INET or AF_INET6."), SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Port number of the server."), From 5e777155d5b6c7aa86fa13316eb034b8036ac623 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 17 Oct 2025 12:14:13 -0400 Subject: [PATCH 09/13] wait-online: dispatch DNSConfiguration with SD_JSON_ALLOW_EXTENSIONS Currently if an unknown field is encountered in the JSON, it is a fatal error. Dispatch with SD_JSON_ALLOW_EXTENSIONS to avoid this. --- src/network/wait-online/dns-configuration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/wait-online/dns-configuration.c b/src/network/wait-online/dns-configuration.c index 320f50a981..20bb71608f 100644 --- a/src/network/wait-online/dns-configuration.c +++ b/src/network/wait-online/dns-configuration.c @@ -198,7 +198,7 @@ static int dispatch_dns_configuration(const char *name, sd_json_variant *variant } int dns_configuration_from_json(sd_json_variant *variant, DNSConfiguration **ret) { - return dispatch_dns_configuration(NULL, variant, SD_JSON_LOG, ret); + return dispatch_dns_configuration(NULL, variant, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, ret); } bool dns_is_accessible(DNSConfiguration *c) { From d49a0bd1da8e7f5e53dd373148f813cfe0635cff Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 17 Oct 2025 12:12:18 -0400 Subject: [PATCH 10/13] wait-online: ignore unused DNSConfiguration fields when dispatching JSON The io.systemd.Resolve.Monitor.DNSConfiguration type is being expanded, but we do not need the extra information for determining online status. Ignore these fields when dispatching JSON to avoid "Unrecognized object field" messages adding noise to systemd-networkd-wait-online debug output. --- src/network/wait-online/dns-configuration.c | 36 ++++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/network/wait-online/dns-configuration.c b/src/network/wait-online/dns-configuration.c index 20bb71608f..9efd2d8f7b 100644 --- a/src/network/wait-online/dns-configuration.c +++ b/src/network/wait-online/dns-configuration.c @@ -32,12 +32,13 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( static int dispatch_dns_server(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { static const sd_json_dispatch_field dns_server_dispatch_table[] = { - { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(DNSServer, addr), SD_JSON_MANDATORY }, - { "family", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSServer, family), SD_JSON_MANDATORY }, - { "port", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint16, offsetof(DNSServer, port), 0 }, - { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSServer, ifindex), SD_JSON_RELAX }, - { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSServer, server_name), 0 }, - { "accessible", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DNSServer, accessible), SD_JSON_MANDATORY }, + { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(DNSServer, addr), SD_JSON_MANDATORY }, + { "addressString", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "family", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSServer, family), SD_JSON_MANDATORY }, + { "port", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint16, offsetof(DNSServer, port), 0 }, + { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSServer, ifindex), SD_JSON_RELAX }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSServer, server_name), 0 }, + { "accessible", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DNSServer, accessible), SD_JSON_MANDATORY }, {}, }; DNSServer **ret = ASSERT_PTR(userdata); @@ -171,12 +172,23 @@ DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( static int dispatch_dns_configuration(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { static const sd_json_dispatch_field dns_configuration_dispatch_table[] = { - { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSConfiguration, ifname), 0 }, - { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSConfiguration, ifindex), SD_JSON_RELAX }, - { "defaultRoute", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DNSConfiguration, default_route), 0 }, - { "currentServer", SD_JSON_VARIANT_OBJECT, dispatch_dns_server, offsetof(DNSConfiguration, current_dns_server), 0 }, - { "servers", SD_JSON_VARIANT_ARRAY, dispatch_dns_server_array, offsetof(DNSConfiguration, dns_servers), 0 }, - { "searchDomains", SD_JSON_VARIANT_ARRAY, dispatch_search_domain_array, offsetof(DNSConfiguration, search_domains), 0 }, + { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSConfiguration, ifname), 0 }, + { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSConfiguration, ifindex), SD_JSON_RELAX }, + { "defaultRoute", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DNSConfiguration, default_route), 0 }, + { "currentServer", SD_JSON_VARIANT_OBJECT, dispatch_dns_server, offsetof(DNSConfiguration, current_dns_server), 0 }, + { "servers", SD_JSON_VARIANT_ARRAY, dispatch_dns_server_array, offsetof(DNSConfiguration, dns_servers), 0 }, + { "searchDomains", SD_JSON_VARIANT_ARRAY, dispatch_search_domain_array, offsetof(DNSConfiguration, search_domains), 0 }, + + /* The remaining fields are currently unused by wait-online. */ + { "delegate", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "fallbackServers", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "negativeTrustAnchors", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "dnssec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "dnsOverTLS", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "llmnr", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "mDNS", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "resolvConfMode", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "scopes", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, {}, }; From 01278ceba0d16a5259b7d21433ce1c326bd671a5 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:35 -0400 Subject: [PATCH 11/13] resolve: add DumpDNSConfiguration to varlink API Add io.systemd.Resolve.DumpDNSConfiguration. This provides the same information as io.systemd.Resolve.Monitor.SubscribeDNSConfiguration, but just returns the configuration once without the subscription logic. In order to use the same definitions for DNSConfiguration et al. between both interfaces, move the definitions to io.systemd.Resolve, and include them in io.systemd.Resolve.Monitor. This will be used to implement --json for resolvectl status. --- src/resolve/resolved-varlink.c | 40 +++++++-- .../varlink-io.systemd.Resolve.Monitor.c | 86 +++--------------- src/shared/varlink-io.systemd.Resolve.c | 89 +++++++++++++++++++ src/shared/varlink-io.systemd.Resolve.h | 4 + 4 files changed, 135 insertions(+), 84 deletions(-) diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 0a73e41961..e4ea55b173 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -1409,6 +1409,29 @@ fail: return log_debug_errno(r, "Failed to subscribe client to DNS configuration monitor: %m"); } +static int vl_method_dump_dns_configuration(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL; + Manager *m; + Link *l; + int r; + + assert(link); + + m = ASSERT_PTR(sd_varlink_server_get_userdata(sd_varlink_get_server(link))); + + /* Make sure the accessible flag is not stale. */ + dns_server_reset_accessible_all(m->dns_servers); + + HASHMAP_FOREACH(l, m->links) + dns_server_reset_accessible_all(l->dns_servers); + + r = manager_dump_dns_configuration_json(m, &configuration); + if (r < 0) + return r; + + return sd_varlink_reply(link, configuration); +} + static int varlink_monitor_server_init(Manager *m) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL; int r; @@ -1481,14 +1504,15 @@ static int varlink_main_server_init(Manager *m) { r = sd_varlink_server_bind_method_many( s, - "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, - "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address, - "io.systemd.Resolve.ResolveService", vl_method_resolve_service, - "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.Resolve.BrowseServices", vl_method_browse_services); + "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, + "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address, + "io.systemd.Resolve.ResolveService", vl_method_resolve_service, + "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.Resolve.BrowseServices", vl_method_browse_services, + "io.systemd.Resolve.DumpDNSConfiguration", vl_method_dump_dns_configuration); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index bd514145fc..0fc67df221 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -3,8 +3,16 @@ #include "bus-polkit.h" #include "varlink-io.systemd.Resolve.Monitor.h" -/* We want to reuse the ResourceKey and ResourceRecord structures from the io.systemd.Resolve interface, - * hence import them here. */ +/* We want to reuse several structures from the io.systemd.Resolve interface, namely: + * + * - ResourceKey + * - ResourceRecord + * - DNSServer + * - DNSScope + * - SearchDomain + * - DNSConfiguration + * + * Hence, import them here . */ #include "varlink-io.systemd.Resolve.h" static SD_VARLINK_DEFINE_STRUCT_TYPE( @@ -114,80 +122,6 @@ static SD_VARLINK_DEFINE_METHOD( ResetStatistics, VARLINK_DEFINE_POLKIT_INPUT); -static SD_VARLINK_DEFINE_STRUCT_TYPE( - DNSServer, - SD_VARLINK_FIELD_COMMENT("IPv4 or IPv6 address of the server."), - SD_VARLINK_DEFINE_FIELD(address, SD_VARLINK_INT, SD_VARLINK_ARRAY), - SD_VARLINK_FIELD_COMMENT("IPv4 or IPv6 address of the server, formatted as a human-readable string."), - SD_VARLINK_DEFINE_FIELD(addressString, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Address family of the server, one of AF_INET or AF_INET6."), - SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, 0), - SD_VARLINK_FIELD_COMMENT("Port number of the server."), - SD_VARLINK_DEFINE_FIELD(port, SD_VARLINK_INT, 0), - SD_VARLINK_FIELD_COMMENT("Interface index for which this server is configured."), - SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Server Name Indication (SNI) of the server."), - SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Indicates if the DNS server is accessible or not."), - SD_VARLINK_DEFINE_FIELD(accessible, SD_VARLINK_BOOL, 0)); - -static SD_VARLINK_DEFINE_STRUCT_TYPE( - SearchDomain, - SD_VARLINK_FIELD_COMMENT("Domain name."), - SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0), - SD_VARLINK_FIELD_COMMENT("Indicates whether or not this is a routing-only domain."), - SD_VARLINK_DEFINE_FIELD(routeOnly, SD_VARLINK_BOOL, 0), - SD_VARLINK_FIELD_COMMENT("Interface index for which this search domain is configured."), - SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); - -static SD_VARLINK_DEFINE_STRUCT_TYPE( - DNSScope, - SD_VARLINK_FIELD_COMMENT("Protocol associated with this scope."), - SD_VARLINK_DEFINE_FIELD(protocol, SD_VARLINK_STRING, 0), - SD_VARLINK_FIELD_COMMENT("Address family associated with this scope."), - SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Interface index associated with this scope."), - SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Interface name associated with this scope."), - SD_VARLINK_DEFINE_FIELD(ifname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("DNSSEC mode associated with this scope."), - SD_VARLINK_DEFINE_FIELD(dnssec, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("DNSOverTLS mode associated with this scope."), - SD_VARLINK_DEFINE_FIELD(dnsOverTLS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); - -static SD_VARLINK_DEFINE_STRUCT_TYPE( - DNSConfiguration, - SD_VARLINK_FIELD_COMMENT("Interface name, if any, associated with this configuration. Empty for global configuration."), - SD_VARLINK_DEFINE_FIELD(ifname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Interface index, if any, associated with this configuration. Empty for global configuration."), - SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Delegate name, if any, associated with this configuration. Empty for global or link configurations."), - SD_VARLINK_DEFINE_FIELD(delegate, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Indicates whether or not this link's DNS servers will be used for resolving domain names that do not match any link's configured domains."), - SD_VARLINK_DEFINE_FIELD(defaultRoute, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("DNS server currently selected to use for lookups."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(currentServer, DNSServer, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Array of configured DNS servers."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(servers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Array of configured fallback DNS servers, set for global configuration only."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(fallbackServers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Array of configured search domains."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Array of configured DNSSEC negative trust anchors."), - SD_VARLINK_DEFINE_FIELD(negativeTrustAnchors, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("DNSSEC mode."), - SD_VARLINK_DEFINE_FIELD(dnssec, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("DNSOverTLS mode."), - SD_VARLINK_DEFINE_FIELD(dnsOverTLS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("LLMNR support."), - SD_VARLINK_DEFINE_FIELD(llmnr, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("mDNS support."), - SD_VARLINK_DEFINE_FIELD(mDNS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("resolv.conf mode, set for global configuration only."), - SD_VARLINK_DEFINE_FIELD(resolvConfMode, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Array of current DNS scopes."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(scopes, DNSScope, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); - static SD_VARLINK_DEFINE_METHOD_FULL( SubscribeDNSConfiguration, SD_VARLINK_REQUIRES_MORE, diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c index 80835aef4f..bdc0d9cd07 100644 --- a/src/shared/varlink-io.systemd.Resolve.c +++ b/src/shared/varlink-io.systemd.Resolve.c @@ -170,6 +170,80 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(raw, SD_VARLINK_STRING, 0)); +SD_VARLINK_DEFINE_STRUCT_TYPE( + DNSServer, + SD_VARLINK_FIELD_COMMENT("IPv4 or IPv6 address of the server."), + SD_VARLINK_DEFINE_FIELD(address, SD_VARLINK_INT, SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("IPv4 or IPv6 address of the server, formatted as a human-readable string."), + SD_VARLINK_DEFINE_FIELD(addressString, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Address family of the server, one of AF_INET or AF_INET6."), + SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Port number of the server."), + SD_VARLINK_DEFINE_FIELD(port, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Interface index for which this server is configured."), + SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Server Name Indication (SNI) of the server."), + SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Indicates if the DNS server is accessible or not."), + SD_VARLINK_DEFINE_FIELD(accessible, SD_VARLINK_BOOL, 0)); + +SD_VARLINK_DEFINE_STRUCT_TYPE( + SearchDomain, + SD_VARLINK_FIELD_COMMENT("Domain name."), + SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Indicates whether or not this is a routing-only domain."), + SD_VARLINK_DEFINE_FIELD(routeOnly, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Interface index for which this search domain is configured."), + SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_STRUCT_TYPE( + DNSScope, + SD_VARLINK_FIELD_COMMENT("Protocol associated with this scope."), + SD_VARLINK_DEFINE_FIELD(protocol, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Address family associated with this scope."), + SD_VARLINK_DEFINE_FIELD(family, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Interface index associated with this scope."), + SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Interface name associated with this scope."), + SD_VARLINK_DEFINE_FIELD(ifname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNSSEC mode associated with this scope."), + SD_VARLINK_DEFINE_FIELD(dnssec, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNSOverTLS mode associated with this scope."), + SD_VARLINK_DEFINE_FIELD(dnsOverTLS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_STRUCT_TYPE( + DNSConfiguration, + SD_VARLINK_FIELD_COMMENT("Interface name, if any, associated with this configuration. Empty for global configuration."), + SD_VARLINK_DEFINE_FIELD(ifname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Interface index, if any, associated with this configuration. Empty for global configuration."), + SD_VARLINK_DEFINE_FIELD(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Delegate name, if any, associated with this configuration. Empty for global or link configurations."), + SD_VARLINK_DEFINE_FIELD(delegate, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Indicates whether or not this link's DNS servers will be used for resolving domain names that do not match any link's configured domains."), + SD_VARLINK_DEFINE_FIELD(defaultRoute, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNS server currently selected to use for lookups."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(currentServer, DNSServer, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Array of configured DNS servers."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(servers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Array of configured fallback DNS servers, set for global configuration only."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(fallbackServers, DNSServer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Array of configured search domains."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Array of configured DNSSEC negative trust anchors."), + SD_VARLINK_DEFINE_FIELD(negativeTrustAnchors, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNSSEC mode."), + SD_VARLINK_DEFINE_FIELD(dnssec, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("DNSOverTLS mode."), + SD_VARLINK_DEFINE_FIELD(dnsOverTLS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("LLMNR support."), + SD_VARLINK_DEFINE_FIELD(llmnr, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("mDNS support."), + SD_VARLINK_DEFINE_FIELD(mDNS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("resolv.conf mode, set for global configuration only."), + SD_VARLINK_DEFINE_FIELD(resolvConfMode, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Array of current DNS scopes."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(scopes, DNSScope, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_METHOD( ResolveRecord, SD_VARLINK_DEFINE_INPUT(ifindex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -194,6 +268,11 @@ static SD_VARLINK_DEFINE_METHOD_FULL( 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_METHOD( + DumpDNSConfiguration, + SD_VARLINK_FIELD_COMMENT("The current global and per-interface DNS configurations"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(configuration, DNSConfiguration, SD_VARLINK_ARRAY)); + static SD_VARLINK_DEFINE_ERROR(NoNameServers); static SD_VARLINK_DEFINE_ERROR(NoSuchResourceRecord); static SD_VARLINK_DEFINE_ERROR(QueryTimedOut); @@ -236,6 +315,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_ResolveRecord, SD_VARLINK_SYMBOL_COMMENT("Starts browsing for DNS-SD services of specified type."), &vl_method_BrowseServices, + SD_VARLINK_SYMBOL_COMMENT("Current global and per-link DNS configurations."), + &vl_method_DumpDNSConfiguration, SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved address."), &vl_type_ResolvedAddress, SD_VARLINK_SYMBOL_COMMENT("Encapsulates a resolved host name."), @@ -254,6 +335,14 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_BrowseServiceUpdateFlag, SD_VARLINK_SYMBOL_COMMENT("Encapsulates the service data obtained from browsing."), &vl_type_ServiceData, + SD_VARLINK_SYMBOL_COMMENT("Encapsulates a DNS server address specification."), + &vl_type_DNSServer, + SD_VARLINK_SYMBOL_COMMENT("Encapsulates a search domain specification."), + &vl_type_SearchDomain, + SD_VARLINK_SYMBOL_COMMENT("Encapsulates a global or per-link DNS configuration, including configured DNS servers, search domains, and more."), + &vl_type_DNSConfiguration, + SD_VARLINK_SYMBOL_COMMENT("Encapsulates a DNS scope specification."), + &vl_type_DNSScope, &vl_error_NoNameServers, &vl_error_NoSuchResourceRecord, &vl_error_QueryTimedOut, diff --git a/src/shared/varlink-io.systemd.Resolve.h b/src/shared/varlink-io.systemd.Resolve.h index b7a828530d..48fdabe13c 100644 --- a/src/shared/varlink-io.systemd.Resolve.h +++ b/src/shared/varlink-io.systemd.Resolve.h @@ -5,5 +5,9 @@ extern const sd_varlink_symbol vl_type_ResourceKey; extern const sd_varlink_symbol vl_type_ResourceRecord; +extern const sd_varlink_symbol vl_type_DNSServer; +extern const sd_varlink_symbol vl_type_DNSScope; +extern const sd_varlink_symbol vl_type_SearchDomain; +extern const sd_varlink_symbol vl_type_DNSConfiguration; extern const sd_varlink_interface vl_interface_io_systemd_Resolve; From 0536b37629c163af268975fcc3017cad823b1e9b Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:36 -0400 Subject: [PATCH 12/13] resolvectl: implement --json flag for resolvectl status Add --json support for all status commands in resolvectl by making use of the new DumpDNSConfiguration varlink method. E.g, $ resolvectl --json=pretty status eth0 [ { "ifname" : "eth0", "ifindex" : 9, "defaultRoute" : true, "currentServer" : { "addressString" : "10.148.181.1", "address" : [ 10, 148, 181, 1 ], "family" : 2, "port" : 53, "ifindex" : 9, "accessible" : true }, "servers" : [ { "addressString" : "10.148.181.1", "address" : [ 10, 148, 181, 1 ], "family" : 2, "port" : 53, "ifindex" : 9, "accessible" : true } ], "searchDomains" : [ { "name" : "local", "routeOnly" : false, "ifindex" : 9 } ], "dnssec" : "allow-downgrade", "dnsOverTLS" : "no", "llmnr" : "no", "mDNS" : "no", "scopes" : [ { "protocol" : "dns", "ifindex" : 9, "ifname" : "eth0", "dnssec" : "allow-downgrade", "dnsOverTLS" : "no" } ] } ] Like the regular status output, fields are omitted all together when empty, unless explicitly requested via one of the sub-commands dns, domain, nta, etc. --- src/resolve/resolvectl.c | 165 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 13f68f87b9..e412576654 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -45,6 +45,7 @@ #include "resolvectl.h" #include "resolved-def.h" #include "resolved-util.h" +#include "set.h" #include "socket-netlink.h" #include "sort-util.h" #include "stdio-util.h" @@ -1792,6 +1793,161 @@ static char** global_protocol_status(const GlobalInfo *info) { return TAKE_PTR(s); } +static const char* status_mode_to_json_field(StatusMode mode) { + switch (mode) { + + case STATUS_ALL: + return NULL; + + case STATUS_DNS: + return "servers"; + + case STATUS_DOMAIN: + return "searchDomains"; + + case STATUS_DEFAULT_ROUTE: + return "defaultRoute"; + + case STATUS_LLMNR: + return "llmnr"; + + case STATUS_MDNS: + return "mDNS"; + + case STATUS_PRIVATE: + return "dnsOverTLS"; + + case STATUS_DNSSEC: + return "dnssec"; + + case STATUS_NTA: + return "negativeTrustAnchors"; + + default: + assert_not_reached(); + } +} + +static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + const char *field; + int r; + + assert(configuration); + + field = status_mode_to_json_field(mode); + if (!field) + /* Nothing to filter for this mode. */ + return 0; + + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + /* Always include identifier fields like ifname or delegate, and include the requested + * field even if it is empty in the configuration. */ + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), + SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); + if (r < 0) + return r; + } + + JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); + return 0; +} + +static int status_json_filter_links(sd_json_variant **configuration, char **links) { + _cleanup_set_free_ Set *links_by_index = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + int r; + + assert(configuration); + + if (links) + STRV_FOREACH(ifname, links) { + int ifindex = rtnl_resolve_interface_or_warn(/* rtnl= */ NULL, *ifname); + + if (ifindex < 0) + return ifindex; + + r = set_ensure_put(&links_by_index, NULL, INT_TO_PTR(ifindex)); + if (r < 0) + return r; + } + + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + int ifindex = sd_json_variant_unsigned(sd_json_variant_by_key(w, "ifindex")); + + if (links_by_index) { + if (ifindex <= 0) + /* Possibly invalid, but most likely unset because this is global + * or delegate configuration. */ + continue; + + if (!set_contains(links_by_index, INT_TO_PTR(ifindex))) + continue; + + } else if (ifindex == LOOPBACK_IFINDEX) + /* By default, exclude the loopback interface. */ + continue; + + r = sd_json_variant_append_array(&v, w); + if (r < 0) + return r; + } + + JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); + return 0; +} + +static int varlink_dump_dns_configuration(sd_json_variant **ret) { + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + sd_json_variant *v; + int r; + + assert(ret); + + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); + if (r < 0) + return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); + + r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); + if (r < 0) + return r; + + v = sd_json_variant_by_key(reply, "configuration"); + + if (!sd_json_variant_is_array(v)) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); + + TAKE_PTR(reply); + *ret = sd_json_variant_ref(v); + return 0; +} + +static int status_json(StatusMode mode, char **links) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL; + int r; + + r = varlink_dump_dns_configuration(&configuration); + if (r < 0) + return r; + + r = status_json_filter_links(&configuration, links); + if (r < 0) + return log_error_errno(r, "Failed to filter configuration JSON links: %m"); + + r = status_json_filter_fields(&configuration, mode); + if (r < 0) + return log_error_errno(r, "Failed to filter configuration JSON fields: %m"); + + return sd_json_variant_dump(configuration, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); +} + static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode mode, bool *empty_line) { static const struct bus_properties_map property_map[] = { { "ScopesMask", "t", NULL, offsetof(LinkInfo, scopes_mask) }, @@ -1828,6 +1984,9 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode name = ifname; } + if (sd_json_format_enabled(arg_json_format_flags)) + return status_json(mode, STRV_MAKE(name)); + xsprintf(ifi, "%i", ifindex); r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifi, &p); if (r < 0) @@ -2423,6 +2582,9 @@ static int status_all(sd_bus *bus, StatusMode mode) { assert(bus); + if (sd_json_format_enabled(arg_json_format_flags)) + return status_json(mode, /* links= */ NULL); + r = status_global(bus, mode, &empty_line); if (r < 0) return r; @@ -2444,6 +2606,9 @@ static int verb_status(int argc, char **argv, void *userdata) { bool empty_line = false; int r, ret = 0; + if (sd_json_format_enabled(arg_json_format_flags)) + return status_json(STATUS_ALL, argc > 1 ? strv_skip(argv, 1) : NULL); + r = acquire_bus(&bus); if (r < 0) return r; From 313d2166629b3e11d6e35493beccce9d666b22af Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 10 Oct 2025 15:56:36 -0400 Subject: [PATCH 13/13] test: expand testcases to include resolvectl --json usage --- test/units/TEST-75-RESOLVED.sh | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index b40856f663..4d2257d6e0 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -852,6 +852,18 @@ testcase_09_resolvectl_showcache() { } testcase_10_resolvectl_json() { + local status_json + + # Cleanup + # shellcheck disable=SC2317 + cleanup() { + rm -f /run/systemd/resolved.conf.d/90-fallback.conf + systemctl reload systemd-resolved.service + resolvectl revert dns0 + } + + trap cleanup RETURN ERR + # Issue: https://github.com/systemd/systemd/issues/29580 (part #1) dig @127.0.0.54 signed.test @@ -871,6 +883,68 @@ testcase_10_resolvectl_json() { # so we need to select it only if it's present, otherwise the type == "array" check would fail echo "$line" | jq -e '[. | .question, (select(has("answer")) | .answer) | type == "array"] | all' done + + + # Test some global-only settings. + mkdir -p /run/systemd/resolved.conf.d + { + echo "[Resolve]" + echo "FallbackDNS=10.0.0.1 10.0.0.2" + } >/run/systemd/resolved.conf.d/90-fallback.conf + systemctl reload systemd-resolved + + status_json="$(mktemp)" + resolvectl --json=short >"$status_json" + + # Delegates field should be empty when no delegates are configured. + (! jq -rce '.[] | select(.delegate != null)' "$status_json") + + # Test that some links are present. + jq -rce '.[] | select(.ifname == "dns0")' "$status_json" + + # Test some global-specific configuration. + assert_eq \ + "$(jq -rc '.[] | select(.ifindex == null and .delegate == null) | [ .fallbackServers[] | .addressString ]' "$status_json")" \ + '["10.0.0.1","10.0.0.2"]' + assert_eq \ + "$(jq -rc '.[] | select(.ifindex == null and .delegate == null) | .resolvConfMode' "$status_json")" \ + 'stub' + + # Test link status. + resolvectl dns dns0 '1.2.3.4' + resolvectl domain dns0 'foo' + resolvectl default-route dns0 'false' + resolvectl llmnr dns0 'no' + resolvectl mdns dns0 'no' + resolvectl dnsovertls dns0 'opportunistic' + resolvectl dnssec dns0 'yes' + resolvectl nta dns0 'bar' + + resolvectl --json=short status dns0 >"$status_json" + + assert_eq "$(resolvectl --json=short dns dns0 | jq -rc '.[0].servers | .[0].addressString')" '1.2.3.4' + assert_eq "$(jq -rc '.[0].servers | .[0].addressString' "$status_json")" '1.2.3.4' + + assert_eq "$(resolvectl --json=short domain dns0 | jq -rc '.[0].searchDomains| .[0].name')" 'foo' + assert_eq "$(jq -rc '.[0].searchDomains | .[0].name' "$status_json")" 'foo' + + assert_eq "$(resolvectl --json=short default-route dns0 | jq -rc '.[0].defaultRoute')" 'false' + assert_eq "$(jq -rc '.[0].defaultRoute' "$status_json")" 'false' + + assert_eq "$(resolvectl --json=short llmnr dns0 | jq -rc '.[0].llmnr')" 'no' + assert_eq "$(jq -rc '.[0].llmnr' "$status_json")" 'no' + + assert_eq "$(resolvectl --json=short mdns dns0 | jq -rc '.[0].mDNS')" 'no' + assert_eq "$(jq -rc '.[0].mDNS' "$status_json")" 'no' + + assert_eq "$(resolvectl --json=short dnsovertls dns0 | jq -rc '.[0].dnsOverTLS')" 'opportunistic' + assert_eq "$(jq -rc '.[0].dnsOverTLS' "$status_json")" 'opportunistic' + + assert_eq "$(resolvectl --json=short dnssec dns0 | jq -rc '.[0].dnssec')" 'yes' + assert_eq "$(jq -rc '.[0].dnssec' "$status_json")" 'yes' + + assert_eq "$(resolvectl --json=short nta dns0 | jq -rc '.[0].negativeTrustAnchors | .[0]')" 'bar' + assert_eq "$(jq -rc '.[0].negativeTrustAnchors | .[0]' "$status_json")" 'bar' } # Test serve stale feature and NFTSet= if nftables is installed @@ -1414,6 +1488,9 @@ EOF systemctl reload systemd-resolved resolvectl status + assert_eq "$(resolvectl --json=short | jq -rc '.[] | select(.delegate == "testcase") | .servers | .[0].addressString')" '192.168.77.78' + assert_eq "$(resolvectl --json=short | jq -rc '.[] | select(.delegate == "testcase") | .searchDomains | .[0].name')" 'exercise.test' + # Now that we installed the delegation the resolution should fail, because nothing is listening on that IP address (! resolvectl query delegation.exercise.test)