summary refs log tree commit diff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/dns/nm-dns-dnsconfd.c124
-rw-r--r--src/core/nm-l3cfg.c69
-rw-r--r--src/core/nm-manager.c164
-rw-r--r--src/core/nm-netns.c19
-rw-r--r--src/core/nm-netns.h4
-rw-r--r--src/core/nm-policy.c35
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.c13
7 files changed, 242 insertions, 186 deletions
diff --git a/src/core/dns/nm-dns-dnsconfd.c b/src/core/dns/nm-dns-dnsconfd.c
index b356c2f9..63b3060f 100644
--- a/src/core/dns/nm-dns-dnsconfd.c
+++ b/src/core/dns/nm-dns-dnsconfd.c
@@ -32,6 +32,8 @@ typedef struct {
     char            *name_owner;
     guint            name_owner_changed_id;
     GCancellable    *name_owner_cancellable;
+    bool             was_start_tried;
+    GCancellable    *start_cancellable;
     GVariant        *latest_update_args;
 
     guint         awaited_configuration_serial;
@@ -61,7 +63,11 @@ G_DEFINE_TYPE(NMDnsDnsconfd, nm_dns_dnsconfd, NM_TYPE_DNS_PLUGIN)
 
 #define DNSCONFD_DBUS_SERVICE "com.redhat.dnsconfd"
 
-typedef enum { CONNECTION_FAIL, CONNECTION_SUCCESS, CONNECTION_WAIT } ConnectionState;
+typedef enum {
+    CONNECTION_FAIL,
+    CONNECTION_SUCCESS,
+    CONNECTION_WAIT,
+} ConnectionState;
 
 /*****************************************************************************/
 
@@ -329,29 +335,8 @@ get_networks(NMDnsConfigIPData *ip_data, char ***networks)
 static void
 server_builder_append_interface_info(GVariantBuilder *argument_builder,
                                      const char      *interface,
-                                     char           **networks,
-                                     const char      *connection_id,
-                                     const char      *connection_uuid,
-                                     const char      *dbus_path)
+                                     char           **networks)
 {
-    if (connection_id) {
-        g_variant_builder_add(argument_builder,
-                              "{sv}",
-                              "connection-id",
-                              g_variant_new("s", connection_id));
-    }
-    if (connection_uuid) {
-        g_variant_builder_add(argument_builder,
-                              "{sv}",
-                              "connection-uuid",
-                              g_variant_new("s", connection_uuid));
-    }
-    if (dbus_path) {
-        g_variant_builder_add(argument_builder,
-                              "{sv}",
-                              "connection-object",
-                              g_variant_new("s", dbus_path));
-    }
     if (interface) {
         g_variant_builder_add(argument_builder, "{sv}", "interface", g_variant_new("s", interface));
     }
@@ -500,6 +485,7 @@ name_owner_changed(NMDnsDnsconfd *self, const char *name_owner)
     }
 
     _LOGT("D-Bus name for dnsconfd got owner %s", name_owner);
+    priv->was_start_tried = FALSE;
 
     if (!subscribe_serial(self)) {
         /* This means that in time between new name and subscribe serial call
@@ -544,6 +530,33 @@ get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data)
     name_owner_changed(user_data, name_owner);
 }
 
+static void
+dnsconfd_start_done(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    NMDnsDnsconfd             *self;
+    NMDnsDnsconfdPrivate      *priv;
+    gs_free_error GError      *error    = NULL;
+    gs_unref_variant GVariant *response = NULL;
+
+    response = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error);
+    if (nm_utils_error_is_cancelled(error))
+        return;
+
+    self = user_data;
+    priv = NM_DNS_DNSCONFD_GET_PRIVATE(self);
+    nm_clear_g_cancellable(&priv->start_cancellable);
+
+    if (!res) {
+        g_dbus_error_strip_remote_error(error);
+        _LOGW("failed to start Dnsconfd %s", error->message);
+    } else {
+        _LOGT("succesfully started Dnsconfd");
+    }
+
+    /* No update maybe changed or state change, as this is handled by the name owner callbacks
+     * which should be triggered right after this */
+}
+
 static ConnectionState
 ensure_all_connected(NMDnsDnsconfd *self)
 {
@@ -581,6 +594,19 @@ ensure_all_connected(NMDnsDnsconfd *self)
                                                self);
     }
 
