]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: add SubscribeDNSConfiguration to varlink API
authorNick Rosbrook <enr0n@ubuntu.com>
Fri, 11 Oct 2024 18:44:44 +0000 (14:44 -0400)
committerNick Rosbrook <enr0n@ubuntu.com>
Tue, 28 Jan 2025 00:26:31 +0000 (19:26 -0500)
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.

src/resolve/org.freedesktop.resolve1.policy
src/resolve/resolved-dns-search-domain.c
src/resolve/resolved-dns-search-domain.h
src/resolve/resolved-dns-server.c
src/resolve/resolved-dns-server.h
src/resolve/resolved-link-bus.c
src/resolve/resolved-manager.c
src/resolve/resolved-manager.h
src/resolve/resolved-varlink.c
src/shared/varlink-io.systemd.Resolve.Monitor.c

index b96c8c0d6ade44065605fbe13d7750ede918d593..097e78e73ca7eee44c39c17392e3c548b7ab12c3 100644 (file)
                 <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>
index a11b21350a57ab43286df6ad8246a25c7f087b57..642d52c6ea3bf7c7c5b1f6358865552f6686e0bf 100644 (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)));
+}
index f0d96aca37ddd011ea3fed30155a08060f1a28f3..3e5229c12f566f61b7a6ed4f2cfecf3f342fea7b 100644 (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);
index 1702866a1435f897da6e5db859c23b087278c054..54d0d32ec5d962d9c5c61544e34d373ad304af12 100644 (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));
+}
index 8915b87e957bc97d8b41c8c43610e2320bb03499..0f066b15b9f004b39b419578b822446297145309 100644 (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) {
index b0c4b833b5d1e867192ccaf6b57c2427c6747128..77d2663e4f3d318116345da2f1417eff58a400f7 100644 (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);
index 883bc097015f766bfbc281dd98b7fa44585f6b2b..e37497869181dd8a8970f00b6d0df9c68d583c61 100644 (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);
+}
index 5d870111d1bbd01bab210c7e75c7c29f7a7fb7ab..3481a8836bfdc97c37922291291da4acc1451aa1 100644 (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);
index fd2fc588532efbade1cf59c0320b551e3902f2e4..02e00eef7dad29d537b6396bf279565280da0a25 100644 (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");
 
index bc8907ddbe093368dc024b116fde0c5ce6b190a8..18d4eafefa058af7014eb7addc483dce9f00cd06 100644 (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);