diff --git a/src/network/wait-online/dns-configuration.c b/src/network/wait-online/dns-configuration.c index 320f50a981..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 }, {}, }; @@ -198,7 +210,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) { 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; 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/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 7e6c074c11..29c5f4488e 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2043,15 +2043,27 @@ 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, + DnsServer *fallback_dns_servers, DnsSearchDomain *search_domains, + Set *negative_trust_anchors, + Set *dns_scopes, + DnssecMode dnssec_mode, + 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, + *fallback_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); @@ -2074,6 +2086,29 @@ 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; + + 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; @@ -2102,46 +2137,183 @@ 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), 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)); + 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", + 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_STRING_NON_EMPTY("resolvConfMode", resolv_conf_mode_to_string(resolv_conf_mode)), + 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, + /* delegate = */ NULL, + /* 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, + manager_get_dnssec_mode(m), + manager_get_dns_over_tls_mode(m), + m->llmnr_support, + m->mdns_support, + resolv_conf_mode(), + 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, + /* delegate = */ NULL, + 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, + link_get_dnssec_mode(l), + 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); +} + +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, + d->id, + d->default_route, + dns_delegate_get_dns_server(d), + d->dns_servers, + /* fallback_dns_servers = */ NULL, + 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, + /* resolv_conf_mode = */ _RESOLV_CONF_MODE_INVALID, + 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); 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; + } + + /* Append configuration for each delegate */ + HASHMAP_FOREACH(d, m->delegates) { + r = delegate_dns_configuration_json_append(d, &configuration); if (r < 0) return r; } 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 2861368a45..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,45 +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("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( - 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("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 search domains."), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(searchDomains, SearchDomain, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); - static SD_VARLINK_DEFINE_METHOD_FULL( SubscribeDNSConfiguration, SD_VARLINK_REQUIRES_MORE, @@ -184,5 +153,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); 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; 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)