summary refs log tree commit diff
path: root/src/core/dns
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2023-02-10 11:50:34 +0100
committerMichael Biebl <biebl@debian.org>2023-02-10 11:50:34 +0100
commit1372848511cb896b80b51ed1a3e9606bd9816631 (patch)
tree674792b9385bdef935988894b45f06b2af39f88c /src/core/dns
parent40ec077ea305994c1fc2130add6787ca0c73e2c6 (diff)
New upstream version 1.42.0 upstream/1.42.0
Diffstat (limited to 'src/core/dns')
-rw-r--r--src/core/dns/nm-dns-dnsmasq.c34
-rw-r--r--src/core/dns/nm-dns-dnsmasq.h3
-rw-r--r--src/core/dns/nm-dns-manager.c128
-rw-r--r--src/core/dns/nm-dns-manager.h12
-rw-r--r--src/core/dns/nm-dns-plugin.h2
-rw-r--r--src/core/dns/nm-dns-systemd-resolved.c216
-rw-r--r--src/core/dns/nm-dns-systemd-resolved.h2
7 files changed, 243 insertions, 154 deletions
diff --git a/src/core/dns/nm-dns-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c
index cc7c03a2..8ae1bb96 100644
--- a/src/core/dns/nm-dns-dnsmasq.c
+++ b/src/core/dns/nm-dns-dnsmasq.c
@@ -801,13 +801,13 @@ add_dnsmasq_nameserver(NMDnsDnsmasq    *self,
     g_variant_builder_close(servers);
 }
 
