about summary refs log tree commit diff
path: root/src/core/dns/nm-dns-systemd-resolved.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/dns/nm-dns-systemd-resolved.c')
-rw-r--r--src/core/dns/nm-dns-systemd-resolved.c425
1 files changed, 304 insertions, 121 deletions
diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c
index c4993884..e0b94647 100644
--- a/src/core/dns/nm-dns-systemd-resolved.c
+++ b/src/core/dns/nm-dns-systemd-resolved.c
@@ -40,8 +40,8 @@ static const char *const DBUS_OP_SET_LINK_DNS_OVER_TLS  = "SetLinkDNSOverTLS";
 /*****************************************************************************/
 
 typedef struct {
-    int   ifindex;
-    CList configs_lst_head;
+    int        ifindex;
+    GPtrArray *ip_data_list;
 } InterfaceConfig;
 
 typedef struct {
@@ -50,6 +50,7 @@ typedef struct {
     GVariant             *argument;
     NMDnsSystemdResolved *self;
     int                   ifindex;
+    int                   ref_count;
 } RequestItem;
 
 struct _NMDnsSystemdResolvedResolveHandle {
@@ -77,15 +78,18 @@ typedef struct {
     GDBusConnection *dbus_connection;
     GHashTable      *dirty_interfaces;
     GCancellable    *cancellable;
-    GSource         *try_start_timeout_source;
+    GCancellable    *service_start_cancellable;
     CList            request_queue_lst_head;
     char            *dbus_owner;
     CList            handle_lst_head;
     guint            name_owner_changed_id;
+    guint            n_pending;
     bool             send_updates_warn_ratelimited : 1;
     bool             try_start_blocked : 1;
+    bool             stopped : 1;
     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
@@ -106,7 +110,7 @@ struct _NMDnsSystemdResolvedClass {
 G_DEFINE_TYPE(NMDnsSystemdResolved, nm_dns_systemd_resolved, NM_TYPE_DNS_PLUGIN)
 
 #define NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self) \
-    _NM_GET_PRIVATE(self, NMDnsSystemdResolved, NM_IS_DNS_SYSTEMD_RESOLVED)
+    _NM_GET_PRIVATE(self, NMDnsSystemdResolved, NM_IS_DNS_SYSTEMD_RESOLVED, NMDnsPlugin)
 
 /*****************************************************************************/
 
@@ -146,10 +150,88 @@ static void _resolve_start(NMDnsSystemdResolved *self, NMDnsSystemdResolvedResol
 
 /*****************************************************************************/
 
+static gboolean
+_update_pending_detect(NMDnsSystemdResolved *self)
+{
+    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+
+    if (priv->n_pending > 0) {
+        /* we have pending calls. We definitely want to wait for them to complete. */
+        return TRUE;
+    }
+    if (!priv->dbus_initied) {
+        if (!priv->dbus_connection)
+            return FALSE;
+        /* D-Bus not yet initialized (and we don't know the name owner yet). Pending. */
+        return TRUE;
+    }
+    if (priv->service_start_cancellable) {
+        /* We are waiting to D-Bus activate resolved. Pending. */
+        return TRUE;
+    }
+    if (priv->try_start_blocked) {
+        /* We earlier tried to start resolved, but are rate limited. We are not pending an update
+         * (that we expect to complete any time soon). */
+        return FALSE;
+    }
+    if (priv->send_updates_waiting) {
+        /* we wait to send updates. We are pending. */
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static void
+_update_pending_maybe_changed(NMDnsSystemdResolved *self)
+{
+    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    gboolean                     update_pending;
+
+    /* Important: we need to make sure that we call _update_pending_maybe_changed(), when
+     * the state changes. */
+
+    update_pending = _update_pending_detect(self);
+    if (priv->update_pending != update_pending) {
+        priv->update_pending = update_pending;
+        _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
+    }
+}
+
+static gboolean
+get_update_pending(NMDnsPlugin *plugin)
+{
+    NMDnsSystemdResolved        *self = NM_DNS_SYSTEMD_RESOLVED(plugin);
+    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+
+    nm_assert(priv->update_pending == _update_pending_detect(self));
+    return priv->update_pending;
+}
+
+/*****************************************************************************/
+
+static RequestItem *
+_request_item_ref(RequestItem *request_item)
+{
+    nm_assert(request_item);
+    nm_assert(request_item->ref_count > 0);
+    nm_assert(request_item->ref_count < G_MAXINT);
+    nm_assert(!c_list_is_empty(&request_item->request_queue_lst));
+
+    request_item->ref_count++;
+    return request_item;
+}
+
 static void
-_request_item_free(RequestItem *request_item)
+_request_item_unref(RequestItem *request_item)
 {
-    c_list_unlink_stale(&request_item->request_queue_lst);
+    nm_assert(request_item);
+    nm_assert(request_item->ref_count > 0);
+
+    if (--request_item->ref_count > 0)
+        return;
+
+    nm_assert(c_list_is_empty(&request_item->request_queue_lst));
+
     g_variant_unref(request_item->argument);
     nm_g_slice_free(request_item);
 }
@@ -165,6 +247,7 @@ _request_item_append(NMDnsSystemdResolved *self,
 
     request_item  = g_slice_new(RequestItem);
     *request_item = (RequestItem){
+        .ref_count = 1,
         .operation = operation,
         .argument  = g_variant_ref_sink(argument),
         .self      = self,
@@ -178,8 +261,8 @@ _request_item_append(NMDnsSystemdResolved *self,
 static void
 _interface_config_free(InterfaceConfig *config)
 {
-    nm_c_list_elem_free_all(&config->configs_lst_head, NULL);
-    g_slice_free(InterfaceConfig, config);
+    nm_g_ptr_array_unref(config->ip_data_list);
+    nm_g_slice_free(config);
 }
 
 static void
@@ -191,42 +274,48 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
     NMDnsSystemdResolvedPrivate *priv;
     RequestItem                 *request_item;
     NMLogLevel                   log_level;
-
-    v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), r, &error);
-    if (nm_utils_error_is_cancelled(error))
-        return;
+    const char                  *operation;
+    int                          ifindex;
 
     request_item = user_data;
     self         = request_item->self;
-    priv         = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    operation    = request_item->operation;
+    ifindex      = request_item->ifindex;
+    _request_item_unref(request_item);
+
+    priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+
+    v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), r, &error);
+    if (nm_utils_error_is_cancelled(error))
+        goto out_dec_pending;
 
     if (v) {
-        if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
+        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 (request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS
+        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");
         }
         priv->send_updates_warn_ratelimited = FALSE;
-        return;
+        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
-            && request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) {
+            && 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
-            && request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) {
+            && 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");
         }
-        return;
+        goto out_dec_pending;
     }
 
     log_level = LOGL_DEBUG;
@@ -234,18 +323,25 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
         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);
+    _NMLOG(log_level, "send-updates %s@%d failed: %s", operation, ifindex, error->message);
+
+out_dec_pending:
+    nm_assert(priv->n_pending > 0);
+    if (--priv->n_pending <= 0) {
+        _update_pending_maybe_changed(self);
+        /* We keep @self alive while pending operations are in progress. It's simpler
+         * to implement. But this requires that we implement "stop()" signal to cancel
+         * all pending requests. Cancelling is necessary, because during shutdown,
+         * we must wrap up fast, and not hang an undefined amount time. */
+        g_object_unref(self);
+    }
 }
 
 static gboolean
-update_add_ip_config(NMDnsSystemdResolved *self,
-                     GVariantBuilder      *dns,
-                     GVariantBuilder      *domains,
-                     NMDnsConfigIPData    *ip_data)
+update_add_ip_config(NMDnsSystemdResolved    *self,
+                     GVariantBuilder         *dns,
+                     GVariantBuilder         *domains,
+                     const NMDnsConfigIPData *ip_data)
 {
     gsize         addr_size;
     guint         n;
@@ -258,8 +354,12 @@ update_add_ip_config(NMDnsSystemdResolved *self,
     addr_size = nm_utils_addr_family_to_size(ip_data->addr_family);
 
     if ((!ip_data->domains.search || !ip_data->domains.search[0])
-        && !ip_data->domains.has_default_route_exclusive && !ip_data->domains.has_default_route)
+        && !ip_data->domains.has_default_route_exclusive && !ip_data->domains.has_default_route) {
+        /* we have no search domain (which systemd-resolved uses to routing the request), but
+         * also the "DefaultRoute" is not set on the interface. This setting has no effect and
+         * gets ignored. */
         return FALSE;
+    }
 
     nameservers = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &n);
     for (i = 0; i < n; i++) {
@@ -295,23 +395,28 @@ free_pending_updates(NMDnsSystemdResolved *self)
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
     RequestItem                 *request_item;
 
-    while ((request_item =
-                c_list_first_entry(&priv->request_queue_lst_head, RequestItem, request_queue_lst)))
-        _request_item_free(request_item);
+    while (
+        (request_item =
+             c_list_first_entry(&priv->request_queue_lst_head, RequestItem, request_queue_lst))) {
+        c_list_unlink(&request_item->request_queue_lst);
+        _request_item_unref(request_item);
+    }
 }
 
 static gboolean
-prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
+prepare_one_interface(NMDnsSystemdResolved *self, const InterfaceConfig *ic)
 {
     GVariantBuilder               dns;
     GVariantBuilder               domains;
-    NMCListElem                  *elem;
-    NMSettingConnectionMdns       mdns         = NM_SETTING_CONNECTION_MDNS_DEFAULT;
-    NMSettingConnectionLlmnr      llmnr        = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
-    NMSettingConnectionDnsOverTls dns_over_tls = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
-    const char                   *mdns_arg = NULL, *llmnr_arg = NULL, *dns_over_tls_arg = NULL;
+    NMSettingConnectionMdns       mdns              = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+    NMSettingConnectionLlmnr      llmnr             = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+    NMSettingConnectionDnsOverTls dns_over_tls      = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
+    const char                   *mdns_arg          = NULL;
+    const char                   *llmnr_arg         = NULL;
+    const char                   *dns_over_tls_arg  = NULL;
     gboolean                      has_config        = FALSE;
     gboolean                      has_default_route = FALSE;
+    guint                         i;
 
     g_variant_builder_init(&dns, G_VARIANT_TYPE("(ia(iay))"));
     g_variant_builder_add(&dns, "i", ic->ifindex);
@@ -321,18 +426,22 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
     g_variant_builder_add(&domains, "i", ic->ifindex);
     g_variant_builder_open(&domains, G_VARIANT_TYPE("a(sb)"));
 
-    c_list_for_each_entry (elem, &ic->configs_lst_head, lst) {
-        NMDnsConfigIPData *ip_data = elem->data;
+    if (ic->ip_data_list) {
+        for (i = 0; i < ic->ip_data_list->len; i++) {
+            const NMDnsConfigIPData *ip_data = ic->ip_data_list->pdata[i];
 
-        has_config |= update_add_ip_config(self, &dns, &domains, ip_data);
+            if (update_add_ip_config(self, &dns, &domains, ip_data))
+                has_config = TRUE;
 
-        if (ip_data->domains.has_default_route)
-            has_default_route = TRUE;
+            if (ip_data->domains.has_default_route)
+                has_default_route = TRUE;
 
-        if (NM_IS_IPv4(ip_data->addr_family)) {
-            mdns         = NM_MAX(mdns, nm_l3_config_data_get_mdns(ip_data->l3cd));
-            llmnr        = NM_MAX(llmnr, nm_l3_config_data_get_llmnr(ip_data->l3cd));
-            dns_over_tls = NM_MAX(dns_over_tls, nm_l3_config_data_get_dns_over_tls(ip_data->l3cd));
+            if (NM_IS_IPv4(ip_data->addr_family)) {
+                mdns  = NM_MAX(mdns, nm_l3_config_data_get_mdns(ip_data->l3cd));
+                llmnr = NM_MAX(llmnr, nm_l3_config_data_get_llmnr(ip_data->l3cd));
+                dns_over_tls =
+                    NM_MAX(dns_over_tls, nm_l3_config_data_get_dns_over_tls(ip_data->l3cd));
+            }
         }
     }
 
@@ -413,33 +522,45 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
     return has_config;
 }
 
-static gboolean
-_ensure_resolved_running_timeout(gpointer user_data)
+static void
+start_resolved_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMDnsSystemdResolved              *self = user_data;
-    NMDnsSystemdResolvedPrivate       *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    gs_unref_variant GVariant         *res   = NULL;
+    gs_free_error GError              *error = NULL;
+    NMDnsSystemdResolved              *self;
+    NMDnsSystemdResolvedPrivate       *priv;
     NMDnsSystemdResolvedResolveHandle *handle;
 
-    nm_clear_g_source_inst(&priv->try_start_timeout_source);
+    res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
+    if (nm_utils_error_is_cancelled(error))
+        return;
+
+    self = user_data;
+    priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    nm_clear_g_cancellable(&priv->service_start_cancellable);
 
-    _LOGT("timeout waiting to D-Bus activate systemd-resolved. Systemd-resolved won't be "
-          "used until it appears on the bus");
+    if (!res) {
+        g_dbus_error_strip_remote_error(error);
+        _LOGD("error activating systemd-resolved: %s", error->message);
 
 again:
-    c_list_for_each_entry (handle, &priv->handle_lst_head, handle_lst) {
-        gs_free_error GError *error = NULL;
+        c_list_for_each_entry (handle, &priv->handle_lst_head, handle_lst) {
+            gs_free_error GError *local = NULL;
 
-        if (handle->is_failing_on_idle)
-            continue;
+            if (handle->is_failing_on_idle)
+                continue;
 
-        nm_utils_error_set_literal(&error,
-                                   NM_UTILS_ERROR_NOT_READY,
-                                   "timeout waiting for systemd-resolved to start");
-        _resolve_complete_error(handle, error);
-        goto again;
-    }
+            nm_utils_error_set(&local,
+                               NM_UTILS_ERROR_NOT_READY,
+                               "error activating systemd-resolved: %s",
+                               error->message);
+            _resolve_complete_error(handle, local);
+            goto again;
+        }
+    } else
+        _LOGD("systemd-resolved successfully started");
 
-    return G_SOURCE_CONTINUE;
+    _update_pending_maybe_changed(self);
 }
 
 static NMTernary
@@ -447,6 +568,9 @@ ensure_resolved_running(NMDnsSystemdResolved *self)
 {
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
 
+    if (priv->stopped)
+        return NM_TERNARY_FALSE;
+
     if (!priv->dbus_initied)
         return NM_TERNARY_DEFAULT;
 
@@ -459,16 +583,15 @@ ensure_resolved_running(NMDnsSystemdResolved *self)
 
         _LOGT("try D-Bus activating systemd-resolved...");
         priv->try_start_blocked = TRUE;
-
-        priv->try_start_timeout_source =
-            nm_g_timeout_add_source(4000, _ensure_resolved_running_timeout, self);
-
+        nm_clear_g_cancellable(&priv->service_start_cancellable);
+        priv->service_start_cancellable = g_cancellable_new();
         nm_dbus_connection_call_start_service_by_name(priv->dbus_connection,
                                                       SYSTEMD_RESOLVED_DBUS_SERVICE,
-                                                      -1,
-                                                      NULL,
-                                                      NULL,
-                                                      NULL);
+                                                      4000,
+                                                      priv->service_start_cancellable,
+                                                      start_resolved_cb,
+                                                      self);
+        _update_pending_maybe_changed(self);
         return NM_TERNARY_DEFAULT;
     }
 
@@ -523,6 +646,12 @@ send_updates(NMDnsSystemdResolved *self)
               request_item->operation,
               (ss = g_variant_print(request_item->argument, FALSE)));
 
+        if (priv->n_pending++ == 0) {
+            /* We are inside send_updates(). All callers are already calling
+             * _update_pending_maybe_changed() afterwards. */
+            g_object_ref(self);
+        }
+
         g_dbus_connection_call(priv->dbus_connection,
                                priv->dbus_owner,
                                SYSTEMD_RESOLVED_DBUS_PATH,
@@ -534,7 +663,7 @@ send_updates(NMDnsSystemdResolved *self)
                                -1,
                                priv->cancellable,
                                call_done,
-                               request_item);
+                               _request_item_ref(request_item));
     }
 
 start_resolve:
@@ -554,43 +683,54 @@ update(NMDnsPlugin             *plugin,
        const char              *hostdomain,
        GError                 **error)
 {
-    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;
+    NMDnsSystemdResolved          *self       = NM_DNS_SYSTEMD_RESOLVED(plugin);
+    NMDnsSystemdResolvedPrivate   *priv       = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    gs_unref_hashtable GHashTable *interfaces = NULL;
+    const NMUtilsNamedValue       *interfaces_arr;
+    NMUtilsNamedValue              interfaces_arr_stack[50];
+    gs_free NMUtilsNamedValue     *interfaces_arr_heap = NULL;
     guint                          interfaces_len;
-    int                            ifindex;
     gpointer                       pointer;
     NMDnsConfigIPData             *ip_data;
     GHashTableIter                 iter;
+    gs_unref_array GArray         *dirty_array = NULL;
     guint                          i;
 
+    nm_assert(!priv->stopped);
+
+    /* Group configs by ifindex/interfaces. */
     interfaces =
         g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) _interface_config_free);
 
     c_list_for_each_entry (ip_data, ip_data_lst_head, ip_data_lst) {
-        InterfaceConfig *ic = NULL;
+        InterfaceConfig *ic      = NULL;
+        int              ifindex = ip_data->data->ifindex;
 
-        ifindex = ip_data->data->ifindex;
         nm_assert(ifindex == nm_l3_config_data_get_ifindex(ip_data->l3cd));
 
         ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(ifindex));
         if (!ic) {
-            ic          = g_slice_new(InterfaceConfig);
-            ic->ifindex = ifindex;
-            c_list_init(&ic->configs_lst_head);
+            ic  = g_slice_new(InterfaceConfig);
+            *ic = (InterfaceConfig){
+                .ifindex      = ifindex,
+                .ip_data_list = g_ptr_array_sized_new(4),
+            };
             g_hash_table_insert(interfaces, GINT_TO_POINTER(ifindex), ic);
         }
 