+    if (!priv->was_start_tried) {
+        _LOGT("attempting to start Dnsconfd via DBus");
+        priv->was_start_tried = TRUE;
+        nm_clear_g_cancellable(&priv->start_cancellable);
+        priv->start_cancellable = g_cancellable_new();
+        nm_dbus_connection_call_start_service_by_name(priv->dbus_connection,
+                                                      DNSCONFD_DBUS_SERVICE,
+                                                      -1,
+                                                      priv->start_cancellable,
+                                                      dnsconfd_start_done,
+                                                      self);
+    }
+
     return CONNECTION_WAIT;
 }
 
@@ -589,18 +615,11 @@ parse_all_interface_config(GVariantBuilder *argument_builder,
                            const CList     *ip_data_lst_head,
                            const char      *ca)
 {
-    NMDnsConfigIPData    *ip_data;
-    const char *const    *dns_server_strings;
-    guint                 nameserver_count;
-    const char           *ifname;
-    NMDevice             *device;
-    NMActiveConnection   *active_connection;
-    NMSettingsConnection *settings_connection;
-    NMActRequest         *act_request;
-    const char           *connection_id;
-    const char           *connection_uuid;
-    const char           *dbus_path;
-    gboolean              explicit_default = is_default_interface_explicit(ip_data_lst_head);
+    NMDnsConfigIPData *ip_data;
+    const char *const *dns_server_strings;
+    guint              nameserver_count;
+    const char        *ifname;
+    gboolean           explicit_default = is_default_interface_explicit(ip_data_lst_head);
 
     c_list_for_each_entry (ip_data, ip_data_lst_head, ip_data_lst) {
         /* No need to free insides of routing and search domains, as they point to data
@@ -615,23 +634,7 @@ parse_all_interface_config(GVariantBuilder *argument_builder,
                                                                &nameserver_count);
         if (!nameserver_count)
             continue;
-        ifname      = nm_platform_link_get_name(NM_PLATFORM_GET, ip_data->data->ifindex);
-        device      = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, ip_data->data->ifindex);
-        act_request = nm_device_get_act_request(device);
-        active_connection = NM_ACTIVE_CONNECTION(act_request);
-
-        /* Presume that when we have server of this interface then the interface has to have
-         * an active connection */
-        nm_assert(active_connection);
-
-        settings_connection = nm_active_connection_get_settings_connection(active_connection);
-        connection_id       = nm_settings_connection_get_id(settings_connection);
-        connection_uuid     = nm_settings_connection_get_uuid(settings_connection);
-        dbus_path           = nm_dbus_object_get_path_still_exported(NM_DBUS_OBJECT(act_request));
-
-        /* dbus_path also should be set, because if we are parsing this connection then we
-         * expect it to be active and exported on dbus */
-        nm_assert(dbus_path && dbus_path[0] != 0);
+        ifname = nm_platform_link_get_name(NM_PLATFORM_GET, ip_data->data->ifindex);
 
         gather_interface_domains(ip_data, explicit_default, &routing_domains, &search_domains);
         get_networks(ip_data, &networks);
@@ -643,12 +646,7 @@ parse_all_interface_config(GVariantBuilder *argument_builder,
                                            routing_domains,
                                            search_domains,
                                            ca)) {
-                server_builder_append_interface_info(argument_builder,
-                                                     ifname,
-                                                     networks,
-                                                     connection_id,
-                                                     connection_uuid,
-                                                     dbus_path);
+                server_builder_append_interface_info(argument_builder, ifname, networks);
             }
         }
     }
