resolved: add SubscribeDNSConfiguration to varlink API

Add a new method to io.systemd.Resolve.Monitor that allows subscribing
to changes in the systemd-resolved DNS configuration. The new method
emits the full DNS configuration (one entry for global configuration,
and one entry for each interface), any time the configuration is
updated.
This commit is contained in:
Nick Rosbrook
2024-10-11 14:44:44 -04:00
parent f63acba4ad
commit 54401c6fdf
10 changed files with 407 additions and 3 deletions

View File

@@ -150,6 +150,17 @@
<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>

View File

@@ -1,5 +1,7 @@
/* 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"
@@ -197,3 +199,21 @@ int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDo
*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)));
}

View File

@@ -1,6 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-json.h"
#include "list.h"
#include "macro.h"
@@ -54,3 +56,5 @@ static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) {
}
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref);
int dns_search_domain_dump_to_json(DnsSearchDomain *domain, sd_json_variant **ret);

View File

@@ -7,6 +7,7 @@
#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"
@@ -881,6 +882,7 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) {
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;
}
@@ -1180,3 +1182,28 @@ void dns_server_reset_accessible_all(DnsServer *first) {
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));
}

View File

@@ -190,6 +190,7 @@ static inline bool dns_server_is_fallback(DnsServer *s) {
}
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) {

View File

@@ -272,6 +272,7 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi
(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);
@@ -751,6 +752,7 @@ int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error
(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);

View File

@@ -22,6 +22,7 @@
#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"
@@ -108,6 +109,11 @@ static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *
/* 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:
@@ -192,6 +198,8 @@ static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, voi
break;
}
(void) manager_send_dns_configuration_changed(m, l, /* reset= */ true);
return 0;
fail:
@@ -199,6 +207,38 @@ 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;
@@ -289,6 +329,7 @@ static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *
(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);
@@ -808,10 +849,14 @@ Manager *manager_free(Manager *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);
@@ -1938,3 +1983,181 @@ void dns_manager_reset_statistics(Manager *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, &current_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);
}

View File

@@ -153,6 +153,12 @@ struct Manager {
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;
@@ -225,3 +231,9 @@ int socket_disable_pmtud(int fd, int af);
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);

View File

@@ -127,15 +127,25 @@ static void vl_on_disconnect(sd_varlink_server *s, sd_varlink *link, void *userd
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(
@@ -1354,6 +1364,45 @@ static int vl_method_reset_statistics(sd_varlink *link, sd_json_variant *paramet
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;
@@ -1377,7 +1426,8 @@ static int varlink_monitor_server_init(Manager *m) {
"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");

View File

@@ -112,6 +112,52 @@ 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,
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",
@@ -129,4 +175,12 @@ SD_VARLINK_DEFINE_INTERFACE(
&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);