summary refs log tree commit diff
path: root/src/dns
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-12-06 21:57:59 +0100
committerMichael Biebl <biebl@debian.org>2020-12-06 21:57:59 +0100
commit65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (patch)
tree180827692f002e5f1dad6a0fa8ca489e6bb6438f /src/dns
parentf2ddac4cbc895837ddcc55015fae112f9859cd0a (diff)
New upstream version 1.28.0 upstream/1.28.0
Diffstat (limited to 'src/dns')
-rw-r--r--src/dns/nm-dns-dnsmasq.c15
-rw-r--r--src/dns/nm-dns-manager.c238
-rw-r--r--src/dns/nm-dns-manager.h26
-rw-r--r--src/dns/nm-dns-systemd-resolved.c234
4 files changed, 388 insertions, 125 deletions
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index 451e4846..97f1dfab 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -820,11 +820,18 @@ add_ip_config(NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsIPConfigD
     for (i = 0; i < num; i++) {
         addr = nm_ip_config_get_nameserver(ip_config, i);
         ip_addr_to_string(addr_family, addr, iface, ip_addr_to_string_buf);
-        for (j = 0; ip_data->domains.search[j]; j++) {
-            domain = nm_utils_parse_dns_domain(ip_data->domains.search[j], NULL);
-            add_dnsmasq_nameserver(self, servers, ip_addr_to_string_buf, domain[0] ? domain : NULL);
-        }
 
+        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);
+        if (ip_data->domains.search) {
+            for (j = 0; ip_data->domains.search[j]; j++) {
+                domain = nm_utils_parse_dns_domain(ip_data->domains.search[j], NULL);
+                add_dnsmasq_nameserver(self,
+                                       servers,
+                                       ip_addr_to_string_buf,
+                                       domain[0] ? domain : NULL);
+            }
+        }
         if (ip_data->domains.reverse) {
             for (j = 0; ip_data->domains.reverse[j]; j++) {
                 add_dnsmasq_nameserver(self,
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 635e94f6..3fc9a396 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -222,6 +222,26 @@ _ASSERT_ip_config_data(const NMDnsIPConfigData *ip_data)
     nm_assert(NM_IS_IP_CONFIG(ip_data->ip_config));
     nm_assert(c_list_contains(&ip_data->data->data_lst_head, &ip_data->data_lst));
     nm_assert(ip_data->data->ifindex == nm_ip_config_get_ifindex(ip_data->ip_config));
+#if NM_MORE_ASSERTS > 5
+    {
+        gboolean has_default = FALSE;
+        gsize    i;
+
+        for (i = 0; ip_data->domains.search && ip_data->domains.search; i++) {
+            const char *d = ip_data->domains.search[i];
+
+            d = nm_utils_parse_dns_domain(d, NULL);
+            nm_assert(d);
+            if (d[0] == '\0')
+                has_default = TRUE;
+        }
+        nm_assert(has_default == ip_data->domains.has_default_route_explicit);
+        if (ip_data->domains.has_default_route_explicit)
+            nm_assert(ip_data->domains.has_default_route_exclusive);
+        if (ip_data->domains.has_default_route_exclusive)
+            nm_assert(ip_data->domains.has_default_route);
+    }
+#endif
 }
 
 static NMDnsIPConfigData *
@@ -233,10 +253,12 @@ _ip_config_data_new(NMDnsConfigData *data, NMIPConfig *ip_config, NMDnsIPConfigT
     nm_assert(NM_IS_IP_CONFIG(ip_config));
     nm_assert(ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED);
 
-    ip_data                 = g_slice_new0(NMDnsIPConfigData);
-    ip_data->data           = data;
-    ip_data->ip_config      = g_object_ref(ip_config);
-    ip_data->ip_config_type = ip_config_type;
+    ip_data  = g_slice_new(NMDnsIPConfigData);
+    *ip_data = (NMDnsIPConfigData){
+        .data           = data,
+        .ip_config      = g_object_ref(ip_config),
+        .ip_config_type = ip_config_type,
+    };
     c_list_link_tail(&data->data_lst_head, &ip_data->data_lst);
     c_list_link_tail(&NM_DNS_MANAGER_GET_PRIVATE(data->self)->ip_config_lst_head,
                      &ip_data->ip_config_lst);
@@ -267,7 +289,7 @@ _ip_config_data_free(NMDnsIPConfigData *ip_data)
                                          ip_data);
 
     g_object_unref(ip_data->ip_config);
-    g_slice_free(NMDnsIPConfigData, ip_data);
+    nm_g_slice_free(ip_data);
 }
 
 static NMDnsIPConfigData *
@@ -292,7 +314,7 @@ _config_data_free(NMDnsConfigData *data)
     _ASSERT_config_data(data);
 
     nm_assert(c_list_is_empty(&data->data_lst_head));
-    g_slice_free(NMDnsConfigData, data);
+    nm_g_slice_free(data);
 }
 
 static int
@@ -1284,6 +1306,19 @@ get_ip_rdns_domains(NMIPConfig *ip_config)
     return _nm_utils_strv_cleanup(strv, FALSE, FALSE, TRUE);
 }
 