@@ -696,7 +694,10 @@ update(NMDnsPlugin             *plugin,
 
     /* We need to consider only whether we are connected, because newer update call
      * overrides the old one */
-    if (all_connected != CONNECTION_SUCCESS) {
+    if (all_connected == CONNECTION_FAIL) {
+        priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
+        _LOGT("failed to connect");
+    } else if (all_connected == CONNECTION_WAIT) {
         priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT;
         _LOGT("not connected, waiting to connect");
     } else {
@@ -704,6 +705,8 @@ update(NMDnsPlugin             *plugin,
         _LOGT("connected, waiting for update to finish");
     }
 
+    _nm_dns_plugin_update_pending_maybe_changed(plugin);
+
     if (all_connected == CONNECTION_FAIL) {
         nm_utils_error_set(error,
                            NM_UTILS_ERROR_UNKNOWN,
@@ -717,8 +720,6 @@ update(NMDnsPlugin             *plugin,
 
     send_dnsconfd_update(self);
 
-    _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
-
     return TRUE;
 }
 
@@ -731,6 +732,7 @@ stop(NMDnsPlugin *plugin)
     nm_clear_g_cancellable(&priv->update_cancellable);
     nm_clear_g_cancellable(&priv->name_owner_cancellable);
     nm_clear_g_cancellable(&priv->serial_cancellable);
+    nm_clear_g_cancellable(&priv->start_cancellable);
     nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
     nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->properties_changed_id);
 }
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c
index fb48b860..a9aa506d 100644
--- a/src/core/nm-l3cfg.c
+++ b/src/core/nm-l3cfg.c
@@ -372,6 +372,8 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT)
 #define _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family) \
     ((gconstpointer) & (((char *) self)[1 + NM_IS_IPv4(addr_family)]))
 
+#define _NETNS_WATCHER_MPTCP_IPV6_TAG(self) ((gconstpointer) & (((char *) self)[3]))
+
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN      LOGD_CORE
@@ -4960,7 +4962,7 @@ next:
     }
 
 out:
-    nm_netns_watcher_remove_all(self->priv.netns, TAG, FALSE);
+    nm_netns_watcher_remove_dirty(self->priv.netns, TAG);
 }
 /*****************************************************************************/
 
@@ -4973,6 +4975,30 @@ _global_tracker_mptcp_untrack(NML3Cfg *self, int addr_family)
                                           TRUE);
 }
 