-        c_list_link_tail(&ic->configs_lst_head, &nm_c_list_elem_new_stale(ip_data)->lst);
+        g_ptr_array_add(ic->ip_data_list, ip_data);
     }
 
     free_pending_updates(self);
 
-    interfaces_keys =
-        nm_utils_hash_keys_to_array(interfaces, nm_cmp_int2ptr_p_with_data, NULL, &interfaces_len);
+    interfaces_arr = nm_utils_hash_to_array_with_buffer(interfaces,
+                                                        &interfaces_len,
+                                                        nm_cmp_int2ptr_p_with_data,
+                                                        NULL,
+                                                        interfaces_arr_stack,
+                                                        &interfaces_arr_heap);
     for (i = 0; i < interfaces_len; i++) {
-        InterfaceConfig *ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(interfaces_keys[i]));
+        const InterfaceConfig *ic = interfaces_arr[i].value_ptr;
 
         if (prepare_one_interface(self, ic))
             g_hash_table_add(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex));
@@ -602,23 +742,38 @@ update(NMDnsPlugin             *plugin,
      * 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))) {
+    while (g_hash_table_iter_next(&iter, &pointer, NULL)) {
+        int ifindex = GPOINTER_TO_INT(pointer);
+
+        if (g_hash_table_contains(interfaces, GINT_TO_POINTER(ifindex))) {
+            /* the interface is still tracked and still dirty. Keep. */
+            continue;
+        }
+
+        if (!dirty_array)
+            dirty_array = g_array_new(FALSE, FALSE, sizeof(int));
+        g_array_append_val(dirty_array, ifindex);
+
+        g_hash_table_iter_remove(&iter);
+    }
+    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);
             InterfaceConfig ic;
 
             _LOGT("clear previously configured ifindex %d", ifindex);
             ic = (InterfaceConfig){
-                .ifindex          = ifindex,
-                .configs_lst_head = C_LIST_INIT(ic.configs_lst_head),
+                .ifindex      = ifindex,
+                .ip_data_list = NULL,
             };
             prepare_one_interface(self, &ic);