+static gboolean
+domain_ht_get_priority(GHashTable *ht, const char *domain, int *out_priority)
+{
+    gpointer ptr;
+
+    if (!ht || !g_hash_table_lookup_extended(ht, domain, NULL, &ptr)) {
+        *out_priority = 0;
+        return FALSE;
+    }
+    *out_priority = GPOINTER_TO_INT(ptr);
+    return TRUE;
+}
+
 /* Check if the domain is shadowed by a parent domain with more negative priority */
 static gboolean
 domain_is_shadowed(GHashTable * ht,
@@ -1300,21 +1335,25 @@ domain_is_shadowed(GHashTable * ht,
 
     nm_assert(!g_hash_table_contains(ht, domain));
 
-    parent_priority = GPOINTER_TO_INT(g_hash_table_lookup(ht, ""));
-    if (parent_priority < 0 && parent_priority < priority) {
-        *out_parent          = "";
-        *out_parent_priority = parent_priority;
-        return TRUE;
+    if (domain_ht_get_priority(ht, "", &parent_priority)) {
+        nm_assert(parent_priority <= priority);
+        if (parent_priority < 0 && parent_priority < priority) {
+            *out_parent          = "";
+            *out_parent_priority = parent_priority;
+            return TRUE;
+        }
     }
 
     parent = strchr(domain, '.');
     while (parent && parent[1]) {
         parent++;
-        parent_priority = GPOINTER_TO_INT(g_hash_table_lookup(ht, parent));
-        if (parent_priority < 0 && parent_priority < priority) {
-            *out_parent          = parent;
-            *out_parent_priority = parent_priority;
-            return TRUE;
+        if (domain_ht_get_priority(ht, parent, &parent_priority)) {
+            nm_assert(parent_priority <= priority);
+            if (parent_priority < 0 && parent_priority < priority) {
+                *out_parent          = parent;
+                *out_parent_priority = parent_priority;
+                return TRUE;
+            }
         }
         parent = strchr(parent, '.');
     }
@@ -1329,8 +1368,22 @@ rebuild_domain_lists(NMDnsManager *self)
     gs_unref_hashtable GHashTable *ht               = NULL;
     gs_unref_hashtable GHashTable *wildcard_entries = NULL;
     CList *                        head;
+    int                            prev_priority = G_MININT;
 
     head = _ip_config_lst_head(self);
+
+#if NM_MORE_ASSERTS
+    /* we call clear_domain_lists() at the end of update. We
+     * don't expect any domain settings here. */
+    c_list_for_each_entry (ip_data, head, ip_config_lst) {
+        nm_assert(!ip_data->domains.search);
+        nm_assert(!ip_data->domains.reverse);
+        nm_assert(!ip_data->domains.has_default_route_explicit);
+        nm_assert(!ip_data->domains.has_default_route_exclusive);
+        nm_assert(!ip_data->domains.has_default_route);
+    }
+#endif
+
     c_list_for_each_entry (ip_data, head, ip_config_lst) {
         NMIPConfig *ip_config    = ip_data->ip_config;
         gboolean    add_wildcard = FALSE;
@@ -1368,8 +1421,11 @@ rebuild_domain_lists(NMDnsManager *self)
         guint        n_domains;
         guint        num_dom1;
         guint        num_dom2;
-        guint        cap_dom;
+        guint        n_domains_allocated;
         guint        i;
+        gboolean     has_default_route_maybe    = FALSE;
+        gboolean     has_default_route_explicit = FALSE;
+        gboolean     has_default_route_auto     = FALSE;
 
         if (!nm_ip_config_get_num_nameservers(ip_config))
             continue;
@@ -1378,15 +1434,10 @@ rebuild_domain_lists(NMDnsManager *self)
         n_domains  = nm_ip_config_get_num_domains(ip_config);
 
         priority = nm_ip_config_get_dns_priority(ip_config);
-        nm_assert(priority != 0);
-
-        cap_dom = 2u + NM_MAX(n_domains, n_searches);
-
-        g_free(ip_data->domains.search);
-        domains                 = g_new(const char *, cap_dom);
-        ip_data->domains.search = domains;
 
-        num_dom1 = 0;
+        nm_assert(priority != 0);
+        nm_assert(prev_priority <= priority);
+        prev_priority = priority;
 
         /* Add wildcard lookup domain to connections with the default route.
          * If there is no default route, add the wildcard domain to all non-VPN
@@ -1398,12 +1449,17 @@ rebuild_domain_lists(NMDnsManager *self)
              * whether it is suitable for certain operations (like having an automatically
              * added "~" domain). */
             if (g_hash_table_contains(wildcard_entries, ip_data))
-                domains[num_dom1++] = "~";
+                has_default_route_maybe = TRUE;
         } else {
             if (ip_data->ip_config_type != NM_DNS_IP_CONFIG_TYPE_VPN)
-                domains[num_dom1++] = "~";
+                has_default_route_maybe = TRUE;
         }
 
+        n_domains_allocated = (n_searches > 0 ? n_searches : n_domains) + 1u;
+        domains             = g_new(const char *, n_domains_allocated);
+
+        num_dom1 = 0;
+
         /* searches are preferred over domains */
         if (n_searches > 0) {
             for (i = 0; i < n_searches; i++)
@@ -1413,32 +1469,55 @@ rebuild_domain_lists(NMDnsManager *self)
                 domains[num_dom1++] = nm_ip_config_get_domain(ip_config, i);
         }
 
-        nm_assert(num_dom1 < cap_dom);
+        nm_assert(num_dom1 < n_domains_allocated);
 
         num_dom2 = 0;
-        for (i = 0; i < num_dom1; i++) {
+        for (i = 0; TRUE; i++) {
+            const char *domain_full;
             const char *domain_clean;
             const char *parent;
             int         old_priority;
             int         parent_priority;
-
-            domain_clean = nm_utils_parse_dns_domain(domains[i], NULL);
+            gboolean    check_default_route;
+
+            if (i < num_dom1) {
+                check_default_route = FALSE;
+                domain_full         = domains[i];
+                domain_clean        = nm_utils_parse_dns_domain(domains[i], NULL);
+            } else if (i == num_dom1) {
+                if (!has_default_route_maybe)
+                    continue;
+                if (has_default_route_explicit)
+                    continue;
+                check_default_route = TRUE;
+                domain_full         = "~";
+                domain_clean        = "";
+            } else
+                break;
 
             /* Remove domains with lower priority */
-            old_priority = GPOINTER_TO_INT(nm_g_hash_table_lookup(ht, domain_clean));
-            if (old_priority != 0) {
+            if (domain_ht_get_priority(ht, domain_clean, &old_priority)) {
+                nm_assert(old_priority <= priority);
                 if (old_priority < priority) {
-                    _LOGT(
-                        "plugin: drop domain '%s' (i=%d, p=%d) because it already exists with p=%d",
-                        domains[i],
-                        ip_data->data->ifindex,
-                        priority,
-                        old_priority);
+                    _LOGT("plugin: drop domain %s%s%s (i=%d, p=%d) because it already exists "
+                          "with p=%d",
+                          NM_PRINT_FMT_QUOTED(!check_default_route,
+                                              "'",
+                                              domain_full,
+                                              "'",
+                                              "<auto-default>"),
+                          ip_data->data->ifindex,
+                          priority,
+                          old_priority);
                     continue;
                 }
             } else if (domain_is_shadowed(ht, domain_clean, priority, &parent, &parent_priority)) {
-                _LOGT("plugin: drop domain '%s' (i=%d, p=%d) shadowed by '%s' (p=%d)",
-                      domains[i],
+                _LOGT("plugin: drop domain %s%s%s (i=%d, p=%d) shadowed by '%s' (p=%d)",
+                      NM_PRINT_FMT_QUOTED(!check_default_route,
+                                          "'",
+                                          domain_full,
+                                          "'",
+                                          "<auto-default>"),
                       ip_data->data->ifindex,
                       priority,
                       parent,
@@ -1446,20 +1525,55 @@ rebuild_domain_lists(NMDnsManager *self)
                 continue;
             }
 
-            _LOGT("plugin: add domain '%s' (i=%d, p=%d)",
-                  domains[i],
-                  ip_data->data->ifindex,
-                  priority);
+            _LOGT(
+                "plugin: add domain %s%s%s (i=%d, p=%d)",
+                NM_PRINT_FMT_QUOTED(!check_default_route, "'", domain_full, "'", "<auto-default>"),
+                ip_data->data->ifindex,
+                priority);
+
             if (!ht)
                 ht = g_hash_table_new(nm_str_hash, g_str_equal);
             g_hash_table_insert(ht, (gpointer) domain_clean, GINT_TO_POINTER(priority));
-            domains[num_dom2++] = domains[i];
+
+            if (check_default_route)
+                has_default_route_auto = TRUE;
+            else {
+                nm_assert(num_dom2 <= num_dom1);
+                nm_assert(num_dom2 < n_domains_allocated);
+                domains[num_dom2++] = domain_full;
+                if (domain_clean[0] == '\0')
+                    has_default_route_explicit = TRUE;
+            }
         }
-        nm_assert(num_dom2 < cap_dom);
+        nm_assert(num_dom2 < n_domains_allocated);
         domains[num_dom2] = NULL;
 
-        g_strfreev(ip_data->domains.reverse);
-        ip_data->domains.reverse = get_ip_rdns_domains(ip_config);
+        nm_assert(!ip_data->domains.search);
+        nm_assert(!ip_data->domains.reverse);
+        ip_data->domains.search                     = domains;
+        ip_data->domains.reverse                    = get_ip_rdns_domains(ip_config);
+        ip_data->domains.has_default_route_explicit = has_default_route_explicit;
+        ip_data->domains.has_default_route_exclusive =
+            has_default_route_explicit || (priority < 0 && has_default_route_auto);
+        ip_data->domains.has_default_route =
+            ip_data->domains.has_default_route_exclusive || has_default_route_auto;
+
+        {
+            gs_free char *str1 = NULL;
+            gs_free char *str2 = NULL;
+
+            _LOGT("plugin: settings: ifindex=%d, priority=%d, default-route=%d%s, search=%s, "
+                  "reverse=%s",
+                  ip_data->data->ifindex,
+                  priority,
+                  ip_data->domains.has_default_route,
+                  ip_data->domains.has_default_route_explicit
+                      ? " (explicit)"
+                      : (ip_data->domains.has_default_route_exclusive ? " (exclusive)" : ""),
+                  (str1 = g_strjoinv(",", (char **) ip_data->domains.search)),
+                  (ip_data->domains.reverse ? (str2 = g_strjoinv(",", ip_data->domains.reverse))
+                                            : ""));
+        }
     }
 }
 
@@ -1473,6 +1587,9 @@ clear_domain_lists(NMDnsManager *self)
     c_list_for_each_entry (ip_data, head, ip_config_lst) {
         nm_clear_g_free(&ip_data->domains.search);
         nm_clear_pointer(&ip_data->domains.reverse, g_strfreev);
+        ip_data->domains.has_default_route_explicit  = FALSE;
+        ip_data->domains.has_default_route_exclusive = FALSE;
+        ip_data->domains.has_default_route           = FALSE;
     }
 }
 
@@ -1697,7 +1814,7 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
 
     priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
-    data = g_hash_table_lookup(priv->configs, GINT_TO_POINTER(ifindex));
+    data = g_hash_table_lookup(priv->configs, &ifindex);
     if (!data)
         ip_data = NULL;
     else
@@ -1713,7 +1830,7 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
         /* deleting a config doesn't invalidate the configs' sort order. */
         _ip_config_data_free(ip_data);
         if (c_list_is_empty(&data->data_lst_head))
-            g_hash_table_remove(priv->configs, GINT_TO_POINTER(ifindex));
+            g_hash_table_remove(priv->configs, &ifindex);
         goto changed;
     }
 
@@ -1723,12 +1840,14 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
     }
 
     if (!data) {
-        data          = g_slice_new0(NMDnsConfigData);
-        data->ifindex = ifindex;
-        data->self    = self;
-        c_list_init(&data->data_lst_head);
+        data  = g_slice_new(NMDnsConfigData);
+        *data = (NMDnsConfigData){
+            .ifindex       = ifindex,
+            .self          = self,
+            .data_lst_head = C_LIST_INIT(data->data_lst_head),
+        };
         _ASSERT_config_data(data);
-        g_hash_table_insert(priv->configs, GINT_TO_POINTER(ifindex), data);
+        g_hash_table_add(priv->configs, data);
     }
 
     if (!ip_data)
@@ -2381,8 +2500,11 @@ nm_dns_manager_init(NMDnsManager *self)
 
     priv->config = g_object_ref(nm_config_get());
 
-    priv->configs =
-        g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) _config_data_free);
+    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMDnsConfigData, ifindex) == 0);
+    priv->configs = g_hash_table_new_full(nm_pint_hash,
+                                          nm_pint_equals,
+                                          (GDestroyNotify) _config_data_free,
+                                          NULL);
 
     /* Set the initial hash */
     compute_hash(self, NULL, NM_DNS_MANAGER_GET_PRIVATE(self)->hash);
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index f91d1556..fb2f36d2 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -22,7 +22,7 @@ typedef enum {
 
 enum {
     NM_DNS_PRIORITY_DEFAULT_NORMAL = 100,
-    NM_DNS_PRIORITY_DEFAULT_VPN    = -50,
+    NM_DNS_PRIORITY_DEFAULT_VPN    = 50,
 };
 
 struct _NMDnsConfigData;
@@ -37,13 +37,35 @@ typedef struct {
     struct {
         const char **search;
         char **      reverse;
+
+        /* Whether "search" explicitly contains a default route "~"
+         * or "". It is redundant information, but for faster lookup. */
+        bool has_default_route_explicit : 1;
+
+        /* Whether an explicit "~" search domain should be added.
+         * For systemd-resolved, this configured an explicit wildcard
+         * search domain, and should be used for profiles with negative
+         * DNS priority.
+         *
+         * If "has_default_route_explicit", this is always TRUE and implied.
+         *
+         * With systemd-resolved, if TRUE we will set a "." search domain.
+         */
+        bool has_default_route_exclusive : 1;
+
+        /* Whether the device should be used for any domains "~".
+         *
+         * If "has_default_route_exclusive", this is always TRUE and implied.
+         *
+         * With systemd-resolved, this is the value for SetLinkDefaultRoute(). */
+        bool has_default_route : 1;
     } domains;
 } NMDnsIPConfigData;
 
 typedef struct _NMDnsConfigData {
+    int                   ifindex;
     struct _NMDnsManager *self;
     CList                 data_lst_head;
-    int                   ifindex;
 } NMDnsConfigData;
 
 #define NM_TYPE_DNS_MANAGER (nm_dns_manager_get_type())
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/dns/nm-dns-systemd-resolved.c
index 24c0ddb5..fe2e78af 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/dns/nm-dns-systemd-resolved.c
@@ -34,6 +34,9 @@
 #define SYSTEMD_RESOLVED_MANAGER_IFACE "org.freedesktop.resolve1.Manager"
 #define SYSTEMD_RESOLVED_DBUS_PATH     "/org/freedesktop/resolve1"
 
+/* define a variable, so that we can compare the operation with pointer equality. */
+static const char *const DBUS_OP_SET_LINK_DEFAULT_ROUTE = "SetLinkDefaultRoute";
+
 /*****************************************************************************/
 
 typedef struct {
@@ -42,15 +45,18 @@ typedef struct {
 } InterfaceConfig;
 
 typedef struct {
-    CList       request_queue_lst;
-    const char *operation;
-    GVariant *  argument;
+    CList                 request_queue_lst;
+    const char *          operation;
+    GVariant *            argument;
+    NMDnsSystemdResolved *self;
+    int                   ifindex;
 } RequestItem;
 
 /*****************************************************************************/
 
 typedef struct {
     GDBusConnection *dbus_connection;
+    GHashTable *     dirty_interfaces;
     GCancellable *   cancellable;
     CList            request_queue_lst_head;
     guint            name_owner_changed_id;
@@ -58,6 +64,8 @@ typedef struct {
     bool             try_start_blocked : 1;
     bool             dbus_has_owner : 1;
     bool             dbus_initied : 1;
+    bool             request_queue_to_send : 1;
+    NMTernary        has_link_default_route : 3;
 } NMDnsSystemdResolvedPrivate;
 
 struct _NMDnsSystemdResolved {
@@ -87,18 +95,26 @@ _request_item_free(RequestItem *request_item)
 {
     c_list_unlink_stale(&request_item->request_queue_lst);
     g_variant_unref(request_item->argument);
-    g_slice_free(RequestItem, request_item);
+    nm_g_slice_free(request_item);
 }
 
 static void
-_request_item_append(CList *request_queue_lst_head, const char *operation, GVariant *argument)
+_request_item_append(NMDnsSystemdResolved *self,
+                     const char *          operation,
+                     int                   ifindex,
+                     GVariant *            argument)
 {
-    RequestItem *request_item;
+    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    RequestItem *                request_item;
 
-    request_item            = g_slice_new(RequestItem);
-    request_item->operation = operation;
-    request_item->argument  = g_variant_ref_sink(argument);
-    c_list_link_tail(request_queue_lst_head, &request_item->request_queue_lst);
+    request_item  = g_slice_new(RequestItem);
+    *request_item = (RequestItem){
+        .operation = operation,
+        .argument  = g_variant_ref_sink(argument),
+        .self      = self,
+        .ifindex   = ifindex,
+    };
+    c_list_link_tail(&priv->request_queue_lst_head, &request_item->request_queue_lst);
 }
 
 /*****************************************************************************/
@@ -115,43 +131,69 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
 {
     gs_unref_variant GVariant *v       = NULL;
     gs_free_error GError *       error = NULL;
-    NMDnsSystemdResolved *       self  = (NMDnsSystemdResolved *) user_data;
+    NMDnsSystemdResolved *       self;
     NMDnsSystemdResolvedPrivate *priv;
+    RequestItem *                request_item;
+    NMLogLevel                   log_level;
 
     v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), r, &error);
-    if (!v && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    if (nm_utils_error_is_cancelled(error))
         return;
 
-    priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    request_item = user_data;
+    self         = request_item->self;
+    priv         = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
 
-    if (!v) {
-        if (!priv->send_updates_warn_ratelimited) {
-            priv->send_updates_warn_ratelimited = TRUE;
-            _LOGW("send-updates failed to update systemd-resolved: %s", error->message);
-        } else
-            _LOGD("send-updates failed: %s", error->message);
-    } else
+    if (v) {
+        if (request_item->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");
+        }
         priv->send_updates_warn_ratelimited = FALSE;
+        return;
+    }
+
+    if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
+        && nm_g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+        if (priv->has_link_default_route == NM_TERNARY_DEFAULT) {
+            priv->has_link_default_route = NM_TERNARY_FALSE;
+            _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API not supported");
+        }
+        return;
+    }
+
+    log_level = LOGL_DEBUG;
+    if (!priv->send_updates_warn_ratelimited) {
+        priv->send_updates_warn_ratelimited = TRUE;
+        log_level                           = LOGL_WARN;
+    }
+    _NMLOG(log_level,
+           "send-updates %s@%d failed: %s",
+           request_item->operation,
+           request_item->ifindex,
+           error->message);
 }
 
-static void
+static gboolean
 update_add_ip_config(NMDnsSystemdResolved *self,
                      GVariantBuilder *     dns,
                      GVariantBuilder *     domains,
                      NMDnsIPConfigData *   data)
 {
-    int          addr_family;
-    gsize        addr_size;
-    guint        i, n;
-    gboolean     is_routing;
-    const char **iter;
-    const char * domain;
+    int         addr_family;
+    gsize       addr_size;
+    guint       i, n;
+    gboolean    is_routing;
+    const char *domain;
+    gboolean    has_config = FALSE;
 
     addr_family = nm_ip_config_get_addr_family(data->ip_config);
     addr_size   = nm_utils_addr_family_to_size(addr_family);
 
-    if (!data->domains.search || !data->domains.search[0])
-        return;
+    if ((!data->domains.search || !data->domains.search[0])
+        && !data->domains.has_default_route_exclusive && !data->domains.has_default_route)
+        return FALSE;
 
     n = nm_ip_config_get_num_nameservers(data->ip_config);
     for (i = 0; i < n; i++) {
@@ -164,12 +206,22 @@ update_add_ip_config(NMDnsSystemdResolved *self,
                                       addr_size,
                                       1));
         g_variant_builder_close(dns);
+        has_config = TRUE;
     }
 
-    for (iter = data->domains.search; *iter; iter++) {
-        domain = nm_utils_parse_dns_domain(*iter, &is_routing);
-        g_variant_builder_add(domains, "(sb)", domain[0] ? domain : ".", is_routing);
+    if (!data->domains.has_default_route_explicit && data->domains.has_default_route_exclusive) {
+        g_variant_builder_add(domains, "(sb)", ".", TRUE);
+        has_config = TRUE;
     }
+    if (data->domains.search) {
+        for (i = 0; data->domains.search[i]; i++) {
+            domain = nm_utils_parse_dns_domain(data->domains.search[i], &is_routing);
+            g_variant_builder_add(domains, "(sb)", domain[0] ? domain : ".", is_routing);
+            has_config = TRUE;
+        }
+    }
+
+    return has_config;
 }
 
 static void
@@ -183,15 +235,17 @@ free_pending_updates(NMDnsSystemdResolved *self)
         _request_item_free(request_item);
 }
 
-static void
+static gboolean
 prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
 {
-    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
-    GVariantBuilder              dns, domains;
-    NMCListElem *                elem;
-    NMSettingConnectionMdns      mdns     = NM_SETTING_CONNECTION_MDNS_DEFAULT;
-    NMSettingConnectionLlmnr     llmnr    = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
-    const char *                 mdns_arg = NULL, *llmnr_arg = NULL;
+    GVariantBuilder          dns;
+    GVariantBuilder          domains;
+    NMCListElem *            elem;
+    NMSettingConnectionMdns  mdns     = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+    NMSettingConnectionLlmnr llmnr    = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+    const char *             mdns_arg = NULL, *llmnr_arg = NULL;
+    gboolean                 has_config        = FALSE;
+    gboolean                 has_default_route = FALSE;
 
     g_variant_builder_init(&dns, G_VARIANT_TYPE("(ia(iay))"));
     g_variant_builder_add(&dns, "i", ic->ifindex);
@@ -205,7 +259,10 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
         NMDnsIPConfigData *data      = elem->data;
         NMIPConfig *       ip_config = data->ip_config;
 
-        update_add_ip_config(self, &dns, &domains, data);
+        has_config |= update_add_ip_config(self, &dns, &domains, data);
+
+        if (data->domains.has_default_route)
+            has_default_route = TRUE;
 
         if (NM_IS_IP4_CONFIG(ip_config)) {
             mdns  = NM_MAX(mdns, nm_ip4_config_mdns_get(NM_IP4_CONFIG(ip_config)));
@@ -248,16 +305,25 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
     }
     nm_assert(llmnr_arg);
 
-    _request_item_append(&priv->request_queue_lst_head, "SetLinkDNS", g_variant_builder_end(&dns));
-    _request_item_append(&priv->request_queue_lst_head,
-                         "SetLinkDomains",
-                         g_variant_builder_end(&domains));
-    _request_item_append(&priv->request_queue_lst_head,
+    if (!nm_str_is_empty(mdns_arg) || !nm_str_is_empty(llmnr_arg))
+        has_config = TRUE;
+
+    _request_item_append(self, "SetLinkDomains", ic->ifindex, g_variant_builder_end(&domains));
+    _request_item_append(self,
+                         DBUS_OP_SET_LINK_DEFAULT_ROUTE,
+                         ic->ifindex,
+                         g_variant_new("(ib)", ic->ifindex, has_default_route));
+    _request_item_append(self,
                          "SetLinkMulticastDNS",
+                         ic->ifindex,
                          g_variant_new("(is)", ic->ifindex, mdns_arg ?: ""));
-    _request_item_append(&priv->request_queue_lst_head,
+    _request_item_append(self,
                          "SetLinkLLMNR",
+                         ic->ifindex,
                          g_variant_new("(is)", ic->ifindex, llmnr_arg ?: ""));
+    _request_item_append(self, "SetLinkDNS", ic->ifindex, g_variant_builder_end(&dns));
+
+    return has_config;
 }
 
 static void
@@ -266,7 +332,7 @@ send_updates(NMDnsSystemdResolved *self)
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
     RequestItem *                request_item;
 
-    if (c_list_is_empty(&priv->request_queue_lst_head)) {
+    if (!priv->request_queue_to_send) {
         /* nothing to do. */
         return;
     }
@@ -296,15 +362,30 @@ send_updates(NMDnsSystemdResolved *self)
         return;
     }
 
-    _LOGT("send-updates: start %lu requests", c_list_length(&priv->request_queue_lst_head));
-
     nm_clear_g_cancellable(&priv->cancellable);
 
+    if (c_list_is_empty(&priv->request_queue_lst_head)) {
+        _LOGT("send-updates: no requests to send");
+        priv->request_queue_to_send = FALSE;
+        return;
+    }
+
+    _LOGT("send-updates: start %lu requests", c_list_length(&priv->request_queue_lst_head));
+
     priv->cancellable = g_cancellable_new();
 
-    while (
-        (request_item =
-             c_list_first_entry(&priv->request_queue_lst_head, RequestItem, request_queue_lst))) {
+    priv->request_queue_to_send = FALSE;
+
+    c_list_for_each_entry (request_item, &priv->request_queue_lst_head, request_queue_lst) {
+        if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
+            && priv->has_link_default_route == NM_TERNARY_FALSE) {
+            /* The "SetLinkDefaultRoute" API is only supported since v240.
+             * We detected that it is not supported, and skip the call. There
+             * is no special workaround, because in this case we rely on systemd-resolved
+             * to do the right thing automatically. */
+            continue;
+        }
+
         /* Above we explicitly call "StartServiceByName" trying to avoid D-Bus activating systmd-resolved
          * multiple times. There is still a race, were we might hit this line although actually
          * the service just quit this very moment. In that case, we would try to D-Bus activate the
@@ -324,8 +405,7 @@ send_updates(NMDnsSystemdResolved *self)
                                -1,
                                priv->cancellable,
                                call_done,
-                               self);
-        _request_item_free(request_item);
+                               request_item);
     }
 }
 
@@ -336,19 +416,22 @@ update(NMDnsPlugin *            plugin,
        const char *             hostname,
        GError **                error)
 {
-    NMDnsSystemdResolved *self                = NM_DNS_SYSTEMD_RESOLVED(plugin);
+    NMDnsSystemdResolved *       self         = NM_DNS_SYSTEMD_RESOLVED(plugin);
+    NMDnsSystemdResolvedPrivate *priv         = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
     gs_unref_hashtable GHashTable *interfaces = NULL;
     gs_free gpointer * interfaces_keys        = NULL;
     guint              interfaces_len;
-    guint              i;
+    int                ifindex;
+    gpointer           pointer;
     NMDnsIPConfigData *ip_data;
+    GHashTableIter     iter;
+    guint              i;
 
     interfaces =
         g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) _interface_config_free);
 
     c_list_for_each_entry (ip_data, ip_config_lst_head, ip_config_lst) {
         InterfaceConfig *ic = NULL;
-        int              ifindex;
 
         ifindex = ip_data->data->ifindex;
         nm_assert(ifindex == nm_ip_config_get_ifindex(ip_data->ip_config));
@@ -371,11 +454,33 @@ update(NMDnsPlugin *            plugin,
     for (i = 0; i < interfaces_len; i++) {
         InterfaceConfig *ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(interfaces_keys[i]));
 
-        prepare_one_interface(self, ic);
+        if (prepare_one_interface(self, ic))
+            g_hash_table_add(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex));
+        else
+            g_hash_table_remove(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex));
     }
 
-    send_updates(self);
+    /* If we previously configured an ifindex with non-empty values in
+     * resolved, and the current update doesn't contain that interface,
+     * reset the resolved configuration for that ifindex. */
+    g_hash_table_iter_init(&iter, priv->dirty_interfaces);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &pointer, NULL)) {
+        ifindex = GPOINTER_TO_INT(pointer);
+        if (!g_hash_table_contains(interfaces, GINT_TO_POINTER(ifindex))) {
+            InterfaceConfig ic;
+
+            _LOGT("clear previously configured ifindex %d", ifindex);
+            ic = (InterfaceConfig){
+                .ifindex          = ifindex,
+                .configs_lst_head = C_LIST_INIT(ic.configs_lst_head),
+            };
+            prepare_one_interface(self, &ic);
+            g_hash_table_iter_remove(&iter);
+        }
+    }
 
+    priv->request_queue_to_send = TRUE;
+    send_updates(self);
     return TRUE;
 }
 
@@ -394,8 +499,11 @@ name_owner_changed(NMDnsSystemdResolved *self, const char *owner)
         _LOGT("D-Bus name for systemd-resolved has owner %s", owner);
 
     priv->dbus_has_owner = !!owner;
-    if (owner)
-        priv->try_start_blocked = FALSE;
+    if (owner) {
+        priv->try_start_blocked     = FALSE;
+        priv->request_queue_to_send = TRUE;
+    } else
+        priv->has_link_default_route = NM_TERNARY_DEFAULT;
 
     send_updates(self);
 }
@@ -468,7 +576,10 @@ nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self)
 {
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
 
+    priv->has_link_default_route = NM_TERNARY_DEFAULT;
+
     c_list_init(&priv->request_queue_lst_head);
+    priv->dirty_interfaces = g_hash_table_new(nm_direct_hash, NULL);
 
     priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET);
     if (!priv->dbus_connection) {
@@ -510,6 +621,7 @@ dispose(GObject *object)
     nm_clear_g_cancellable(&priv->cancellable);
 
     g_clear_object(&priv->dbus_connection);
+    nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_unref);
 
     G_OBJECT_CLASS(nm_dns_systemd_resolved_parent_class)->dispose(object);
 }