-#define IP_ADDR_TO_STRING_BUFLEN (NM_UTILS_INET_ADDRSTRLEN + 1 + IFNAMSIZ)
+#define IP_ADDR_TO_STRING_BUFLEN (NM_INET_ADDRSTRLEN + 1 + IFNAMSIZ)
 
 static const char *
 ip_addr_to_string(int addr_family, gconstpointer addr, const char *iface, char *out_buf)
 {
     int         n_written;
-    char        buf2[NM_UTILS_INET_ADDRSTRLEN];
+    char        buf2[NM_INET_ADDRSTRLEN];
     const char *separator;
 
     nm_assert_addr_family(addr_family);
@@ -815,13 +815,13 @@ ip_addr_to_string(int addr_family, gconstpointer addr, const char *iface, char *
     nm_assert(out_buf);
 
     if (addr_family == AF_INET) {
-        nm_utils_inet_ntop(addr_family, addr, buf2);
+        nm_inet_ntop(addr_family, addr, buf2);
         separator = "@";
     } else {
         if (IN6_IS_ADDR_V4MAPPED(addr))
-            _nm_utils_inet4_ntop(((const struct in6_addr *) addr)->s6_addr32[3], buf2);
+            nm_inet4_ntop(((const struct in6_addr *) addr)->s6_addr32[3], buf2);
         else
-            _nm_utils_inet6_ntop(addr, buf2);
+            nm_inet6_ntop(addr, buf2);
         /* Need to scope link-local addresses with %<zone-id>. Before dnsmasq 2.58,
          * only '@' was supported as delimiter. Since 2.58, '@' and '%' are
          * supported. Due to a bug, since 2.73 only '%' works properly as "server"
@@ -868,22 +868,24 @@ add_global_config(NMDnsDnsmasq            *self,
 static void
 add_ip_config(NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsConfigIPData *ip_data)
 {
-    const char   *iface;
-    const char   *domain;
-    char          ip_addr_to_string_buf[IP_ADDR_TO_STRING_BUFLEN];
-    gconstpointer nameservers;
-    guint         num;
-    guint         i;
-    guint         j;
+    const char        *iface;
+    const char        *domain;
+    char               ip_addr_to_string_buf[IP_ADDR_TO_STRING_BUFLEN];
+    const char *const *strarr;
+    guint              num;
+    guint              i;
+    guint              j;
 
     iface = nm_platform_link_get_name(NM_PLATFORM_GET, ip_data->data->ifindex);
 
-    nameservers = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &num);
+    strarr = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &num);
     for (i = 0; i < num; i++) {
-        gconstpointer addr;
+        NMIPAddr a;
 
-        addr = nm_ip_addr_from_packed_array(ip_data->addr_family, nameservers, i);
-        ip_addr_to_string(ip_data->addr_family, addr, iface, ip_addr_to_string_buf);
+        if (!nm_utils_dnsname_parse_assert(ip_data->addr_family, strarr[i], NULL, &a, NULL))
+            continue;
+
+        ip_addr_to_string(ip_data->addr_family, &a, iface, ip_addr_to_string_buf);
 
         if (!ip_data->domains.has_default_route_explicit && ip_data->domains.has_default_route)
             add_dnsmasq_nameserver(self, servers, ip_addr_to_string_buf, NULL);
diff --git a/src/core/dns/nm-dns-dnsmasq.h b/src/core/dns/nm-dns-dnsmasq.h
index 98761d3d..c2f2a115 100644
--- a/src/core/dns/nm-dns-dnsmasq.h
+++ b/src/core/dns/nm-dns-dnsmasq.h
@@ -10,7 +10,8 @@
 #include "nm-dns-manager.h"
 
 #define NM_TYPE_DNS_DNSMASQ (nm_dns_dnsmasq_get_type())
-#define NM_DNS_DNSMASQ(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasq))
+#define NM_DNS_DNSMASQ(obj) \
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasq))
 #define NM_DNS_DNSMASQ_CLASS(klass) \
     (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqClass))
 #define NM_IS_DNS_DNSMASQ(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_DNSMASQ))
diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c
index edb8ce0f..6ee2e816 100644
--- a/src/core/dns/nm-dns-manager.c
+++ b/src/core/dns/nm-dns-manager.c
@@ -552,29 +552,29 @@ add_dns_domains(GPtrArray            *array,
 static void
 merge_one_l3cd(NMResolvConfData *rc, int addr_family, int ifindex, const NML3ConfigData *l3cd)
 {
-    char               buf[NM_UTILS_INET_ADDRSTRLEN + 50];
+    char               buf[NM_INET_ADDRSTRLEN + 50];
     gboolean           has_trust_ad;
     guint              num_nameservers;
     guint              num;
     guint              i;
-    gconstpointer      nameservers;
-    const char *const *strv;
+    const char *const *strarr;
 
     nm_assert(ifindex == nm_l3_config_data_get_ifindex(l3cd));
 
-    nameservers = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num_nameservers);
+    strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num_nameservers);
     for (i = 0; i < num_nameservers; i++) {
-        const NMIPAddr *addr;
+        NMIPAddr a;
 
-        addr = nm_ip_addr_from_packed_array(addr_family, nameservers, i);
+        if (!nm_utils_dnsname_parse_assert(addr_family, strarr[i], NULL, &a, NULL))
+            continue;
 
         if (addr_family == AF_INET)
-            nm_utils_inet_ntop(addr_family, addr, buf);
-        else if (IN6_IS_ADDR_V4MAPPED(addr))
-            _nm_utils_inet4_ntop(addr->addr6.s6_addr32[3], buf);
+            nm_inet_ntop(addr_family, &a, buf);
+        else if (IN6_IS_ADDR_V4MAPPED(&a))
+            nm_inet4_ntop(a.addr6.s6_addr32[3], buf);
         else {
-            _nm_utils_inet6_ntop(&addr->addr6, buf);
-            if (IN6_IS_ADDR_LINKLOCAL(addr)) {
+            nm_inet6_ntop(&a.addr6, buf);
+            if (IN6_IS_ADDR_LINKLOCAL(&a)) {
                 const char *ifname;
 
                 ifname = nm_platform_link_get_name(NM_PLATFORM_GET, ifindex);
@@ -591,9 +591,9 @@ merge_one_l3cd(NMResolvConfData *rc, int addr_family, int ifindex, const NML3Con
     add_dns_domains(rc->searches, addr_family, l3cd, FALSE, TRUE);
 
     has_trust_ad = FALSE;
-    strv         = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num);
+    strarr       = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num);
     for (i = 0; i < num; i++) {
-        const char *option = strv[i];
+        const char *option = strarr[i];
 
         if (nm_streq(option, NM_SETTING_DNS_OPTION_TRUST_AD)) {
             has_trust_ad = TRUE;
@@ -619,7 +619,7 @@ merge_one_l3cd(NMResolvConfData *rc, int addr_family, int ifindex, const NML3Con
 
         nis_servers = nm_l3_config_data_get_nis_servers(l3cd, &num);
         for (i = 0; i < num; i++)
-            add_string_item(rc->nis_servers, _nm_utils_inet4_ntop(nis_servers[i], buf), TRUE);
+            add_string_item(rc->nis_servers, nm_inet4_ntop(nis_servers[i], buf), TRUE);
 
         if ((nis_domain = nm_l3_config_data_get_nis_domain(l3cd))) {
             /* FIXME: handle multiple domains */
@@ -1201,14 +1201,19 @@ compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[
 
     if (global)
         nm_global_dns_config_update_checksum(global, sum);
-    else {
+
+    if (!global || !nm_global_dns_config_lookup_domain(global, "*")) {
         const CList *head;
 
         /* FIXME(ip-config-checksum): this relies on the fact that an IP
          * configuration without DNS parameters gives a zero checksum. */
         head = _mgr_get_ip_data_lst_head(self);
-        c_list_for_each_entry (ip_data, head, ip_data_lst)
-            nm_ip_config_dns_hash(ip_data->l3cd, sum, ip_data->addr_family);
+        c_list_for_each_entry (ip_data, head, ip_data_lst) {
+            nm_l3_config_data_hash_dns(ip_data->l3cd,
+                                       sum,
+                                       ip_data->addr_family,
+                                       ip_data->ip_config_type);
+        }
     }
 
     nm_utils_checksum_get_digest_len(sum, buffer, HASH_LEN);
@@ -1244,13 +1249,15 @@ merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf)
     }
 
     default_domain = nm_global_dns_config_lookup_domain(global_conf, "*");
-    nm_assert(default_domain);
+    if (!default_domain)
+        return TRUE;
 
     servers = nm_global_dns_domain_get_servers(default_domain);
-    if (servers) {
-        for (i = 0; servers[i]; i++)
-            add_string_item(rc->nameservers, servers[i], TRUE);
-    }
+    if (!servers)
+        return TRUE;
+
+    for (i = 0; servers[i]; i++)
+        add_string_item(rc->nameservers, servers[i], TRUE);
 
     return TRUE;
 }
@@ -1258,19 +1265,21 @@ merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf)
 static const char *
 get_nameserver_list(int addr_family, const NML3ConfigData *l3cd, NMStrBuf *tmp_strbuf)
 {
-    char          buf[NM_UTILS_INET_ADDRSTRLEN];
-    guint         num;
-    guint         i;
-    gconstpointer nameservers;
+    char               buf[NM_INET_ADDRSTRLEN];
+    guint              num;
+    guint              i;
+    const char *const *strarr;
 
     nm_str_buf_reset(tmp_strbuf);
 
-    nameservers = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num);
+    strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num);
     for (i = 0; i < num; i++) {
-        const NMIPAddr *addr;
+        NMIPAddr a;
+
+        if (!nm_utils_dnsname_parse_assert(addr_family, strarr[i], NULL, &a, NULL))
+            continue;
 
-        addr = nm_ip_addr_from_packed_array(addr_family, nameservers, i);
-        nm_utils_inet_ntop(addr_family, addr->addr_ptr, buf);
+        nm_inet_ntop(addr_family, &a, buf);
         if (i > 0)
             nm_str_buf_append_c(tmp_strbuf, ' ');
         nm_str_buf_append(tmp_strbuf, buf);
@@ -1311,7 +1320,8 @@ _collect_resolv_conf_data(NMDnsManager      *self,
 
     if (global_config)
         merge_global_dns_config(&rc, global_config);
-    else {
+
+    if (!global_config || !nm_global_dns_config_lookup_domain(global_config, "*")) {
         nm_auto_str_buf NMStrBuf tmp_strbuf = NM_STR_BUF_INIT(0, FALSE);
         int                      first_prio = 0;
         const NMDnsConfigIPData *ip_data;
@@ -2090,8 +2100,7 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s
 
     /* Certain hostnames we don't want to include in resolv.conf 'searches' */
     if (hostname && nm_utils_is_specific_hostname(hostname)
-        && !g_str_has_suffix(hostname, ".in-addr.arpa")
-        && !nm_utils_ipaddr_is_valid(AF_UNSPEC, hostname)) {
+        && !g_str_has_suffix(hostname, ".in-addr.arpa") && !nm_inet_is_valid(AF_UNSPEC, hostname)) {
         domain = strchr(hostname, '.');
         if (domain) {
             domain++;
@@ -2555,14 +2564,12 @@ config_changed_cb(NMConfig           *config,
     }
 }
 
-static GVariant *
-_get_global_config_variant(NMGlobalDnsConfig *global)
+static void
+_get_global_config_variant(GVariantBuilder *builder, NMGlobalDnsConfig *global)
 {
     NMGlobalDnsDomain *domain;
-    GVariantBuilder    builder;
     guint              i, num;
 
-    g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}"));
     num = nm_global_dns_config_get_num_domains(global);
     for (i = 0; i < num; i++) {
         GVariantBuilder    conf_builder;
@@ -2598,10 +2605,8 @@ _get_global_config_variant(NMGlobalDnsConfig *global)
                               "priority",
                               g_variant_new_int32(NM_DNS_PRIORITY_DEFAULT_NORMAL));
 
-        g_variant_builder_add(&builder, "a{sv}", &conf_builder);
+        g_variant_builder_add(builder, "a{sv}", &conf_builder);
     }
-
-    return g_variant_ref_sink(g_variant_builder_end(&builder));
 }
 
 static GVariant *
@@ -2618,28 +2623,25 @@ _get_config_variant(NMDnsManager *self)
     if (priv->config_variant)
         return priv->config_variant;
 
-    global_config = nm_config_data_get_global_dns_config(nm_config_get_data(priv->config));
-    if (global_config) {
-        priv->config_variant = _get_global_config_variant(global_config);
-        _LOGT("current configuration: %s", (str = g_variant_print(priv->config_variant, TRUE)));
-        return priv->config_variant;
-    }
-
     g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}"));
 
+    global_config = nm_config_data_get_global_dns_config(nm_config_get_data(priv->config));
+    if (global_config)
+        _get_global_config_variant(&builder, global_config);
+
     head = _mgr_get_ip_data_lst_head(self);
     c_list_for_each_entry (ip_data, head, ip_data_lst) {
-        GVariantBuilder entry_builder;
-        GVariantBuilder strv_builder;
-        guint           num;
-        guint           num_domains;
-        guint           num_searches;
-        guint           i;
-        char            buf[NM_UTILS_INET_ADDRSTRLEN];
-        const char     *ifname;
-        gconstpointer   nameservers;
-
-        nameservers = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &num);
+        GVariantBuilder    entry_builder;
+        GVariantBuilder    strv_builder;
+        guint              num;
+        guint              num_domains;
+        guint              num_searches;
+        guint              i;
+        char               buf[NM_INET_ADDRSTRLEN];
+        const char        *ifname;
+        const char *const *strarr;
+
+        strarr = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &num);
         if (num == 0)
             continue;
 
@@ -2647,12 +2649,12 @@ _get_config_variant(NMDnsManager *self)
 
         g_variant_builder_init(&strv_builder, G_VARIANT_TYPE("as"));
         for (i = 0; i < num; i++) {
-            const NMIPAddr *addr;
+            NMIPAddr a;
+
+            if (!nm_utils_dnsname_parse_assert(ip_data->addr_family, strarr[i], NULL, &a, NULL))
+                continue;
 
-            addr = nm_ip_addr_from_packed_array(ip_data->addr_family, nameservers, i);
-            g_variant_builder_add(&strv_builder,
-                                  "s",
-                                  nm_utils_inet_ntop(ip_data->addr_family, addr, buf));
+            g_variant_builder_add(&strv_builder, "s", nm_inet_ntop(ip_data->addr_family, &a, buf));
         }
         g_variant_builder_add(&entry_builder,
                               "{sv}",
diff --git a/src/core/dns/nm-dns-manager.h b/src/core/dns/nm-dns-manager.h
index 210f9f6c..42f9dec5 100644
--- a/src/core/dns/nm-dns-manager.h
+++ b/src/core/dns/nm-dns-manager.h
@@ -12,16 +12,6 @@
 #include "nm-setting-connection.h"
 #include "nm-dns-plugin.h"
 
-typedef enum {
-    NM_DNS_IP_CONFIG_TYPE_REMOVED = -1,
-
-    NM_DNS_IP_CONFIG_TYPE_DEFAULT = 0,
-    NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE,
-    NM_DNS_IP_CONFIG_TYPE_VPN,
-} NMDnsIPConfigType;
-
-/*****************************************************************************/
-
 struct _NMDnsConfigData;
 struct _NMDnsManager;
 
@@ -71,7 +61,7 @@ typedef struct _NMDnsConfigData {
 /*****************************************************************************/
 
 #define NM_TYPE_DNS_MANAGER (nm_dns_manager_get_type())
-#define NM_DNS_MANAGER(o)   (G_TYPE_CHECK_INSTANCE_CAST((o), NM_TYPE_DNS_MANAGER, NMDnsManager))
+#define NM_DNS_MANAGER(o)   (_NM_G_TYPE_CHECK_INSTANCE_CAST((o), NM_TYPE_DNS_MANAGER, NMDnsManager))
 #define NM_DNS_MANAGER_CLASS(k) \
     (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_DNS_MANAGER, NMDnsManagerClass))
 #define NM_IS_DNS_MANAGER(o)       (G_TYPE_CHECK_INSTANCE_TYPE((o), NM_TYPE_DNS_MANAGER))
diff --git a/src/core/dns/nm-dns-plugin.h b/src/core/dns/nm-dns-plugin.h
index 24d6083b..364f15ce 100644
--- a/src/core/dns/nm-dns-plugin.h
+++ b/src/core/dns/nm-dns-plugin.h
@@ -11,7 +11,7 @@
 #include "nm-config-data.h"
 
 #define NM_TYPE_DNS_PLUGIN (nm_dns_plugin_get_type())
-#define NM_DNS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_PLUGIN, NMDnsPlugin))
+#define NM_DNS_PLUGIN(obj) (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_PLUGIN, NMDnsPlugin))
 #define NM_DNS_PLUGIN_CLASS(klass) \
     (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass))
 #define NM_IS_DNS_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_PLUGIN))
diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c
index e0b94647..24c7c774 100644
--- a/src/core/dns/nm-dns-systemd-resolved.c
+++ b/src/core/dns/nm-dns-systemd-resolved.c
@@ -36,6 +36,7 @@
 /* define a variable, so that we can compare the operation with pointer equality. */
 static const char *const DBUS_OP_SET_LINK_DEFAULT_ROUTE = "SetLinkDefaultRoute";
 static const char *const DBUS_OP_SET_LINK_DNS_OVER_TLS  = "SetLinkDNSOverTLS";
+static const char *const DBUS_OP_SET_LINK_DNS_EX        = "SetLinkDNSEx";
 
 /*****************************************************************************/
 
@@ -90,12 +91,11 @@ typedef struct {
     bool             dbus_initied : 1;
     bool             send_updates_waiting : 1;
     bool             update_pending : 1;
-    /* These two variables ensure that the log is not spammed with
-     * API (not) supported messages.
-     * They can be removed when no distro uses systemd-resolved < v240 anymore
-     */
-    NMTernary has_link_default_route : 3;
-    NMTernary has_link_dns_over_tls : 3;
+
+    /* Detect support for the respective D-Bus API. */
+    NMTernary has_set_link_default_route : 3;
+    NMTernary has_set_link_dns_over_tls : 3;
+    NMTernary has_set_link_dns_ex : 3;
 } NMDnsSystemdResolvedPrivate;
 
 struct _NMDnsSystemdResolved {
@@ -148,6 +148,8 @@ static void _resolve_complete_error(NMDnsSystemdResolvedResolveHandle *handle, G
 
 static void _resolve_start(NMDnsSystemdResolved *self, NMDnsSystemdResolvedResolveHandle *handle);
 
+static void send_updates(NMDnsSystemdResolved *self);
+
 /*****************************************************************************/
 
 static gboolean
@@ -276,6 +278,7 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
     NMLogLevel                   log_level;
     const char                  *operation;
     int                          ifindex;
+    gboolean                     reconfigure = FALSE;
 
     request_item = user_data;
     self         = request_item->self;
@@ -290,30 +293,52 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
         goto out_dec_pending;
 
     if (v) {
-        if (operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
-            && priv->has_link_default_route == NM_TERNARY_DEFAULT) {
-            priv->has_link_default_route = NM_TERNARY_TRUE;
-            _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API supported");
-        }
-        if (operation == DBUS_OP_SET_LINK_DNS_OVER_TLS
-            && priv->has_link_dns_over_tls == NM_TERNARY_DEFAULT) {
-            priv->has_link_dns_over_tls = NM_TERNARY_TRUE;
-            _LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API supported");
+        if (operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) {
+            if (priv->has_set_link_default_route == NM_TERNARY_DEFAULT) {
+                priv->has_set_link_default_route = NM_TERNARY_TRUE;
+                _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API supported");
+            }
+        } else if (operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) {
+            if (priv->has_set_link_dns_over_tls == NM_TERNARY_DEFAULT) {
+                priv->has_set_link_dns_over_tls = NM_TERNARY_TRUE;
+                _LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API supported");
+            }
+        } else if (operation == DBUS_OP_SET_LINK_DNS_EX) {
+            if (priv->has_set_link_dns_ex == NM_TERNARY_DEFAULT) {
+                priv->has_set_link_dns_ex = NM_TERNARY_TRUE;
+                _LOGD("systemd-resolved support for SetLinkDNSEx(): API supported");
+            }
         }
         priv->send_updates_warn_ratelimited = FALSE;
         goto out_dec_pending;
     }
 
     if (nm_g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
-        if (priv->has_link_default_route == NM_TERNARY_DEFAULT
-            && operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) {
-            priv->has_link_default_route = NM_TERNARY_FALSE;
-            _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API not supported");
-        }
-        if (priv->has_link_dns_over_tls == NM_TERNARY_DEFAULT
-            && operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) {
-            priv->has_link_dns_over_tls = NM_TERNARY_FALSE;
-            _LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API not supported");
+        if (operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) {
+            if (priv->has_set_link_default_route == NM_TERNARY_DEFAULT) {
+                priv->has_set_link_default_route = NM_TERNARY_FALSE;
+                _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API not supported");
+            }
+        } else if (operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) {
+            if (priv->has_set_link_dns_over_tls == NM_TERNARY_DEFAULT) {
+                priv->has_set_link_dns_over_tls = NM_TERNARY_FALSE;
+                _LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API not supported");
+            }
+        } else if (operation == DBUS_OP_SET_LINK_DNS_EX) {
+            if (priv->has_set_link_dns_ex == NM_TERNARY_DEFAULT) {
+                priv->has_set_link_dns_ex = NM_TERNARY_FALSE;
+                _LOGD("systemd-resolved support for SetLinkDNSEx(): API not supported");
+
+                _LOGW("systemd-resolved does not support SetLinkDNSEx API (v246). "
+                      "Cannot set DoT server name (SNI)");
+
+                /* We need to reconfigure with the SetLinkDNS fallback.
+                 *
+                 * In the other cases above, there is no need to reconfigure anything.
+                 * We won't retry SetLinkDefaultRoute/SetLinkDNSOverTLS anymore, but there
+                 * is nothing else we can do about that. */
+                reconfigure = TRUE;
+            }
         }
         goto out_dec_pending;
     }
@@ -335,21 +360,29 @@ out_dec_pending:
          * we must wrap up fast, and not hang an undefined amount time. */
         g_object_unref(self);
     }
+
+    if (reconfigure) {
+        priv->send_updates_waiting = TRUE;
+        send_updates(self);
+    }
 }
 
 static gboolean
 update_add_ip_config(NMDnsSystemdResolved    *self,
+                     const NMDnsConfigIPData *ip_data,
                      GVariantBuilder         *dns,
+                     GVariantBuilder         *dns_ex,
                      GVariantBuilder         *domains,
-                     const NMDnsConfigIPData *ip_data)
+                     gboolean                *out_require_dns_ex)
 {
-    gsize         addr_size;
-    guint         n;
-    guint         i;
-    gboolean      is_routing;
-    const char   *domain;
-    gboolean      has_config = FALSE;
-    gconstpointer nameservers;
+    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    gsize                        addr_size;
+    guint                        n;
+    guint                        i;
+    gboolean                     is_routing;
+    const char                  *domain;
+    gboolean                     has_config = FALSE;
+    const char *const           *strarr;
 
     addr_size = nm_utils_addr_family_to_size(ip_data->addr_family);
 
@@ -361,29 +394,52 @@ update_add_ip_config(NMDnsSystemdResolved    *self,
         return FALSE;
     }
 
-    nameservers = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &n);
+    strarr = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &n);
     for (i = 0; i < n; i++) {
-        g_variant_builder_open(dns, G_VARIANT_TYPE("(iay)"));
-        g_variant_builder_add(dns, "i", ip_data->addr_family);
-        g_variant_builder_add_value(
-            dns,
-            nm_g_variant_new_ay(nm_ip_addr_from_packed_array(ip_data->addr_family, nameservers, i),
-                                addr_size));
-        g_variant_builder_close(dns);
-        has_config = TRUE;
-    }
+        const char *server_name;
+        NMIPAddr    a;
+
+        if (!nm_utils_dnsname_parse_assert(ip_data->addr_family, strarr[i], NULL, &a, &server_name))
+            continue;
 
-    if (!ip_data->domains.has_default_route_explicit
-        && ip_data->domains.has_default_route_exclusive) {
-        g_variant_builder_add(domains, "(sb)", ".", TRUE);
+        if (server_name) {
+            NM_SET_OUT(out_require_dns_ex, TRUE);
+            if (priv->has_set_link_dns_ex == FALSE) {
+                /* The caller won't care about this result anymore. We can skip setting it. */
+                dns = NULL;
+            }
+        }
+
+        if (dns_ex) {
+            g_variant_builder_open(dns_ex, G_VARIANT_TYPE("(iayqs)"));
+            g_variant_builder_add(dns_ex, "i", ip_data->addr_family);
+            g_variant_builder_add_value(dns_ex, nm_g_variant_new_ay((gconstpointer) &a, addr_size));
+            g_variant_builder_add(dns_ex, "q", 0);
+            g_variant_builder_add(dns_ex, "s", server_name ?: "");
+            g_variant_builder_close(dns_ex);
+        }
+        if (dns) {
+            g_variant_builder_open(dns, G_VARIANT_TYPE("(iay)"));
+            g_variant_builder_add(dns, "i", ip_data->addr_family);
+            g_variant_builder_add_value(dns, nm_g_variant_new_ay((gconstpointer) &a, addr_size));
+            g_variant_builder_close(dns);
+        }
         has_config = TRUE;
     }
-    if (ip_data->domains.search) {
-        for (i = 0; ip_data->domains.search[i]; i++) {
-            domain = nm_utils_parse_dns_domain(ip_data->domains.search[i], &is_routing);
-            g_variant_builder_add(domains, "(sb)", domain[0] ? domain : ".", is_routing);
+
+    if (!has_config || domains) {
+        if (!ip_data->domains.has_default_route_explicit
+            && ip_data->domains.has_default_route_exclusive) {
+            g_variant_builder_add(domains, "(sb)", ".", TRUE);
             has_config = TRUE;
         }
+        if (ip_data->domains.search) {
+            for (i = 0; ip_data->domains.search[i]; i++) {
+                domain = nm_utils_parse_dns_domain(ip_data->domains.search[i], &is_routing);
+                g_variant_builder_add(domains, "(sb)", domain[0] ? domain : ".", is_routing);
+                has_config = TRUE;
+            }
+        }
     }
 
     return has_config;
@@ -406,7 +462,9 @@ free_pending_updates(NMDnsSystemdResolved *self)
 static gboolean
 prepare_one_interface(NMDnsSystemdResolved *self, const InterfaceConfig *ic)
 {
+    NMDnsSystemdResolvedPrivate  *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
     GVariantBuilder               dns;
+    GVariantBuilder               dns_ex;
     GVariantBuilder               domains;
     NMSettingConnectionMdns       mdns              = NM_SETTING_CONNECTION_MDNS_DEFAULT;
     NMSettingConnectionLlmnr      llmnr             = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
@@ -417,6 +475,9 @@ prepare_one_interface(NMDnsSystemdResolved *self, const InterfaceConfig *ic)
     gboolean                      has_config        = FALSE;
     gboolean                      has_default_route = FALSE;
     guint                         i;
+    gboolean                      require_dns_ex = FALSE;
+
+    nm_assert(ic->ifindex != NM_LOOPBACK_IFINDEX);
 
     g_variant_builder_init(&dns, G_VARIANT_TYPE("(ia(iay))"));
     g_variant_builder_add(&dns, "i", ic->ifindex);
@@ -430,7 +491,7 @@ prepare_one_interface(NMDnsSystemdResolved *self, const InterfaceConfig *ic)
         for (i = 0; i < ic->ip_data_list->len; i++) {
             const NMDnsConfigIPData *ip_data = ic->ip_data_list->pdata[i];
 
-            if (update_add_ip_config(self, &dns, &domains, ip_data))
+            if (update_add_ip_config(self, ip_data, &dns, NULL, &domains, &require_dns_ex))
                 has_config = TRUE;
 
             if (ip_data->domains.has_default_route)
@@ -448,6 +509,23 @@ prepare_one_interface(NMDnsSystemdResolved *self, const InterfaceConfig *ic)
     g_variant_builder_close(&dns);
     g_variant_builder_close(&domains);
 
+    if (!require_dns_ex) {
+        /* No need to use the new API. SetLinkDNS() is sufficient. */
+    } else if (!priv->has_set_link_dns_ex) {
+        /* API to set server name is not supported. Nothing we can do. */
+        require_dns_ex = FALSE;
+    } else {
+        g_variant_builder_init(&dns_ex, G_VARIANT_TYPE("(ia(iayqs))"));
+        g_variant_builder_add(&dns_ex, "i", ic->ifindex);
+        g_variant_builder_open(&dns_ex, G_VARIANT_TYPE("a(iayqs)"));
+        for (i = 0; i < ic->ip_data_list->len; i++) {
+            const NMDnsConfigIPData *ip_data = ic->ip_data_list->pdata[i];
+
+            update_add_ip_config(self, ip_data, NULL, &dns_ex, NULL, NULL);
+        }
+        g_variant_builder_close(&dns_ex);
+    }
+
     switch (mdns) {
     case NM_SETTING_CONNECTION_MDNS_NO:
         mdns_arg = "no";
@@ -513,7 +591,14 @@ prepare_one_interface(NMDnsSystemdResolved *self, const InterfaceConfig *ic)
                          "SetLinkLLMNR",
                          ic->ifindex,
                          g_variant_new("(is)", ic->ifindex, llmnr_arg ?: ""));
-    _request_item_append(self, "SetLinkDNS", ic->ifindex, g_variant_builder_end(&dns));
+    if (require_dns_ex) {
+        _request_item_append(self,
+                             DBUS_OP_SET_LINK_DNS_EX,
+                             ic->ifindex,
+                             g_variant_builder_end(&dns_ex));
+        g_variant_builder_clear(&dns);
+    } else
+        _request_item_append(self, "SetLinkDNS", ic->ifindex, g_variant_builder_end(&dns));
     _request_item_append(self,
                          DBUS_OP_SET_LINK_DNS_OVER_TLS,
                          ic->ifindex,
@@ -631,9 +716,9 @@ send_updates(NMDnsSystemdResolved *self)
         gs_free char *ss = NULL;
 
         if ((request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
-             && priv->has_link_default_route == NM_TERNARY_FALSE)
+             && priv->has_set_link_default_route == NM_TERNARY_FALSE)
             || (request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS
-                && priv->has_link_dns_over_tls == NM_TERNARY_FALSE)) {
+                && priv->has_set_link_dns_over_tls == NM_TERNARY_FALSE)) {
             /* The "SetLinkDefaultRoute" API is only supported since v240.
              * The "SetLinkDNSOverTLS" API is only supported since v239.
              * We detected whether they are supported, and skip the calls. There
@@ -708,6 +793,13 @@ update(NMDnsPlugin             *plugin,
 
         nm_assert(ifindex == nm_l3_config_data_get_ifindex(ip_data->l3cd));
 
+        if (ifindex == NM_LOOPBACK_IFINDEX) {
+            /* systemd-resolved API is per-link, and loopback is not supported.
+             * Unclear what to do about DNS configuration on loopback. Just skip
+             * it here. */
+            continue;
+        }
+
         ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(ifindex));
         if (!ic) {
             ic  = g_slice_new(InterfaceConfig);
@@ -759,7 +851,7 @@ update(NMDnsPlugin             *plugin,
     if (dirty_array) {
         g_array_sort_with_data(dirty_array, nm_cmp_int2ptr_p_with_data, NULL);
         for (i = 0; i < dirty_array->len; i++) {
-            int             ifindex = g_array_index(dirty_array, int, i);
+            int             ifindex = nm_g_array_index(dirty_array, int, i);
             InterfaceConfig ic;
 
             _LOGT("clear previously configured ifindex %d", ifindex);
@@ -798,8 +890,9 @@ name_owner_changed(NMDnsSystemdResolved *self, const char *owner)
         priv->try_start_blocked    = FALSE;
         priv->send_updates_waiting = TRUE;
     } else {
-        priv->has_link_default_route = NM_TERNARY_DEFAULT;
-        priv->has_link_dns_over_tls  = NM_TERNARY_DEFAULT;
+        priv->has_set_link_default_route = NM_TERNARY_DEFAULT;
+        priv->has_set_link_dns_over_tls  = NM_TERNARY_DEFAULT;
+        priv->has_set_link_dns_ex        = NM_TERNARY_DEFAULT;
     }
 
     send_updates(self);
@@ -968,7 +1061,7 @@ _resolve_handle_call_cb(GObject *source, GAsyncResult *result, gpointer user_dat
     g_variant_iter_free(v_names_iter);
 
     _resolve_complete(handle,
-                      &g_array_index(v_names, NMDnsSystemdResolvedAddressResult, 0),
+                      nm_g_array_first_p(v_names, NMDnsSystemdResolvedAddressResult),
                       v_names->len,
                       v_flags,
                       NULL);
@@ -1066,7 +1159,7 @@ nm_dns_systemd_resolved_resolve_address(NMDnsSystemdResolved
 {
     NMDnsSystemdResolvedPrivate       *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
     NMDnsSystemdResolvedResolveHandle *handle;
-    char                               addr_str[NM_UTILS_INET_ADDRSTRLEN];
+    char                               addr_str[NM_INET_ADDRSTRLEN];
 
     g_return_val_if_fail(NM_IS_DNS_SYSTEMD_RESOLVED(self), NULL);
     nm_assert_addr_family(addr_family);
@@ -1092,7 +1185,7 @@ nm_dns_systemd_resolved_resolve_address(NMDnsSystemdResolved
     _LOG2T(handle,
            "resolve-address(ifindex=%d, %s, flags=%" G_GINT64_MODIFIER "x): new request",
            handle->r_address.ifindex,
-           nm_utils_inet_ntop(handle->r_address.addr_family, &handle->r_address.addr, addr_str),
+           nm_inet_ntop(handle->r_address.addr_family, &handle->r_address.addr, addr_str),
            handle->r_address.flags);
 
     _resolve_start(self, handle);
@@ -1158,8 +1251,9 @@ nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self)
 {
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
 
-    priv->has_link_default_route = NM_TERNARY_DEFAULT;
-    priv->has_link_dns_over_tls  = NM_TERNARY_DEFAULT;
+    priv->has_set_link_default_route = NM_TERNARY_DEFAULT;
+    priv->has_set_link_dns_over_tls  = NM_TERNARY_DEFAULT;
+    priv->has_set_link_dns_ex        = NM_TERNARY_DEFAULT;
 
     c_list_init(&priv->request_queue_lst_head);
     c_list_init(&priv->handle_lst_head);
diff --git a/src/core/dns/nm-dns-systemd-resolved.h b/src/core/dns/nm-dns-systemd-resolved.h
index 02bc524c..0563a70f 100644
--- a/src/core/dns/nm-dns-systemd-resolved.h
+++ b/src/core/dns/nm-dns-systemd-resolved.h
@@ -12,7 +12,7 @@
 
 #define NM_TYPE_DNS_SYSTEMD_RESOLVED (nm_dns_systemd_resolved_get_type())
 #define NM_DNS_SYSTEMD_RESOLVED(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_SYSTEMD_RESOLVED, NMDnsSystemdResolved))
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_SYSTEMD_RESOLVED, NMDnsSystemdResolved))
 #define NM_DNS_SYSTEMD_RESOLVED_CLASS(klass) \
     (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_SYSTEMD_RESOLVED, NMDnsSystemdResolvedClass))
 #define NM_IS_DNS_SYSTEMD_RESOLVED(obj) \