-            g_hash_table_iter_remove(&iter);
         }
     }
 
     priv->send_updates_waiting = TRUE;
     send_updates(self);
+    _update_pending_maybe_changed(self);
     return TRUE;
 }
 
@@ -636,8 +791,7 @@ name_owner_changed(NMDnsSystemdResolved *self, const char *owner)
     else
         _LOGT("D-Bus name for systemd-resolved has owner %s", owner);
 
-    nm_clear_g_source_inst(&priv->try_start_timeout_source);
-
+    nm_clear_g_cancellable(&priv->service_start_cancellable);
     nm_strdup_reset(&priv->dbus_owner, owner);
 
     if (owner) {
@@ -649,6 +803,7 @@ name_owner_changed(NMDnsSystemdResolved *self, const char *owner)
     }
 
     send_updates(self);
+    _update_pending_maybe_changed(self);
 }
 
 static void
@@ -957,6 +1112,48 @@ nm_dns_systemd_resolved_resolve_cancel(NMDnsSystemdResolvedResolveHandle *handle
 /*****************************************************************************/
 
 static void
+stop(NMDnsPlugin *plugin)
+{
+    NMDnsSystemdResolved              *self = NM_DNS_SYSTEMD_RESOLVED(plugin);
+    NMDnsSystemdResolvedPrivate       *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    NMDnsSystemdResolvedResolveHandle *handle;
+
+    /* This function must be re-entrant!!
+     *
+     * Currently there is no concept of unregistering/shutting down. It's not
+     * clear whether we should de-configure anything in systemd-resolved, we
+     * don't.
+     *
+     * Implementing stop() is important because pending operations take a
+     * reference on @self. We can only cancel (fast shutdown) the instance
+     * by cancelling those requests. */
+
+    priv->stopped           = TRUE;
+    priv->try_start_blocked = TRUE;
+
+    nm_clear_g_cancellable(&priv->cancellable);
+
+    nm_clear_g_free(&priv->dbus_owner);
+
+    while ((handle = c_list_first_entry(&priv->handle_lst_head,
+                                        NMDnsSystemdResolvedResolveHandle,
+                                        handle_lst))) {
+        gs_free_error GError *error = NULL;
+
+        nm_utils_error_set_cancelled(&error, TRUE, "NMDnsSystemdResolved");
+        _resolve_complete_error(handle, error);
+    }
+
+    free_pending_updates(self);
+
+    nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
+
+    nm_clear_g_cancellable(&priv->service_start_cancellable);
+}
+
+/*****************************************************************************/
+
+static void
 nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self)
 {
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
@@ -974,6 +1171,8 @@ nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self)
         return;
     }
 