+static void
+mptcp_ipv6_addr_cb(NMNetns                       *netns,
+                   NMNetnsWatcherType             watcher_type,
+                   const NMNetnsWatcherData      *watcher_data,
+                   gconstpointer                  tag,
+                   const NMNetnsWatcherEventData *event_data,
+                   gpointer                       user_data)
+{
+    NML3Cfg *self = user_data;
+
+    if (event_data->ip_addr.change_type == NM_PLATFORM_SIGNAL_REMOVED)
+        return;
+
+    nm_assert(NMP_OBJECT_GET_TYPE(event_data->ip_addr.obj) == NMP_OBJECT_TYPE_IP6_ADDRESS);
+
+    if (event_data->ip_addr.obj->ip6_address.n_ifa_flags & IFA_F_TENTATIVE)
+        return;
+
+    /* We are inside the handler for a platform event, we should not
+     * perform other operations on platform synchronously. Schedule a
+     * commit in a idle handler. */
+    nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO);
+}
+
 static gboolean
 _l3_commit_mptcp_af(NML3Cfg          *self,
                     NML3CfgCommitType commit_type,
@@ -5051,6 +5077,8 @@ _l3_commit_mptcp_af(NML3Cfg          *self,
                                                         self->priv.p->combined_l3cd_commited,
                                                         addr_family,
                                                         (const NMPlatformIPAddress **) &addr) {
+                const NMPObject *obj;
+
                 /* We want to evaluate the  with-{loopback,link_local}-{4,6} flags based on the actual
                  * ifa_scope that the address will have once we configure it.
                  * "addr" is an address we want to configure, we expect that it will
@@ -5077,6 +5105,34 @@ _l3_commit_mptcp_af(NML3Cfg          *self,
                     break;
                 }
 
+                obj = nm_platform_ip_address_get(self->priv.platform,
+                                                 addr_family,
+                                                 self->priv.ifindex,
+                                                 addr);
+                if (!obj) {
+                    /* The address is not yet configured in platform, typically due to
+                     * IPv4 DAD; skip it for now otherwise the kernel will try to use
+                     * the endpoint, it will fail, and it will never try it again. */
+                    goto skip_addr;
+                }
+                if (!IS_IPv4 && (obj->ip6_address.n_ifa_flags & IFA_F_TENTATIVE)) {
+                    NMNetnsWatcherData watcher_data = {};
+
+                    /* The endpoint is not usable when the address is tentative.
+                     * Watch the address until it becomes non-tentative and then
+                     * schedule a new commit. */
+                    watcher_data.ip_addr.addr.addr_family = AF_INET6;
+                    watcher_data.ip_addr.addr.addr.addr6  = addr->a6.address;
+
+                    nm_netns_watcher_add(self->priv.netns,
+                                         NM_NETNS_WATCHER_TYPE_IP_ADDR,
+                                         &watcher_data,
+                                         _NETNS_WATCHER_MPTCP_IPV6_TAG(self),
+                                         mptcp_ipv6_addr_cb,
+                                         self);
+                    goto skip_addr;
+                }
+
                 a.addr = nm_ip_addr_init(addr_family, addr->ax.address_ptr);
 
                 /* We track the address with different priorities, that depends
@@ -5105,6 +5161,8 @@ skip_addr:
             }
         }
 
+        nm_netns_watcher_remove_dirty(self->priv.netns, _NETNS_WATCHER_MPTCP_IPV6_TAG(self));
+
         if (!any_tracked) {
             /* We need to make it known that this ifindex is used. Track a dummy object. */
             if (nmp_global_tracker_track(
@@ -5841,12 +5899,9 @@ finalize(GObject *object)
     gboolean changed;
 
     if (self->priv.netns) {
-        nm_netns_watcher_remove_all(self->priv.netns,
-                                    _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET),
-                                    TRUE);
-        nm_netns_watcher_remove_all(self->priv.netns,
-                                    _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET6),
-                                    TRUE);
+        nm_netns_watcher_remove_all(self->priv.netns, _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET));
+        nm_netns_watcher_remove_all(self->priv.netns, _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET6));
+        nm_netns_watcher_remove_all(self->priv.netns, _NETNS_WATCHER_MPTCP_IPV6_TAG(self));
     }
 
     nm_prioq_destroy(&self->priv.p->failedobj_prioq);
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index c9bcbd12..a861b2fe 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -2594,7 +2594,7 @@ nm_manager_remove_device(NMManager *self, const char *ifname, NMDeviceType devic
  * Returns: A #NMDevice that was just realized; %NULL if none
  */
 static NMDevice *
-system_create_virtual_device(NMManager *self, NMConnection *connection)
+system_create_virtual_device(NMManager *self, NMConnection *connection, GError **error)
 {
     NMManagerPrivate            *priv = NM_MANAGER_GET_PRIVATE(self);
     NMDeviceFactory             *factory;
@@ -2605,20 +2605,20 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
     NMDevice                    *device = NULL;
     NMDevice                    *parent = NULL;
     NMDevice                    *dev_candidate;
-    gs_free_error GError        *error = NULL;
-    NMLogLevel                   log_level;
 
     g_return_val_if_fail(NM_IS_MANAGER(self), NULL);
     g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
 
-    iface = nm_manager_get_connection_iface(self, connection, &parent, &parent_spec, &error);
-    if (!iface) {
-        _LOG3D(LOGD_DEVICE, connection, "can't get a name of a virtual device: %s", error->message);
+    iface = nm_manager_get_connection_iface(self, connection, &parent, &parent_spec, error);
+    if (!iface)
         return NULL;
-    }
 
     if (parent_spec && !parent) {
-        /* parent is not ready, wait */
+        g_set_error(error,
+                    NM_MANAGER_ERROR,
+                    NM_MANAGER_ERROR_DEPENDENCY_FAILED,
+                    "Parent device for '%s' not available",
+                    iface);
         return NULL;
     }
 
@@ -2626,7 +2626,11 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
     c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) {
         if (nm_device_check_connection_compatible(dev_candidate, connection, FALSE, NULL)) {
             if (nm_device_is_real(dev_candidate)) {
-                _LOG3D(LOGD_DEVICE, connection, "already created virtual interface name %s", iface);
+                g_set_error(error,
+                            NM_MANAGER_ERROR,
+                            NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+                            "Device named '%s' already exists",
+                            iface);
                 return NULL;
             }
 
@@ -2640,27 +2644,22 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
 
         factory = nm_device_factory_manager_find_factory_for_connection(connection);
         if (!factory) {
-            _LOG3E(LOGD_DEVICE,
-                   connection,
-                   "(%s) NetworkManager plugin for '%s' unavailable",
-                   iface,
-                   nm_connection_get_connection_type(connection));
+            g_set_error(error,
+                        NM_MANAGER_ERROR,
+                        NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+                        "'%s' plugin for '%s' unavailable",
+                        nm_connection_get_connection_type(connection),
+                        iface);
             return NULL;
         }
 
-        device = nm_device_factory_create_device(factory, iface, NULL, connection, NULL, &error);
-        if (!device) {
-            _LOG3W(LOGD_DEVICE, connection, "factory can't create the device: %s", error->message);
+        device = nm_device_factory_create_device(factory, iface, NULL, connection, NULL, error);
+        if (!device)
             return NULL;
-        }
 
         _LOG3D(LOGD_DEVICE, connection, "create virtual device %s", nm_device_get_iface(device));
 
-        if (!add_device(self, device, &error)) {
-            _LOG3W(LOGD_DEVICE,
-                   connection,
-                   "can't register the device with manager: %s",
-                   error->message);
+        if (!add_device(self, device, error)) {
             g_object_unref(device);
             return NULL;
         }
@@ -2679,10 +2678,8 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
         return device;
     }
 
-    if (!find_controller(self, connection, device, NULL, NULL, NULL, &error)) {
-        _LOG3D(LOGD_DEVICE, connection, "skip activation: %s", error->message);
+    if (!find_controller(self, connection, device, NULL, NULL, NULL, error))
         return device;
-    }
 
     /* Create backing resources if the device has any autoconnect connections */
     connections = nm_settings_get_connections_sorted_by_autoconnect_priority(priv->settings, NULL);
@@ -2699,18 +2696,8 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
             continue;
 
         /* Create any backing resources the device needs */
-        if (!nm_device_create_and_realize(device, connection, parent, &error)) {
-            log_level =
-                g_error_matches(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES)
-                    ? LOGL_DEBUG
-                    : LOGL_ERR;
-            _NMLOG3(log_level,
-                    LOGD_DEVICE,
-                    connection,
-                    "couldn't create the device: %s",
-                    error->message);
+        if (!nm_device_create_and_realize(device, connection, parent, error))
             return NULL;
-        }
 
         retry_connections_for_parent_device(self, device);
         break;
@@ -2751,8 +2738,9 @@ retry_connections_for_parent_device(NMManager *self, NMDevice *device)
 static void
 connection_changed(NMManager *self, NMSettingsConnection *sett_conn)
 {
-    NMConnection *connection;
-    NMDevice     *device;
+    NMConnection         *connection;
+    NMDevice             *device;
+    gs_free_error GError *error = NULL;
 
     if (NM_FLAGS_ANY(nm_settings_connection_get_flags(sett_conn),
                      NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
@@ -2764,9 +2752,11 @@ connection_changed(NMManager *self, NMSettingsConnection *sett_conn)
     if (!nm_connection_is_virtual(connection))
         return;
 
-    device = system_create_virtual_device(self, connection);
-    if (!device)
+    device = system_create_virtual_device(self, connection, &error);
+    if (!device) {
+        _LOG3D(LOGD_DEVICE, connection, "Can't create a virtual device: %s", error->message);
         return;
+    }
 
     /* Maybe the device that was created was needed by some other
      * connection's device (parent of a VLAN). Let the connections
@@ -4699,10 +4689,16 @@ found_better:
         if (nm_g_hash_table_contains(exclude_devices, device))
             continue;
 
-        if (!nm_device_is_available(device,
-                                    for_user_request
-                                        ? NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST
-                                        : NM_DEVICE_CHECK_DEV_AVAILABLE_NONE))
+        /* During startup, NM performs a cleanup of the ovsdb to remove previous entries.
+         * Before the device is suitable for the connection, it must have ovsdb->ready set
+         * to TRUE. Performing this check in all kind of interfaces is too agressive and leads
+         * to race conditions, e.g when a non-virtual bond port gets a carrier, preventing the
+         * device to be a good candidate for the connection. */
+        if (nm_device_get_device_type(device) == NM_DEVICE_TYPE_OVS_INTERFACE
+            && !nm_device_is_available(device,
+                                       for_user_request
+                                           ? NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST
+                                           : NM_DEVICE_CHECK_DEV_AVAILABLE_NONE))
             continue;
 
         /* determine the priority of this device. Currently, this priority is independent
@@ -6542,18 +6538,9 @@ find_device_for_activation(NMManager            *self,
                 return FALSE;
 
             device = find_device_by_iface(self, iface, connection, NULL, NULL);
-            if (!device) {
-                g_set_error_literal(error,
-                                    NM_MANAGER_ERROR,
-                                    NM_MANAGER_ERROR_UNKNOWN_DEVICE,
-                                    "Failed to find a compatible device for this connection");
-                return FALSE;
-            }
         }
     }
 
-    nm_assert(is_vpn || NM_IS_DEVICE(device));
-
     *out_device = device;
     *out_is_vpn = is_vpn;
 
@@ -6678,7 +6665,14 @@ impl_manager_activate_connection(NMDBusObject                      *obj,
     if (!subject)
         goto error;
 
-    if (!find_device_for_activation(self, sett_conn, NULL, device_path, &device, &is_vpn, &error)) {
+    if (!find_device_for_activation(self, sett_conn, NULL, device_path, &device, &is_vpn, &error))
+        goto error;
+
+    if (!device && !is_vpn) {
+        g_set_error_literal(&error,
+                            NM_MANAGER_ERROR,
+                            NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+                            "Failed to find a compatible device for this connection");
         goto error;
     }
 
@@ -6801,6 +6795,7 @@ _add_and_activate_auth_done(NMManager                      *self,
                             const char                     *error_desc)
 {
     NMManagerPrivate *priv;
+    NMDevice         *device;
     GError           *error = NULL;
 
     if (!success) {
@@ -6813,6 +6808,14 @@ _add_and_activate_auth_done(NMManager                      *self,
                                    nm_active_connection_get_subject(active),
                                    error->message);
         g_dbus_method_invocation_take_error(invocation, error);
+
+        device = nm_active_connection_get_device(active);
+        if (device && nm_device_is_software(device)
+            && !nm_device_managed_type_is_external_or_assume(device)
+            && _check_remove_dev_on_link_deleted(self, device)) {
+            remove_device(self, device, FALSE);
+        }
+
         return;
     }
 
@@ -6968,32 +6971,11 @@ impl_manager_add_and_activate_connection(NMDBusObject                      *obj,
         goto error;
     }
 
-    if (is_vpn) {
-        /* Try to fill the VPN's connection setting and name at least */
-        if (!nm_connection_get_setting_vpn(incompl_conn)) {
-            error = g_error_new_literal(NM_CONNECTION_ERROR,
-                                        NM_CONNECTION_ERROR_MISSING_SETTING,
-                                        "VPN connections require a 'vpn' setting");
-            g_prefix_error(&error, "%s: ", NM_SETTING_VPN_SETTING_NAME);
-            goto error;
-        }
-
-        conns = nm_settings_connections_array_to_connections(
-            nm_settings_get_connections(priv->settings, NULL),
-            -1);
+    conns = nm_settings_connections_array_to_connections(
+        nm_settings_get_connections(priv->settings, NULL),
+        -1);
 
-        nm_utils_complete_generic(priv->platform,
-                                  incompl_conn,
-                                  NM_SETTING_VPN_SETTING_NAME,
-                                  conns,
-                                  NULL,
-                                  _("VPN connection"),
-                                  NULL,
-                                  NULL);
-    } else {
-        conns = nm_settings_connections_array_to_connections(
-            nm_settings_get_connections(priv->settings, NULL),
-            -1);
+    if (device) {
         /* Let each device subclass complete the connection */
         if (!nm_device_complete_connection(device,
                                            incompl_conn,
@@ -7001,9 +6983,27 @@ impl_manager_add_and_activate_connection(NMDBusObject                      *obj,
                                            conns,
                                            &error))
             goto error;
-    }
+    } else {
+        nm_utils_complete_generic(priv->platform,
+                                  incompl_conn,
+                                  nm_connection_get_connection_type(incompl_conn),
+                                  conns,
+                                  is_vpn ? _("VPN connection") : NULL,
+                                  nm_connection_get_connection_type(incompl_conn),
+                                  NULL,
+                                  NULL);
+
+        if (!nm_connection_verify(incompl_conn, &error))
+            goto error;
+
+        if (!is_vpn && !device) {
+            nm_assert(nm_connection_is_virtual(incompl_conn));
 
-    nm_assert(_nm_connection_verify(incompl_conn, NULL) == NM_SETTING_VERIFY_SUCCESS);
+            device = system_create_virtual_device(self, incompl_conn, &error);
+            if (!device)
+                goto error;
+        }
+    }
 
     active = _new_active_connection(self,
                                     is_vpn,
diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c
index 420d01e4..57dbe9c5 100644
--- a/src/core/nm-netns.c
+++ b/src/core/nm-netns.c
@@ -1369,8 +1369,8 @@ nm_netns_watcher_remove_handle(NMNetns *self, NMNetnsWatcherHandle *handle)
         g_object_unref(self);
 }
 
-void
-nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all)
+static void
+watcher_remove(NMNetns *self, gconstpointer tag, gboolean all)
 {
     NMNetnsPrivate       *priv;
     WatcherByTag         *watcher_by_tag;
@@ -1420,6 +1420,21 @@ nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all)
     }
 }
 
+void
+nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag)
+{
+    watcher_remove(self, tag, TRUE);
+}
+
+/* Similar to nm_netns_watcher_remove_all(), but removes only watchers
+ * that were marked as "dirty" in a previous call of this function and were
+ * not added back via nm_netns_watcher_add() in the meantime. */
+void
+nm_netns_watcher_remove_dirty(NMNetns *self, gconstpointer tag)
+{
+    watcher_remove(self, tag, FALSE);
+}
+
 /*****************************************************************************/
 
 static void
diff --git a/src/core/nm-netns.h b/src/core/nm-netns.h
index 7725ae79..43e9c781 100644
--- a/src/core/nm-netns.h
+++ b/src/core/nm-netns.h
@@ -98,7 +98,7 @@ void nm_netns_watcher_add(NMNetns                  *self,
                           NMNetnsWatcherCallback    callback,
                           gpointer                  user_data);
 
-void
-nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all /* or only dirty */);
+void nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag);
+void nm_netns_watcher_remove_dirty(NMNetns *self, gconstpointer tag);
 
 #endif /* __NM_NETNS_H__ */
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
index f86d8115..33073e4a 100644
--- a/src/core/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -1873,8 +1873,7 @@ unblock_autoconnect_for_children(NMPolicy   *self,
                                  const char *parent_device,
                                  const char *parent_uuid_settings,
                                  const char *parent_uuid_applied,
-                                 const char *parent_mac_addr,
-                                 gboolean    reset_devcon_autoconnect)
+                                 const char *parent_mac_addr)
 {
     NMPolicyPrivate             *priv = NM_POLICY_GET_PRIVATE(self);
     NMSettingsConnection *const *connections;
@@ -1915,10 +1914,8 @@ unblock_autoconnect_for_children(NMPolicy   *self,
                           parent_mac_addr))
             continue;
 
