<annotate key="org.freedesktop.policykit.owner">unix-user:systemd-resolve</annotate>
</action>
+ <action id="org.freedesktop.resolve1.subscribe-dns-configuration">
+ <description gettext-domain="systemd">Subscribe to DNS configuration</description>
+ <message gettext-domain="systemd">Authentication is required to subscribe to DNS configuration.</message>
+ <defaults>
+ <allow_any>auth_admin</allow_any>
+ <allow_inactive>auth_admin</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-resolve</annotate>
+ </action>
+
<action id="org.freedesktop.resolve1.dump-cache">
<description gettext-domain="systemd">Dump cache</description>
<message gettext-domain="systemd">Authentication is required to dump cache.</message>
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "sd-json.h"
+
#include "alloc-util.h"
#include "dns-domain.h"
#include "resolved-dns-search-domain.h"
*ret = NULL;
return 0;
}
+
+int dns_search_domain_dump_to_json(DnsSearchDomain *domain, sd_json_variant **ret) {
+ int ifindex = 0;
+
+ assert(domain);
+ assert(ret);
+
+ if (domain->type == DNS_SEARCH_DOMAIN_LINK) {
+ assert(domain->link);
+ ifindex = domain->link->ifindex;
+ }
+
+ return sd_json_buildo(
+ ret,
+ SD_JSON_BUILD_PAIR_STRING("name", domain->name),
+ SD_JSON_BUILD_PAIR_BOOLEAN("routeOnly", domain->route_only),
+ SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", SD_JSON_BUILD_UNSIGNED(ifindex)));
+}
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include "sd-json.h"
+
#include "list.h"
#include "macro.h"
}
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref);
+
+int dns_search_domain_dump_to_json(DnsSearchDomain *domain, sd_json_variant **ret);
#include "alloc-util.h"
#include "errno-util.h"
#include "fd-util.h"
+#include "json-util.h"
#include "resolved-bus.h"
#include "resolved-dns-server.h"
#include "resolved-dns-stub.h"
dns_cache_flush(&m->unicast_scope->cache);
(void) manager_send_changed(m, "CurrentDNSServer");
+ (void) manager_send_dns_configuration_changed(m, NULL, /* reset= */ false);
return s;
}
LIST_FOREACH(servers, s, first)
dns_server_reset_accessible(s);
}
+
+int dns_server_dump_configuration_to_json(DnsServer *server, sd_json_variant **ret) {
+ bool accessible = false;
+ int ifindex, r;
+
+ assert(server);
+ assert(ret);
+
+ ifindex = dns_server_ifindex(server);
+
+ r = dns_server_is_accessible(server);
+ if (r < 0)
+ log_debug_errno(r, "Failed to check if %s is accessible, assume not: %m", dns_server_string_full(server));
+ else
+ accessible = r;
+
+ return sd_json_buildo(
+ ret,
+ 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)),
+ SD_JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", SD_JSON_BUILD_UNSIGNED(ifindex)),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("name", server->server_name),
+ SD_JSON_BUILD_PAIR_BOOLEAN("accessible", accessible));
+}
}
int dns_server_dump_state_to_json(DnsServer *server, sd_json_variant **ret);
+int dns_server_dump_configuration_to_json(DnsServer *server, sd_json_variant **ret);
int dns_server_is_accessible(DnsServer *s);
static inline void dns_server_reset_accessible(DnsServer *s) {
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
(void) manager_send_changed(l->manager, "DNS");
+ (void) manager_send_dns_configuration_changed(l->manager, l, /* reset= */ true);
if (j)
log_link_info(l, "Bus client set DNS server list to: %s", j);
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
(void) manager_send_changed(l->manager, "DNS");
+ (void) manager_send_dns_configuration_changed(l->manager, l, /* reset= */ true);
manager_llmnr_maybe_stop(l->manager);
manager_mdns_maybe_stop(l->manager);
#include "idn-util.h"
#include "io-util.h"
#include "iovec-util.h"
+#include "json-util.h"
#include "memstream-util.h"
#include "missing_network.h"
#include "missing_socket.h"
/* Now check all the links, and if mDNS/llmr are disabled everywhere, stop them globally too. */
manager_llmnr_maybe_stop(m);
manager_mdns_maybe_stop(m);
+
+ /* The accessible flag on link DNS servers will have been reset by
+ * link_update(). Just reset the global DNS servers. */
+ (void) manager_send_dns_configuration_changed(m, NULL, /* reset= */ true);
+
return 0;
fail:
break;
}
+ (void) manager_send_dns_configuration_changed(m, l, /* reset= */ true);
+
return 0;
fail:
return 0;
}
+static int manager_process_route(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+ Link *l = NULL;
+ uint16_t type;
+ uint32_t ifindex = 0;
+ int r;
+
+ assert(rtnl);
+ assert(mm);
+
+ r = sd_netlink_message_get_type(mm, &type);
+ if (r < 0) {
+ log_warning_errno(r, "Failed not get message type, ignoring: %m");
+ return 0;
+ }
+
+ if (!IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)) {
+ log_warning("Unexpected message type %u when processing route, ignoring.", type);
+ return 0;
+ }
+
+ r = sd_netlink_message_read_u32(mm, RTA_OIF, &ifindex);
+ if (r < 0)
+ log_full_errno(r == -ENODATA ? LOG_DEBUG : LOG_WARNING, r, "Failed to get route ifindex, ignoring: %m");
+ else
+ l = hashmap_get(m->links, INT_TO_PTR(ifindex));
+
+ (void) manager_send_dns_configuration_changed(m, l, /* reset= */ true);
+
+ return 0;
+}
+
static int manager_rtnl_listen(Manager *m) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
int r;
(void) manager_write_resolv_conf(m);
(void) manager_send_changed(m, "DNS");
+ (void) manager_send_dns_configuration_changed(m, NULL, /* reset= */ true);
/* Now check all the links, and if mDNS/llmr are disabled everywhere, stop them globally too. */
manager_llmnr_maybe_stop(m);
sd_event_source_unref(m->network_event_source);
sd_network_monitor_unref(m->network_monitor);
+ sd_netlink_slot_unref(m->netlink_new_route_slot);
+ sd_netlink_slot_unref(m->netlink_del_route_slot);
sd_netlink_unref(m->rtnl);
sd_event_source_unref(m->rtnl_event_source);
sd_event_source_unref(m->clock_change_event_source);
+ sd_json_variant_unref(m->dns_configuration_json);
+
manager_llmnr_stop(m);
manager_mdns_stop(m);
manager_dns_stub_stop(m);
m->n_failure_responses_served_stale_total = 0;
zero(m->n_dnssec_verdict);
}
+
+static int dns_configuration_json_append(
+ const char *ifname,
+ int ifindex,
+ int default_route,
+ DnsServer *current_dns_server,
+ DnsServer *dns_servers,
+ DnsSearchDomain *search_domains,
+ 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;
+ int r;
+
+ assert(configuration);
+
+ if (dns_servers) {
+ r = sd_json_variant_new_array(&dns_servers_json, NULL, 0);
+ if (r < 0)
+ return r;
+ }
+
+ if (search_domains) {
+ r = sd_json_variant_new_array(&search_domains_json, NULL, 0);
+ if (r < 0)
+ return r;
+ }
+
+ if (current_dns_server) {
+ r = dns_server_dump_configuration_to_json(current_dns_server, ¤t_dns_server_json);
+ if (r < 0)
+ return r;
+ }
+
+ LIST_FOREACH(servers, s, dns_servers) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+
+ assert(dns_servers_json);
+
+ r = dns_server_dump_configuration_to_json(s, &v);
+ if (r < 0)
+ return r;
+
+ r = sd_json_variant_append_array(&dns_servers_json, v);
+ if (r < 0)
+ return r;
+ }
+
+ LIST_FOREACH(domains, d, search_domains) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+
+ assert(search_domains_json);
+
+ r = dns_search_domain_dump_to_json(d, &v);
+ if (r < 0)
+ return r;
+
+ r = sd_json_variant_append_array(&search_domains_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_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));
+}
+
+int manager_dump_dns_configuration_json(Manager *m, sd_json_variant **ret) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL;
+ Link *l;
+ 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);
+ 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);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_json_buildo(ret, SD_JSON_BUILD_PAIR_VARIANT("configuration", configuration));
+}
+
+int manager_send_dns_configuration_changed(Manager *m, Link *l, bool reset) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *configuration = NULL;
+ int r;
+
+ assert(m);
+
+ if (set_isempty(m->varlink_dns_configuration_subscription))
+ return 0;
+
+ if (reset) {
+ dns_server_reset_accessible_all(m->dns_servers);
+
+ if (l)
+ dns_server_reset_accessible_all(l->dns_servers);
+ }
+
+ r = manager_dump_dns_configuration_json(m, &configuration);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to dump DNS configuration json: %m");
+
+ if (sd_json_variant_equal(configuration, m->dns_configuration_json))
+ return 0;
+
+ JSON_VARIANT_REPLACE(m->dns_configuration_json, TAKE_PTR(configuration));
+
+ r = varlink_many_notify(m->varlink_dns_configuration_subscription, m->dns_configuration_json);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to send DNS configuration event: %m");
+
+ return 0;
+}
+
+int manager_start_dns_configuration_monitor(Manager *m) {
+ Link *l;
+ int r;
+
+ assert(m);
+ assert(!m->dns_configuration_json);
+ assert(!m->netlink_new_route_slot);
+ assert(!m->netlink_del_route_slot);
+
+ 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, &m->dns_configuration_json);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, &m->netlink_new_route_slot, RTM_NEWROUTE, manager_process_route, NULL, m, "resolve-NEWROUTE");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, &m->netlink_del_route_slot, RTM_DELROUTE, manager_process_route, NULL, m, "resolve-DELROUTE");
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_stop_dns_configuration_monitor(Manager *m) {
+ assert(m);
+
+ m->dns_configuration_json = sd_json_variant_unref(m->dns_configuration_json);
+ m->netlink_new_route_slot = sd_netlink_slot_unref(m->netlink_new_route_slot);
+ m->netlink_del_route_slot = sd_netlink_slot_unref(m->netlink_del_route_slot);
+}
sd_varlink_server *varlink_monitor_server;
Set *varlink_query_results_subscription;
+ Set *varlink_dns_configuration_subscription;
+
+ sd_json_variant *dns_configuration_json;
+
+ sd_netlink_slot *netlink_new_route_slot;
+ sd_netlink_slot *netlink_del_route_slot;
sd_event_source *clock_change_event_source;
int dns_manager_dump_statistics_json(Manager *m, sd_json_variant **ret);
void dns_manager_reset_statistics(Manager *m);
+
+int manager_dump_dns_configuration_json(Manager *m, sd_json_variant **ret);
+int manager_send_dns_configuration_changed(Manager *m, Link *l, bool reset);
+
+int manager_start_dns_configuration_monitor(Manager *m);
+void manager_stop_dns_configuration_monitor(Manager *m);
static void vl_on_notification_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
+ sd_varlink *removed_link = NULL;
assert(s);
assert(link);
- sd_varlink *removed_link = set_remove(m->varlink_query_results_subscription, link);
+ removed_link = set_remove(m->varlink_query_results_subscription, link);
if (removed_link) {
sd_varlink_unref(removed_link);
log_debug("%u query result monitor clients remain active", set_size(m->varlink_query_results_subscription));
}
+
+ removed_link = set_remove(m->varlink_dns_configuration_subscription, link);
+ if (removed_link) {
+ sd_varlink_unref(removed_link);
+ log_debug("%u DNS monitor clients remain active", set_size(m->varlink_dns_configuration_subscription));
+
+ if (set_isempty(m->varlink_dns_configuration_subscription))
+ manager_stop_dns_configuration_monitor(m);
+ }
}
static bool validate_and_mangle_flags(
return sd_varlink_replyb(link, SD_JSON_BUILD_EMPTY_OBJECT);
}
+static int vl_method_subscribe_dns_configuration(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+ Manager *m = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link)));
+ int r;
+
+ /* if the client didn't set the more flag, it is using us incorrectly */
+ if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
+ return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL);
+
+ r = verify_polkit(link, parameters, "org.freedesktop.resolve1.subscribe-dns-configuration");
+ if (r <= 0)
+ return r;
+
+ if (set_isempty(m->varlink_dns_configuration_subscription)) {
+ r = manager_start_dns_configuration_monitor(m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to start DNS configuration monitor: %m");
+ }
+
+ r = sd_varlink_notify(link, m->dns_configuration_json);
+ if (r < 0)
+ goto fail;
+
+ r = set_ensure_put(&m->varlink_dns_configuration_subscription, NULL, link);
+ if (r < 0)
+ goto fail;
+ sd_varlink_ref(link);
+
+ log_debug("%u clients now attached for link configuration varlink notifications",
+ set_size(m->varlink_dns_configuration_subscription));
+
+ return 1;
+fail:
+ if (set_isempty(m->varlink_dns_configuration_subscription))
+ manager_stop_dns_configuration_monitor(m);
+
+
+ return log_debug_errno(r, "Failed to subscribe client to DNS configuration monitor: %m");
+}
+
static int varlink_monitor_server_init(Manager *m) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL;
int r;
"io.systemd.Resolve.Monitor.DumpCache", vl_method_dump_cache,
"io.systemd.Resolve.Monitor.DumpServerState", vl_method_dump_server_state,
"io.systemd.Resolve.Monitor.DumpStatistics", vl_method_dump_statistics,
- "io.systemd.Resolve.Monitor.ResetStatistics", vl_method_reset_statistics);
+ "io.systemd.Resolve.Monitor.ResetStatistics", vl_method_reset_statistics,
+ "io.systemd.Resolve.Monitor.SubscribeDNSConfiguration", vl_method_subscribe_dns_configuration);
if (r < 0)
return log_error_errno(r, "Failed to register varlink methods: %m");
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,
+ SD_VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The current global and per-interface DNS configurations"),
+ SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(configuration, DNSConfiguration, SD_VARLINK_ARRAY));
+
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_Resolve_Monitor,
"io.systemd.Resolve.Monitor",
&vl_type_TransactionStatistics,
&vl_type_CacheStatistics,
&vl_type_DnssecStatistics,
- &vl_type_ServerState);
+ &vl_type_ServerState,
+ 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("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);