+    priv->update_pending = TRUE;
+
     priv->name_owner_changed_id =
         nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection,
                                                                SYSTEMD_RESOLVED_DBUS_SERVICE,
@@ -998,33 +1197,15 @@ nm_dns_systemd_resolved_new(void)
 static void
 dispose(GObject *object)
 {
-    NMDnsSystemdResolved              *self = NM_DNS_SYSTEMD_RESOLVED(object);
-    NMDnsSystemdResolvedPrivate       *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
-    NMDnsSystemdResolvedResolveHandle *handle;
-
-    while ((handle = c_list_first_entry(&priv->handle_lst_head,
-                                        NMDnsSystemdResolvedResolveHandle,
-                                        handle_lst))) {
-        gs_free_error GError *error = NULL;
-
-        nm_utils_error_set_cancelled(&error, TRUE, "NMDnsSystemdResolved");
-        _resolve_complete_error(handle, error);
-    }
-
-    free_pending_updates(self);
-
-    nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
-
-    nm_clear_g_cancellable(&priv->cancellable);
+    NMDnsSystemdResolved        *self = NM_DNS_SYSTEMD_RESOLVED(object);
+    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
 
-    nm_clear_g_source_inst(&priv->try_start_timeout_source);
+    stop(NM_DNS_PLUGIN(self));
 
     g_clear_object(&priv->dbus_connection);
-    nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_unref);
+    nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_destroy);
 
     G_OBJECT_CLASS(nm_dns_systemd_resolved_parent_class)->dispose(object);
-
-    nm_clear_g_free(&priv->dbus_owner);
 }
 
 static void
@@ -1035,7 +1216,9 @@ nm_dns_systemd_resolved_class_init(NMDnsSystemdResolvedClass *dns_class)
 
     object_class->dispose = dispose;
 
-    plugin_class->plugin_name = "systemd-resolved";
-    plugin_class->is_caching  = TRUE;
-    plugin_class->update      = update;
+    plugin_class->plugin_name        = "systemd-resolved";
+    plugin_class->is_caching         = TRUE;
+    plugin_class->stop               = stop;
+    plugin_class->update             = update;
+    plugin_class->get_update_pending = get_update_pending;
 }