-        if (reset_devcon_autoconnect) {
-            if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn))
-                changed = TRUE;
-        }
+        if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn))
+            changed = TRUE;
 
         /* unblock the devices associated with that connection */
         if (nm_manager_devcon_autoconnect_blocked_reason_set(
@@ -1940,12 +1937,11 @@ static void
 unblock_autoconnect_for_ports(NMPolicy   *self,
                               const char *controller_device,
                               const char *controller_uuid_settings,
-                              const char *controller_uuid_applied,
-                              gboolean    reset_devcon_autoconnect)
+                              const char *controller_uuid_applied)
 {
     NMPolicyPrivate             *priv = NM_POLICY_GET_PRIVATE(self);
     NMSettingsConnection *const *connections;
-    gboolean                     changed;
+    gboolean                     changed = FALSE;
     guint                        i;
 
     _LOGT(LOGD_CORE,
@@ -1959,7 +1955,6 @@ unblock_autoconnect_for_ports(NMPolicy   *self,
                               "\"",
                               ""));
 
-    changed     = FALSE;
     connections = nm_settings_get_connections(priv->settings, NULL);
     for (i = 0; connections[i]; i++) {
         NMSettingsConnection *sett_conn = connections[i];
@@ -1977,10 +1972,8 @@ unblock_autoconnect_for_ports(NMPolicy   *self,
                           controller_uuid_settings))
             continue;
 
-        if (reset_devcon_autoconnect) {
-            if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn))
-                changed = TRUE;
-        }
+        if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn))
+            changed = TRUE;
 
         /* unblock the devices associated with that connection */
         if (nm_manager_devcon_autoconnect_blocked_reason_set(
@@ -2015,7 +2008,7 @@ unblock_autoconnect_for_ports_for_sett_conn(NMPolicy *self, NMSettingsConnection
     controller_uuid_settings = nm_setting_connection_get_uuid(s_con);
     controller_device        = nm_setting_connection_get_interface_name(s_con);
 
-    unblock_autoconnect_for_ports(self, controller_device, controller_uuid_settings, NULL, TRUE);
+    unblock_autoconnect_for_ports(self, controller_device, controller_uuid_settings, NULL);
 }
 
 static void
@@ -2028,7 +2021,6 @@ activate_port_or_children_connections(NMPolicy *self,
     const char   *controller_uuid_applied  = NULL;
     const char   *parent_mac_addr          = NULL;
     NMActRequest *req;
-    gboolean      internal_activation = FALSE;
 
     controller_device = nm_device_get_iface(device);
     nm_assert(controller_device);
@@ -2039,7 +2031,6 @@ activate_port_or_children_connections(NMPolicy *self,
     if (req) {
         NMConnection         *connection;
         NMSettingsConnection *sett_conn;
-        NMAuthSubject        *subject;
 
         sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req));
         if (sett_conn)
@@ -2051,25 +2042,19 @@ activate_port_or_children_connections(NMPolicy *self,
 
         if (nm_streq0(controller_uuid_settings, controller_uuid_applied))
             controller_uuid_applied = NULL;
-
-        subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req));
-        internal_activation =
-            subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL);
     }
 
     if (!activate_children_connections_only) {
         unblock_autoconnect_for_ports(self,
                                       controller_device,
                                       controller_uuid_settings,
-                                      controller_uuid_applied,
-                                      !internal_activation);
+                                      controller_uuid_applied);
     }
     unblock_autoconnect_for_children(self,
                                      controller_device,
                                      controller_uuid_settings,
                                      controller_uuid_applied,
-                                     parent_mac_addr,
-                                     !internal_activation);
+                                     parent_mac_addr);
 }
 
 static gboolean
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
index 1679cab6..681eef85 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -880,12 +880,11 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
                                      NMConnection      **out_connection,
                                      GError            **error)
 {
-    NMSKeyfilePluginPrivate      *priv             = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
-    NMSKeyfileStorage            *storage          = NMS_KEYFILE_STORAGE(storage_x);
-    gs_unref_object NMConnection *connection_clone = NULL;
-    gs_unref_object NMConnection *reread           = NULL;
-    gs_free char                 *full_filename    = NULL;
-    gs_free_error GError         *local            = NULL;
+    NMSKeyfilePluginPrivate      *priv          = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    NMSKeyfileStorage            *storage       = NMS_KEYFILE_STORAGE(storage_x);
+    gs_unref_object NMConnection *reread        = NULL;
+    gs_free char                 *full_filename = NULL;
+    gs_free_error GError         *local         = NULL;
     struct timespec               mtime;
     const char                   *previous_filename;
     gboolean                      reread_same;
@@ -946,7 +945,7 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
             &local)) {
         _LOGW("commit: failure to write %s (%s) to \"%s\": %s",
               uuid,
-              nm_connection_get_id(connection_clone),
+              nm_connection_get_id(connection),
               previous_filename,
               local->message);
         g_propagate_error(error, g_steal_pointer(&local));