about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/devices/nm-device-ppp.c9
-rw-r--r--src/devices/nm-device.c154
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c2
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c47
-rw-r--r--src/devices/ovs/nm-ovsdb.c32
-rw-r--r--src/dhcp/nm-dhcp-listener.c2
-rw-r--r--src/dns/nm-dns-manager.c10
-rw-r--r--src/nm-active-connection.c33
-rw-r--r--src/nm-active-connection.h4
-rw-r--r--src/nm-manager.c142
-rw-r--r--src/nm-policy.c10
-rw-r--r--src/platform/nm-linux-platform.c9
-rw-r--r--src/platform/nmp-netns.c6
-rw-r--r--src/platform/tests/test-common.c37
-rw-r--r--src/platform/tests/test-link.c17
-rw-r--r--src/platform/tests/test-route.c44
-rw-r--r--src/ppp/nm-ppp-manager.c12
-rw-r--r--src/ppp/nm-pppd-plugin.c19
-rw-r--r--src/settings/nm-settings-connection.c14
-rw-r--r--src/settings/nm-settings.c25
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c2
22 files changed, 378 insertions, 254 deletions
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index 8b3968d5..639ec44a 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -207,6 +207,14 @@ act_stage3_ip4_config_start (NMDevice *device,
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
+static NMActStageReturn
+act_stage3_ip6_config_start (NMDevice *self,
+                             NMIP6Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
 static gboolean
 create_and_realize (NMDevice *device,
                     NMConnection *connection,
@@ -273,6 +281,7 @@ nm_device_ppp_class_init (NMDevicePppClass *klass)
 
 	parent_class->act_stage2_config = act_stage2_config;
 	parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	parent_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	parent_class->check_connection_compatible = check_connection_compatible;
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->deactivate = deactivate;
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 6f9289ee..b8828d14 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -3940,21 +3940,24 @@ get_ip_config_may_fail (NMDevice *self, int addr_family)
 /*
  * check_ip_state
  *
- * Transition the device from IP_CONFIG to the next state according to the
- * outcome of IPv4 and IPv6 configuration. @may_fail indicates that we are
- * called just after the initial configuration and thus IPv4/IPv6 are allowed to
- * fail if the ipvx.may-fail properties say so, because the IP methods couldn't
- * even be started.
+ * When @full_state_update is TRUE, transition the device from IP_CONFIG to the
+ * next state according to the outcome of IPv4 and IPv6 configuration. @may_fail
+ * indicates that we are called just after the initial configuration and thus
+ * IPv4/IPv6 are allowed to fail if the ipvx.may-fail properties say so, because
+ * the IP methods couldn't even be started.
+ * If @full_state_update is FALSE, just check if the connection should be failed
+ * due to the state of both ip families and the ipvx.may-fail settings.
  */
 static void
-check_ip_state (NMDevice *self, gboolean may_fail)
+check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean ip4_disabled = FALSE, ip6_ignore = FALSE;
 	NMSettingIPConfig *s_ip4, *s_ip6;
 	NMDeviceState state;
 
-	if (nm_device_get_state (self) != NM_DEVICE_STATE_IP_CONFIG)
+	if (   full_state_update
+	    && nm_device_get_state (self) != NM_DEVICE_STATE_IP_CONFIG)
 		return;
 
 	/* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the
@@ -4001,9 +4004,12 @@ check_ip_state (NMDevice *self, gboolean may_fail)
 			state = NM_DEVICE_STATE_FAILED;
 		}
 
-		nm_device_state_changed (self,
-		                         state,
-		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		if (   full_state_update
+		    || state == NM_DEVICE_STATE_FAILED) {
+			nm_device_state_changed (self,
+			                         state,
+			                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		}
 		return;
 	}
 
@@ -4016,7 +4022,8 @@ check_ip_state (NMDevice *self, gboolean may_fail)
 	/* If at least a method has completed, proceed with activation */
 	if (   (priv->ip4_state == IP_DONE && !ip4_disabled)
 	    || (priv->ip6_state == IP_DONE && !ip6_ignore)) {
-		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
+		if (full_state_update)
+			nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 		return;
 	}
 }
@@ -4058,7 +4065,7 @@ nm_device_slave_notify_enslave (NMDevice *self, gboolean success)
 
 	if (activating) {
 		if (success)
-			check_ip_state (self, FALSE);
+			check_ip_state (self, FALSE, TRUE);
 		else
 			nm_device_queue_state (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_UNKNOWN);
 	} else
@@ -5434,7 +5441,7 @@ nm_device_ip_method_failed (NMDevice *self,
 	_set_ip_state (self, addr_family, IP_FAIL);
 
 	if (get_ip_config_may_fail (self, addr_family))
-		check_ip_state (self, FALSE);
+		check_ip_state (self, FALSE, (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG));
 	else
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 }
@@ -6059,15 +6066,23 @@ dhcp4_fail (NMDevice *self, gboolean timeout)
 	    && (timeout || (priv->ip4_state == IP_CONF))
 	    && !priv->dhcp4.was_active)
 		nm_device_activate_schedule_ip4_config_timeout (self);
-	else if (priv->ip4_state == IP_DONE || priv->dhcp4.was_active) {
+	else if (   priv->dhcp4.num_tries_left < DHCP_NUM_TRIES_MAX
+	         || priv->ip4_state == IP_DONE
+	         || priv->dhcp4.was_active) {
 		/* Don't fail immediately when the lease expires but try to
 		 * restart DHCP for a predefined number of times.
 		 */
 		if (priv->dhcp4.num_tries_left) {
 			priv->dhcp4.num_tries_left--;
 			dhcp_schedule_restart (self, AF_INET, "lease expired");
-		} else
+		} else {
 			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+			/* We failed the ipv4 method but schedule again the retries if the ipv6 method is
+			 * configured, keeping the connection up.
+			 */
+			if (nm_device_get_state (self) != NM_DEVICE_STATE_FAILED)
+				dhcp_schedule_restart (self, AF_INET, "renewal failed");
+		}
 	} else
 		g_warn_if_reached ();
 }
@@ -6109,6 +6124,12 @@ dhcp4_state_changed (NMDhcpClient *client,
 			break;
 		}
 
+		/* After some failures, we have been able to renew the lease:
+		 * update the ip state
+		 */
+		if (priv->ip4_state == IP_FAIL)
+			_set_ip_state (self, AF_INET, IP_CONF);
+
 		g_free (priv->dhcp4.pac_url);
 		priv->dhcp4.pac_url = g_strdup (g_hash_table_lookup (options, "wpad"));
 		nm_device_set_proxy_config (self, priv->dhcp4.pac_url);
@@ -6778,13 +6799,15 @@ static void
 dhcp6_fail (NMDevice *self, gboolean timeout)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean is_dhcp_managed;
 
 	_LOGD (LOGD_DHCP6, "DHCPv6 failed: timeout %d, num tries left %u",
            timeout, priv->dhcp6.num_tries_left);
 
+	is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
 	dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 
-	if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED) {
+	if (is_dhcp_managed || priv->dhcp6.num_tries_left < DHCP_NUM_TRIES_MAX) {
 		/* Don't fail if there are static addresses configured on
 		 * the device, instead retry after some time.
 		 */
@@ -6799,15 +6822,23 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 		    && (timeout || (priv->ip6_state == IP_CONF))
 		    && !priv->dhcp6.was_active)
 			nm_device_activate_schedule_ip6_config_timeout (self);
-		else if (priv->ip6_state == IP_DONE || priv->dhcp6.was_active) {
+		else if (   priv->dhcp6.num_tries_left < DHCP_NUM_TRIES_MAX
+		         || priv->ip6_state == IP_DONE
+		         || priv->dhcp6.was_active) {
 			/* Don't fail immediately when the lease expires but try to
 			 * restart DHCP for a predefined number of times.
 			 */
 			if (priv->dhcp6.num_tries_left) {
 				priv->dhcp6.num_tries_left--;
 				dhcp_schedule_restart (self, AF_INET6, "lease expired");
-			} else
+			} else {
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+				/* We failed the ipv6 method but schedule again the retries if the ipv4 method is
+				 * configured, keeping the connection up.
+				 */
+				if (nm_device_get_state (self) != NM_DEVICE_STATE_FAILED)
+					dhcp_schedule_restart (self, AF_INET6, "renewal failed");
+			}
 		} else
 			g_warn_if_reached ();
 	} else {
@@ -6874,6 +6905,12 @@ dhcp6_state_changed (NMDhcpClient *client,
 			}
 		}
 
+		/* After long time we have been able to renew the lease:
+		 * update the ip state
+		 */
+		if (priv->ip6_state == IP_FAIL)
+			_set_ip_state (self, AF_INET6, IP_CONF);
+
 		priv->dhcp6.num_tries_left = DHCP_NUM_TRIES_MAX;
 
 		if (priv->ip6_state == IP_CONF) {
@@ -7506,7 +7543,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		gboolean anticipated_failure = FALSE;
 
 		if (!priv->mtu_initial && !priv->ip6_mtu_initial) {
-			/* before touching any of the MTU paramters, record the
+			/* before touching any of the MTU parameters, record the
 			 * original setting to restore on deactivation. */
 			priv->mtu_initial = mtu_plat;
 			priv->ip6_mtu_initial = _IP6_MTU_SYS ();
@@ -8163,7 +8200,7 @@ nm_device_activate_stage3_ip4_start (NMDevice *self)
 
 	if (nm_device_sys_iface_state_is_external (self)) {
 		_set_ip_state (self, AF_INET, IP_DONE);
-		check_ip_state (self, FALSE);
+		check_ip_state (self, FALSE, TRUE);
 		return TRUE;
 	}
 
@@ -8176,7 +8213,7 @@ nm_device_activate_stage3_ip4_start (NMDevice *self)
 		g_object_unref (ip4_config);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
 		_set_ip_state (self, AF_INET, IP_DONE);
-		check_ip_state (self, FALSE);
+		check_ip_state (self, FALSE, TRUE);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 		return FALSE;
@@ -8210,7 +8247,7 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 
 	if (nm_device_sys_iface_state_is_external (self)) {
 		_set_ip_state (self, AF_INET6, IP_DONE);
-		check_ip_state (self, FALSE);
+		check_ip_state (self, FALSE, TRUE);
 		return TRUE;
 	}
 
@@ -8227,7 +8264,7 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 		nm_device_activate_schedule_ip6_config_result (self);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
 		_set_ip_state (self, AF_INET6, IP_DONE);
-		check_ip_state (self, FALSE);
+		check_ip_state (self, FALSE, TRUE);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 		return FALSE;
@@ -8277,7 +8314,7 @@ activate_stage3_ip_config_start (NMDevice *self)
 	/* Proxy */
 	nm_device_set_proxy_config (self, NULL);
 
-	check_ip_state (self, TRUE);
+	check_ip_state (self, TRUE, TRUE);
 }
 
 static void
@@ -8416,7 +8453,7 @@ activate_stage4_ip4_config_timeout (NMDevice *self)
 
 	_set_ip_state (self, AF_INET, IP_FAIL);
 
-	check_ip_state (self, FALSE);
+	check_ip_state (self, FALSE, TRUE);
 }
 
 /*
@@ -8472,7 +8509,7 @@ activate_stage4_ip6_config_timeout (NMDevice *self)
 
 	_set_ip_state (self, AF_INET6, IP_FAIL);
 
-	check_ip_state (self, FALSE);
+	check_ip_state (self, FALSE, TRUE);
 }
 
 /*
@@ -8720,7 +8757,7 @@ activate_stage5_ip4_config_result (NMDevice *self)
 
 	/* Enter the IP_CHECK state if this is the first method to complete */
 	_set_ip_state (self, AF_INET, IP_DONE);
-	check_ip_state (self, FALSE);
+	check_ip_state (self, FALSE, TRUE);
 }
 
 void
@@ -8875,7 +8912,7 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 				_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination");
 			} else {
 				_set_ip_state (self, AF_INET6, IP_DONE);
-				check_ip_state (self, FALSE);
+				check_ip_state (self, FALSE, TRUE);
 			}
 		}
 	} else {
@@ -9879,10 +9916,12 @@ static void
 _clear_queued_act_request (NMDevicePrivate *priv)
 {
 	if (priv->queued_act_request) {
-		nm_active_connection_set_state ((NMActiveConnection *) priv->queued_act_request,
-		                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
-		                                NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
-		g_clear_object (&priv->queued_act_request);
+		gs_unref_object NMActRequest *ac = NULL;
+
+		ac = g_steal_pointer (&priv->queued_act_request);
+		nm_active_connection_set_state_fail ((NMActiveConnection *) ac,
+		                                     NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED,
+		                                     NULL);
 	}
 }
 
@@ -9959,26 +9998,34 @@ impl_device_delete (NMDevice *self, GDBusMethodInvocation *context)
 	               NULL);
 }
 
-static gboolean
+static void
 _device_activate (NMDevice *self, NMActRequest *req)
 {
-	NMDevicePrivate *priv;
 	NMConnection *connection;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-	g_return_val_if_fail (NM_IS_ACT_REQUEST (req), FALSE);
-	g_return_val_if_fail (nm_device_get_managed (self, FALSE), FALSE);
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+	nm_assert (nm_device_is_real (self));
 
 	/* Ensure the activation request is still valid; the master may have
 	 * already failed in which case activation of this device should not proceed.
 	 */
 	if (nm_active_connection_get_state (NM_ACTIVE_CONNECTION (req)) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)
-		return FALSE;
+		return;
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
+	if (!nm_device_get_managed (self, FALSE)) {
+		/* It's unclear why the device would be unmanaged at this point.
+		 * Just to be sure, handle it and error out. */
+		_LOGE (LOGD_DEVICE, "Activation: failed activating connection '%s' because device is still unmanaged",
+		       nm_active_connection_get_settings_connection_id ((NMActiveConnection *) req));
+		nm_active_connection_set_state_fail ((NMActiveConnection *) req,
+		                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
+		                                     NULL);
+		return;
+	}
 
 	connection = nm_act_request_get_applied_connection (req);
-	g_assert (connection);
+	nm_assert (connection);
 
 	_LOGI (LOGD_DEVICE, "Activation: starting connection '%s' (%s)",
 	       nm_connection_get_id (connection),
@@ -9989,14 +10036,12 @@ _device_activate (NMDevice *self, NMActRequest *req)
 	act_request_set (self, req);
 
 	nm_device_activate_schedule_stage1_device_prepare (self);
-	return TRUE;
 }
 
 static void
 _carrier_wait_check_queued_act_request (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActRequest *queued_req;
 
 	if (   !priv->queued_act_request
 	    || !priv->queued_act_request_is_waiting_for_carrier)
@@ -10007,11 +10052,11 @@ _carrier_wait_check_queued_act_request (NMDevice *self)
 		_LOGD (LOGD_DEVICE, "Cancel queued activation request as we have no carrier after timeout");
 		_clear_queued_act_request (priv);
 	} else {
+		gs_unref_object NMActRequest *queued_req = NULL;
+
 		_LOGD (LOGD_DEVICE, "Activate queued activation request as we now have carrier");
-		queued_req = priv->queued_act_request;
-		priv->queued_act_request = NULL;
+		queued_req = g_steal_pointer (&priv->queued_act_request);
 		_device_activate (self, queued_req);
-		g_object_unref (queued_req);
 	}
 }
 
@@ -10071,10 +10116,11 @@ nm_device_steal_connection (NMDevice *self, NMSettingsConnection *connection)
 
 	if (   priv->act_request
 	    && connection == nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (priv->act_request))
-	    && priv->state < NM_DEVICE_STATE_DEACTIVATING)
+	    && priv->state < NM_DEVICE_STATE_DEACTIVATING) {
 		nm_device_state_changed (self,
 		                         NM_DEVICE_STATE_DEACTIVATING,
 		                         NM_DEVICE_STATE_REASON_NEW_ACTIVATION);
+	}
 }
 
 void
@@ -10085,10 +10131,10 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 
 	must_queue = _carrier_wait_check_act_request_must_queue (self, req);
 
-	if (!priv->act_request && !must_queue && nm_device_is_real (self)) {
-		/* Just activate immediately */
-		if (!_device_activate (self, req))
-			g_assert_not_reached ();
+	if (   !priv->act_request
+	    && !must_queue
+	    && nm_device_is_real (self)) {
+		_device_activate (self, req);
 		return;
 	}
 
@@ -11369,7 +11415,7 @@ queued_ip6_config_change (gpointer user_data)
 			_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated");
 			g_clear_object (&priv->dad6_ip6_config);
 			_set_ip_state (self, AF_INET6, IP_DONE);
-			check_ip_state (self, FALSE);
+			check_ip_state (self, FALSE, TRUE);
 			if (priv->rt6_temporary_not_available)
 				nm_device_activate_schedule_ip6_config_result (self);
 		}
@@ -13199,12 +13245,10 @@ _set_state_full (NMDevice *self,
 	case NM_DEVICE_STATE_DISCONNECTED:
 		if (   priv->queued_act_request
 		    && !priv->queued_act_request_is_waiting_for_carrier) {
-			NMActRequest *queued_req;
+			gs_unref_object NMActRequest *queued_req = NULL;
 
-			queued_req = priv->queued_act_request;
-			priv->queued_act_request = NULL;
+			queued_req = g_steal_pointer (&priv->queued_act_request);
 			_device_activate (self, queued_req);
-			g_object_unref (queued_req);
 		}
 		break;
 	case NM_DEVICE_STATE_ACTIVATED:
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index e746a3fd..ce32c2dd 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -50,7 +50,7 @@ struct _NMDeviceOvsInterfaceClass {
 
 G_DEFINE_TYPE (NMDeviceOvsInterface, nm_device_ovs_interface, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceOvsInterface, NM_IS_DEVICE_OVS_INTERFACE)
+#define NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceOvsInterface, NM_IS_DEVICE_OVS_INTERFACE, NMDevice)
 
 /*****************************************************************************/
 
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index 83199f2d..cb0915af 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -114,12 +114,13 @@ add_iface_cb (GError *error, gpointer user_data)
 {
 	NMDevice *slave = user_data;
 
-	if (error) {
-	        nm_log_warn (LOGD_DEVICE, "device %s could not be added to a ovs port: %s",
+	if (   error
+	    && !g_error_matches (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
+		nm_log_warn (LOGD_DEVICE, "device %s could not be added to a ovs port: %s",
 		             nm_device_get_iface (slave), error->message);
-	        nm_device_state_changed (slave,
-	                                 NM_DEVICE_STATE_FAILED,
-	                                 NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+		nm_device_state_changed (slave,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_OVSDB_FAILED);
 	}
 
 	g_object_unref (slave);
@@ -128,23 +129,22 @@ add_iface_cb (GError *error, gpointer user_data)
 static gboolean
 enslave_slave (NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
 {
-        NMActiveConnection *ac_port = NULL;
-        NMActiveConnection *ac_bridge = NULL;
+	NMActiveConnection *ac_port = NULL;
+	NMActiveConnection *ac_bridge = NULL;
 
 	if (!configure)
 		return TRUE;
 
+	ac_port = NM_ACTIVE_CONNECTION (nm_device_get_act_request (device));
+	ac_bridge = nm_active_connection_get_master (ac_port);
+	if (!ac_bridge)
+		ac_bridge = ac_port;
 
-        ac_port = NM_ACTIVE_CONNECTION (nm_device_get_act_request (device));
-        ac_bridge = nm_active_connection_get_master (ac_port);
-        if (!ac_bridge)
-                ac_bridge = ac_port;
-
-        nm_ovsdb_add_interface (nm_ovsdb_get (),
-                                nm_active_connection_get_applied_connection (ac_bridge),
-                                nm_device_get_applied_connection (device),
-                                nm_device_get_applied_connection (slave),
-                                add_iface_cb, g_object_ref (slave));
+	nm_ovsdb_add_interface (nm_ovsdb_get (),
+	                        nm_active_connection_get_applied_connection (ac_bridge),
+	                        nm_device_get_applied_connection (device),
+	                        nm_device_get_applied_connection (slave),
+	                        add_iface_cb, g_object_ref (slave));
 
 	return TRUE;
 }
@@ -154,12 +154,13 @@ del_iface_cb (GError *error, gpointer user_data)
 {
 	NMDevice *slave = user_data;
 
-	if (error) {
-	        nm_log_warn (LOGD_DEVICE, "device %s could not be removed from a ovs port: %s",
+	if (   error
+	    && !g_error_matches (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
+		nm_log_warn (LOGD_DEVICE, "device %s could not be removed from a ovs port: %s",
 		             nm_device_get_iface (slave), error->message);
-	        nm_device_state_changed (slave,
-	                                 NM_DEVICE_STATE_FAILED,
-	                                 NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+		nm_device_state_changed (slave,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_OVSDB_FAILED);
 	}
 
 	g_object_unref (slave);
@@ -169,7 +170,7 @@ static void
 release_slave (NMDevice *device, NMDevice *slave, gboolean configure)
 {
 	nm_ovsdb_del_interface (nm_ovsdb_get (), nm_device_get_iface (slave),
-				del_iface_cb, g_object_ref (slave));
+	                        del_iface_cb, g_object_ref (slave));
 }
 
 /*****************************************************************************/
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index b44668c0..92fcfa01 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -119,7 +119,7 @@ NM_DEFINE_SINGLETON_GETTER (NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB);
 /*****************************************************************************/
 
 static void ovsdb_try_connect (NMOvsdb *self);
-static void ovsdb_disconnect (NMOvsdb *self);
+static void ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing);
 static void ovsdb_read (NMOvsdb *self);
 static void ovsdb_write (NMOvsdb *self);
 static void ovsdb_next_command (NMOvsdb *self);
@@ -1103,7 +1103,7 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 	                    "result", &result,
 	                    "error", &error) == -1) {
 		_LOGW ("couldn't grok the message: %s", json_error.text);
-		ovsdb_disconnect (self);
+		ovsdb_disconnect (self, FALSE);
 		return;
 	}
 
@@ -1114,7 +1114,7 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 		/* It's a method call! */
 		if (!params) {
 			_LOGW ("a method call with no params: '%s'", method);
-			ovsdb_disconnect (self);
+			ovsdb_disconnect (self, FALSE);
 			return;
 		}
 
@@ -1134,13 +1134,13 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 		/* This is a response to a method call. */
 		if (!priv->calls->len) {
 			_LOGE ("there are no queued calls expecting response %" G_GUINT64_FORMAT, id);
-			ovsdb_disconnect (self);
+			ovsdb_disconnect (self, FALSE);
 			return;
 		}
 		call = &g_array_index (priv->calls, OvsdbMethodCall, 0);
 		if (call->id != id) {
 			_LOGE ("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT, call->id, id);
-			ovsdb_disconnect (self);
+			ovsdb_disconnect (self, FALSE);
 			return;
 		}
 		/* Cool, we found a corresponsing call. Finish it. */
@@ -1219,7 +1219,7 @@ ovsdb_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
 	if (size == -1) {
 		_LOGW ("short read from ovsdb: %s", error->message);
 		g_clear_error (&error);
-		ovsdb_disconnect (self);
+		ovsdb_disconnect (self, FALSE);
 		return;
 	}
 
@@ -1267,7 +1267,7 @@ ovsdb_write_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
 	if (size == -1) {
 		_LOGW ("short write to ovsdb: %s", error->message);
 		g_clear_error (&error);
-		ovsdb_disconnect (self);
+		ovsdb_disconnect (self, FALSE);
 		return;
 	}
 
@@ -1310,21 +1310,19 @@ ovsdb_write (NMOvsdb *self)
  * puts us back in sync.
  */
 static void
-ovsdb_disconnect (NMOvsdb *self)
+ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing)
 {
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
 	OvsdbMethodCall *call;
 	OvsdbMethodCallback callback;
 	gpointer user_data;
-	GError *error;
+	gs_free_error GError *error = NULL;
 
 	_LOGD ("disconnecting from ovsdb");
+	nm_utils_error_set_cancelled (&error, is_disposing, "NMOvsdb");
 
 	while (priv->calls->len) {
-		error = NULL;
 		call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
-		g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
-
 		callback = call->callback;
 		user_data = call->user_data;
 		g_array_remove_index (priv->calls, priv->calls->len - 1);
@@ -1343,12 +1341,10 @@ static void
 _monitor_bridges_cb (NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
 {
 	if (error) {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		if (!nm_utils_error_is_cancelled (error, TRUE)) {
 			_LOGI ("%s", error->message);
-			ovsdb_disconnect (self);
+			ovsdb_disconnect (self, FALSE);
 		}
-
-		g_clear_error (&error);
 		return;
 	}
 
@@ -1371,7 +1367,7 @@ _client_connect_cb (GObject *source_object, GAsyncResult *res, gpointer user_dat
 		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 			_LOGI ("%s", error->message);
 
-		ovsdb_disconnect (self);
+		ovsdb_disconnect (self, FALSE);
 		g_clear_error (&error);
 		return;
 	}
@@ -1555,7 +1551,7 @@ dispose (GObject *object)
 	NMOvsdb *self = NM_OVSDB (object);
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
 
-	ovsdb_disconnect (self);
+	ovsdb_disconnect (self, TRUE);
 
 	g_string_free (priv->input, TRUE);
 	priv->input = NULL;
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index a0449816..1cce5a1c 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -145,7 +145,7 @@ _method_call_handle (NMDhcpListener *self,
 	gs_free char *iface = NULL;
 	gs_free char *pid_str = NULL;
 	gs_free char *reason = NULL;
-	gs_unref_variant GVariant *options;
+	gs_unref_variant GVariant *options = NULL;
 	int pid;
 	gboolean handled = FALSE;
 
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 973abc1c..dc545470 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -1448,11 +1448,13 @@ nm_dns_manager_stop (NMDnsManager *self)
 	_LOGT ("stopping...");
 
 	/* If we're quitting, leave a valid resolv.conf in place, not one
-	 * pointing to 127.0.0.1 if any plugins were active.  Thus update
-	 * DNS after disposing of all plugins.  But if we haven't done any
-	 * DNS updates yet, there's no reason to touch resolv.conf on shutdown.
+	 * pointing to 127.0.0.1 if dnsmasq was active.  But if we haven't
+	 * done any DNS updates yet, there's no reason to touch resolv.conf
+	 * on shutdown.
 	 */
-	if (priv->dns_touched) {
+	if (   priv->dns_touched
+	    && priv->plugin
+	    && NM_IS_DNS_DNSMASQ (priv->plugin)) {
 		if (!update_dns (self, TRUE, &error)) {
 			_LOGW ("could not commit DNS changes on shutdown: %s", error->message);
 			g_clear_error (&error);
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index 2286a74b..65f57565 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -292,6 +292,36 @@ nm_active_connection_set_state (NMActiveConnection *self,
 	}
 }
 
+void
+nm_active_connection_set_state_fail (NMActiveConnection *self,
+                                     NMActiveConnectionStateReason reason,
+                                     const char *error_desc)
+{
+	NMActiveConnectionState s;
+
+	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));
+
+	if (error_desc) {
+		_LOGD ("Failed to activate '%s': %s",
+		       nm_active_connection_get_settings_connection_id (self),
+		       error_desc);
+	}
+
+	s = nm_active_connection_get_state (self);
+	if (   s >= NM_ACTIVE_CONNECTION_STATE_ACTIVATING
+	    && s < NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
+		nm_active_connection_set_state (self,
+		                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATING,
+		                                reason);
+		s = nm_active_connection_get_state (self);
+	}
+	if (s < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
+		nm_active_connection_set_state (self,
+		                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
+		                                reason);
+	}
+}
+
 NMActivationStateFlags
 nm_active_connection_get_state_flags (NMActiveConnection *self)
 {
@@ -1236,6 +1266,7 @@ set_property (GObject *object, guint prop_id,
 		nm_active_connection_set_device (self, g_value_get_object (value));
 		break;
 	case PROP_INT_SUBJECT:
+		/* construct-only */
 		priv->subject = g_value_dup_object (value);
 		break;
 	case PROP_INT_MASTER:
@@ -1251,6 +1282,7 @@ set_property (GObject *object, guint prop_id,
 		_set_activation_type (self, (NMActivationType) i);
 		break;
 	case PROP_SPECIFIC_OBJECT:
+		/* construct-only */
 		tmp = g_value_get_string (value);
 		/* NM uses "/" to mean NULL */
 		if (g_strcmp0 (tmp, "/") != 0)
@@ -1263,6 +1295,7 @@ set_property (GObject *object, guint prop_id,
 		priv->is_default6 = g_value_get_boolean (value);
 		break;
 	case PROP_VPN:
+		/* construct-only */
 		priv->vpn = g_value_get_boolean (value);
 		break;
 	case PROP_MASTER:
diff --git a/src/nm-active-connection.h b/src/nm-active-connection.h
index 5ede2b04..3015b5c5 100644
--- a/src/nm-active-connection.h
+++ b/src/nm-active-connection.h
@@ -148,6 +148,10 @@ void          nm_active_connection_set_state (NMActiveConnection *self,
                                               NMActiveConnectionState state,
                                               NMActiveConnectionStateReason reason);
 
+void          nm_active_connection_set_state_fail (NMActiveConnection *active,
+                                                   NMActiveConnectionStateReason reason,
+                                                   const char *error_desc);
+
 NMActivationStateFlags  nm_active_connection_get_state_flags (NMActiveConnection *self);
 
 void          nm_active_connection_set_state_flags_full (NMActiveConnection *self,
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 001fae55..8391dbf2 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -685,7 +685,8 @@ active_connection_default_changed (NMActiveConnection *active,
  * Begins to track and manage @active.  Increases the refcount of @active.
  */
 static void
-active_connection_add (NMManager *self, NMActiveConnection *active)
+active_connection_add (NMManager *self,
+                       NMActiveConnection *active)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 
@@ -708,11 +709,12 @@ active_connection_add (NMManager *self, NMActiveConnection *active)
 	                  G_CALLBACK (active_connection_default_changed),
 	                  self);
 
+	if (!nm_exported_object_is_exported (NM_EXPORTED_OBJECT (active)))
+		nm_exported_object_export (NM_EXPORTED_OBJECT (active));
+
 	g_signal_emit (self, signals[ACTIVE_CONNECTION_ADDED], 0, active);
 
-	/* Only notify D-Bus if the active connection is actually exported */
-	if (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (active)))
-		_notify (self, PROP_ACTIVE_CONNECTIONS);
+	_notify (self, PROP_ACTIVE_CONNECTIONS);
 }
 
 const CList *
@@ -2345,7 +2347,6 @@ recheck_assume_connection (NMManager *self,
 		if (find_master (self, NM_CONNECTION (connection), device, NULL, NULL, &master_ac, NULL) && master_ac)
 			nm_active_connection_set_master (active, master_ac);
 
-		nm_exported_object_export (NM_EXPORTED_OBJECT (active));
 		active_connection_add (self, active);
 		nm_device_queue_activation (device, NM_ACT_REQUEST (active));
 	}
@@ -3523,18 +3524,18 @@ autoconnect_slaves (NMManager *self,
 static gboolean
 _internal_activate_vpn (NMManager *self, NMActiveConnection *active, GError **error)
 {
-	gboolean success;
-
-	g_assert (NM_IS_VPN_CONNECTION (active));
+	nm_assert (NM_IS_VPN_CONNECTION (active));
 
 	nm_exported_object_export (NM_EXPORTED_OBJECT (active));
-	success = nm_vpn_manager_activate_connection (NM_MANAGER_GET_PRIVATE (self)->vpn_manager,
-	                                              NM_VPN_CONNECTION (active),
-	                                              error);
-	if (!success)
+	if (!nm_vpn_manager_activate_connection (NM_MANAGER_GET_PRIVATE (self)->vpn_manager,
+	                                         NM_VPN_CONNECTION (active),
+	                                         error)) {
 		nm_exported_object_unexport (NM_EXPORTED_OBJECT (active));
+		return FALSE;
+	}
 
-	return success;
+	active_connection_add (self, active);
+	return TRUE;
 }
 
 /* Traverse the device to disconnected state. This means that the device is ready
@@ -3563,8 +3564,8 @@ unmanaged_to_disconnected (NMDevice *device)
 		                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
 	}
 
-	if (   nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)
-	    && (nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE)) {
+	if (   nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE
+	    && nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)) {
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_DISCONNECTED,
 		                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
@@ -3581,32 +3582,36 @@ active_connection_parent_active (NMActiveConnection *active,
 {
 	NMDevice *device = nm_active_connection_get_device (active);
 	GError *error = NULL;
+	NMSettingsConnection *connection;
+	NMDevice *parent;
 
 	g_signal_handlers_disconnect_by_func (active,
 	                                      (GCallback) active_connection_parent_active,
 	                                      self);
 
-	if (parent_ac) {
-		NMSettingsConnection *connection = nm_active_connection_get_settings_connection (active);
-		NMDevice *parent = nm_active_connection_get_device (parent_ac);
-
-		if (nm_device_create_and_realize (device, (NMConnection *) connection, parent, &error)) {
-			/* We can now proceed to disconnected state so that activation proceeds. */
-			unmanaged_to_disconnected (device);
-		} else {
-			_LOGW (LOGD_CORE, "Could not realize device '%s': %s",
-			       nm_device_get_iface (device), error->message);
-			nm_active_connection_set_state (active,
-			                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
-			                                NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REALIZE_FAILED);
-		}
-	} else {
+	if (!parent_ac) {
 		_LOGW (LOGD_CORE, "The parent connection device '%s' depended on disappeared.",
 		       nm_device_get_iface (device));
-		nm_active_connection_set_state (active,
-		                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
-		                                NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REMOVED);
+		nm_active_connection_set_state_fail (active,
+		                                     NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REMOVED,
+		                                     "parent device disappeared");
+		return;
+	}
+
+	connection = nm_active_connection_get_settings_connection (active);
+	parent = nm_active_connection_get_device (parent_ac);
+
+	if (!nm_device_create_and_realize (device, (NMConnection *) connection, parent, &error)) {
+		_LOGW (LOGD_CORE, "Could not realize device '%s': %s",
+		       nm_device_get_iface (device), error->message);
+		nm_active_connection_set_state_fail (active,
+		                                     NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REALIZE_FAILED,
+		                                     "failure to realize device");
+		return;
 	}
+
+	/* We can now proceed to disconnected state so that activation proceeds. */
+	unmanaged_to_disconnected (device);
 }
 
 static gboolean
@@ -3783,11 +3788,22 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	}
 
 	/* If the device is there, we can ready it for the activation. */
-	if (nm_device_is_real (device))
+	if (nm_device_is_real (device)) {
 		unmanaged_to_disconnected (device);
 
+		if (!nm_device_get_managed (device, FALSE)) {
+			/* Unexpectedly, the device is still unmanaged. That can happen for example,
+			 * if the device is forcibly unmanaged due to NM_UNMANAGED_USER_SETTINGS. */
+			g_set_error_literal (error,
+			                     NM_MANAGER_ERROR,
+			                     NM_MANAGER_ERROR_DEPENDENCY_FAILED,
+			                     "Activation failed because the device is unmanaged");
+			return FALSE;
+		}
+	}
+
 	/* Export the new ActiveConnection to clients and start it on the device */
-	nm_exported_object_export (NM_EXPORTED_OBJECT (active));
+	active_connection_add (self, active);
 	nm_device_queue_activation (device, NM_ACT_REQUEST (active));
 	return TRUE;
 }
@@ -3822,7 +3838,6 @@ _internal_activate_generic (NMManager *self, NMActiveConnection *active, GError
 		 * is exported, make sure the manager's activating-connection property
 		 * is up-to-date.
 		 */
-		active_connection_add (self, active);
 		policy_activating_device_changed (G_OBJECT (priv->policy), NULL, self);
 	}
 
@@ -3929,25 +3944,6 @@ _new_active_connection (NMManager *self,
 }
 
 static void
-_internal_activation_failed (NMManager *self,
-                             NMActiveConnection *active,
-                             const char *error_desc)
-{
-	_LOGD (LOGD_CORE, "Failed to activate '%s': %s",
-	       nm_active_connection_get_settings_connection_id (active),
-	       error_desc);
-
-	if (nm_active_connection_get_state (active) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
-		nm_active_connection_set_state (active,
-		                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATING,
-		                                NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN);
-		nm_active_connection_set_state (active,
-		                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
-		                                NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN);
-	}
-}
-
-static void
 _internal_activation_auth_done (NMActiveConnection *active,
                                 gboolean success,
                                 const char *error_desc,
@@ -3992,7 +3988,9 @@ _internal_activation_auth_done (NMActiveConnection *active,
 	}
 
 	nm_assert (error_desc || error);
-	_internal_activation_failed (self, active, error_desc ? error_desc : error->message);
+	nm_active_connection_set_state_fail (active,
+	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
+	                                     error_desc ?: error->message);
 }
 
 /**
@@ -4211,6 +4209,7 @@ _activation_auth_done (NMActiveConnection *active,
 	GError *error = NULL;
 	NMAuthSubject *subject;
 	NMSettingsConnection *connection;
+	_nm_unused gs_unref_object NMActiveConnection *active_free = active;
 
 	subject = nm_active_connection_get_subject (active);
 	connection = nm_active_connection_get_settings_connection (active);
@@ -4225,7 +4224,6 @@ _activation_auth_done (NMActiveConnection *active,
 			                                       nm_exported_object_get_path (NM_EXPORTED_OBJECT (active))));
 			nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, TRUE, NULL,
 			                            subject, NULL);
-			g_object_unref (active);
 			return;
 		}
 	} else {
@@ -4234,12 +4232,12 @@ _activation_auth_done (NMActiveConnection *active,
 		                             error_desc);
 	}
 
-	g_assert (error);
 	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, FALSE, NULL,
 	                            subject, error->message);
-	_internal_activation_failed (self, active, error->message);
+	nm_active_connection_set_state_fail (active,
+	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
+	                                     error->message);
 
-	g_object_unref (active);
 	g_dbus_method_invocation_take_error (context, error);
 }
 
@@ -4251,8 +4249,8 @@ impl_manager_activate_connection (NMManager *self,
                                   const char *specific_object_path)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMActiveConnection *active = NULL;
-	NMAuthSubject *subject = NULL;
+	gs_unref_object NMActiveConnection *active = NULL;
+	gs_unref_object NMAuthSubject *subject = NULL;
 	NMSettingsConnection *connection = NULL;
 	NMDevice *device = NULL;
 	gboolean is_vpn = FALSE;
@@ -4319,8 +4317,14 @@ impl_manager_activate_connection (NMManager *self,
 	if (!active)
 		goto error;
 
-	nm_active_connection_authorize (active, NULL, _activation_auth_done, self, context);
-	g_clear_object (&subject);
+	/* FIXME: nm_active_connection_authorize() is not cancellable,
+	 * and we pass on the only reference to @active. This construct
+	 * is unsuitable for a coordinated shutdown. */
+	nm_active_connection_authorize (g_steal_pointer (&active),
+	                                NULL,
+	                                _activation_auth_done,
+	                                self,
+	                                context);
 	return;
 
 error:
@@ -4328,10 +4332,6 @@ error:
 		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, FALSE, NULL,
 		                            subject, error->message);
 	}
-	g_clear_object (&active);
-	g_clear_object (&subject);
-
-	g_assert (error);
 	g_dbus_method_invocation_take_error (context, error);
 }
 
@@ -4385,8 +4385,10 @@ activation_add_done (NMSettings *settings,
 		error = local;
 	}
 
-	g_assert (error);
-	_internal_activation_failed (self, active, error->message);
+	nm_assert (error);
+	nm_active_connection_set_state_fail (active,
+	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
+	                                     error->message);
 	if (new_connection)
 		nm_settings_connection_delete (new_connection, NULL);
 	g_dbus_method_invocation_return_gerror (context, error);
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 4d0ef91a..b73e04cb 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -1758,10 +1758,16 @@ device_state_changed (NMDevice *device,
 				 * been consulted, and it may be able to provide the secrets.
 				 *
 				 * We detect this by using a version-id of the agent-manager, which increments
-				 * whenever new agents register. */
+				 * whenever new agents register. Note that the agent-manager's version-id is
+				 * never zero and strictly increasing.
+				 *
+				 * A connection's version-id of zero means that the connection never tried to request secrets.
+				 * That can happen when nm_settings_connection_get_secrets() fails early without actually
+				 * consulting any agents.
+				 */
 				con_v = nm_settings_connection_get_last_secret_agent_version_id (connection);
 				if (   con_v == 0
-				    || con_v != nm_agent_manager_get_agent_version_id (priv->agent_mgr))
+				    || con_v == nm_agent_manager_get_agent_version_id (priv->agent_mgr))
 					block_no_secrets = TRUE;
 			}
 
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index fe270e88..e5961c7e 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -4437,10 +4437,13 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 			nm_auto_nmpobj const NMPObject *obj_replace = NULL;
 			gboolean resync_required = FALSE;
 			gboolean only_dirty = FALSE;
+			gboolean is_ipv6;
 
-			if (NM_FLAGS_HAS (obj->ip_route.r_rtm_flags, RTM_F_CLONED)) {
-				/* a cloned route might be a response for RTM_GETROUTE. Check, whether it is. */
-				nm_assert (!nmp_object_is_alive (obj));
+			/* IPv4 routes that are a response to RTM_GETROUTE must have
+			 * the cloned flag while IPv6 routes don't have to. */
+			is_ipv6 = NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_IP6_ROUTE;
+			if (is_ipv6 || NM_FLAGS_HAS (obj->ip_route.r_rtm_flags, RTM_F_CLONED)) {
+				nm_assert (is_ipv6 || !nmp_object_is_alive (obj));
 				priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 				if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
 					guint i;
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index bc305f01..d8561aef 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -475,6 +475,7 @@ nmp_netns_new (void)
 	NMPNetns *self;
 	int errsv;
 	GError *error = NULL;
+	unsigned long mountflags = 0;
 
 	_stack_ensure_init ();
 
@@ -503,7 +504,10 @@ nmp_netns_new (void)
 		goto err_out;
 	}
 
-	if (mount ("sysfs", "/sys", "sysfs", 0, NULL) != 0) {
+	if (access ("/sys", W_OK) == -1)
+		mountflags = MS_RDONLY;
+
+	if (mount ("sysfs", "/sys", "sysfs", mountflags, NULL) != 0) {
 		errsv = errno;
 		_LOGE (NULL, "failed mount /sys: %s", g_strerror (errsv));
 		goto err_out;
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 9a12f8f9..d56e681e 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1931,47 +1931,12 @@ main (int argc, char **argv)
 			g_error ("unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)", strerror (errsv), errsv);
 		}
 
-		/* Below we need a read-only /sys (to signal that we're in an environment
-		 * we don't have udev and writable /sys/devices so that we still are able
-		 * to test device classes that modify the device attributes (such as bridges).
-		 *
-		 * We use two sysfs instances to achieve this, binding the /device subtree
-		 * of the writeable one to the read-only one.
-		 *
-		 * We abuse a /sys/kernel/debug for our temporary writable sysfs mount,
-		 * just because it's guarranteed to exist and mounts are allowed there even
-		 * after the sysfs mount point hardening [linux 0cbee99269]. It's just in
-		 * our mount namespace, we release it quickly and don't need debugfs anyway...
-		 * An alrernative would be to create a temporary directory, but that seems
-		 * like an overkill. */
-
-		/* Make the mounts below /sys private to our namespace. Other mounts
-		 * wouldn't be permitted for good reasons. */
+		/* We need a read-only /sys so that the platform knows there's no udev. */
 		mount (NULL, "/sys", "sysfs", MS_SLAVE, NULL);
-
-		/* Mount the read-only sysfs. */
 		if (mount ("sys", "/sys", "sysfs", MS_RDONLY, NULL) != 0) {
 			errsv = errno;
 			g_error ("mount(\"/sys\") failed with %s (%d)", strerror (errsv), errsv);
 		}
-
-		/* Create the writable /sys/devices tree. */
-		if (mount ("sys", "/sys/kernel/debug", "sysfs", 0, NULL) != 0) {
-			errsv = errno;
-			g_error ("mount(\"/sys/devices/k\") failed with %s (%d)", strerror (errsv), errsv);
-		}
-
-		/* Bind mound the writable device tree to the read-only sysfs. */
-		if (mount ("/sys/kernel/debug/devices", "/sys/devices", "sysfs", MS_BIND, NULL) != 0) {
-			errsv = errno;
-			g_error ("mount(\"/sys\") failed with %s (%d)", strerror (errsv), errsv);
-		}
-
-		/* Release the temporary mount now that we bound the /devices subtree. */
-		if (umount ("/sys/kernel/debug") != 0) {
-			errsv = errno;
-			g_error ("umount(\"/sys/kernel/debug\") failed with  %s (%d)", strerror (errsv), errsv);
-		}
 	}
 
 	nmtstp_setup_platform ();
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index d3a10bd6..ef78cc24 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1949,6 +1949,17 @@ _test_netns_check_skip (void)
 	return FALSE;
 }
 
+static gboolean
+_check_sysctl_skip (void)
+{
+	if (access ("/proc/sys/net/ipv4/ip_forward", W_OK) == -1) {
+		g_test_skip ("Can not write sysctls");
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
 /*****************************************************************************/
 
 #define _sysctl_assert_eq(plat, path, value) \
@@ -1973,6 +1984,9 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 	if (_test_netns_check_skip ())
 		return;
 
+	if (_check_sysctl_skip ())
+		return;
+
 	platform_1 = nm_linux_platform_new (TRUE, TRUE);
 	platform_2 = _test_netns_create_platform ();
 
@@ -2168,6 +2182,9 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 	if (_test_netns_check_skip ())
 		return;
 
+	if (_check_sysctl_skip ())
+		return;
+
 	pl[0].platform = platform_0 = nm_linux_platform_new (TRUE, TRUE);
 	pl[1].platform = platform_1 = _test_netns_create_platform ();
 	pl[2].platform = platform_2 = _test_netns_create_platform ();
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index fe97db07..13648f16 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -424,7 +424,7 @@ test_ip6_route (void)
 /*****************************************************************************/
 
 static void
-test_ip_route_get (void)
+test_ip4_route_get (void)
 {
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	in_addr_t a;
@@ -586,6 +586,45 @@ test_ip4_route_options (gconstpointer test_data)
 }
 
 static void
+test_ip6_route_get (void)
+{
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	const struct in6_addr *a;
+	NMPlatformError result;
+	nm_auto_nmpobj NMPObject *route = NULL;
+	const NMPlatformIP6Route *r;
+
+	nmtstp_run_command_check ("ip -6 route add fd01:abcd::/64 via fe80::99 dev %s", DEVICE_NAME);
+
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (NM_PLATFORM_GET, 10);
+		if (nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet6_from_string ("fd01:abcd::"), 64, 0, NULL, 0))
+			break;
+	});
+
+	a = nmtst_inet6_from_string ("fd01:abcd::42");
+	result = nm_platform_ip_route_get (NM_PLATFORM_GET,
+	                                   AF_INET6,
+	                                   a,
+	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
+	                                   &route);
+
+	g_assert (result == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMP_OBJECT_GET_TYPE (route) == NMP_OBJECT_TYPE_IP6_ROUTE);
+	g_assert (!NMP_OBJECT_IS_STACKINIT (route));
+	g_assert (route->parent._ref_count == 1);
+	r = NMP_OBJECT_CAST_IP6_ROUTE (route);
+	g_assert (r->ifindex == ifindex);
+	nmtst_assert_ip6_address (&r->network, "fd01:abcd::42");
+	g_assert_cmpint (r->plen, ==, 128);
+	nmtst_assert_ip6_address (&r->gateway, "fe80::99");
+
+	nmtstp_run_command_check ("ip -6 route flush dev %s", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
+}
+
+static void
 test_ip6_route_options (gconstpointer test_data)
 {
 	const int TEST_IDX = GPOINTER_TO_INT (test_data);
@@ -860,7 +899,8 @@ _nmtstp_setup_tests (void)
 
 	if (nmtstp_is_root_test ()) {
 		add_test_func_data ("/route/ip/1", test_ip, GINT_TO_POINTER (1));
-		add_test_func ("/route/ip_route_get", test_ip_route_get);
+		add_test_func ("/route/ip4_route_get", test_ip4_route_get);
+		add_test_func ("/route/ip6_route_get", test_ip6_route_get);
 		add_test_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
 	}
 }
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index 7dd6a9b9..743f80a2 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -139,11 +139,11 @@ static void _ppp_kill (NMPPPManager *manager);
 /*****************************************************************************/
 
 static void
-_ppp_manager_set_route_paramters (NMPPPManager *self,
-                                  guint32 ip4_route_table,
-                                  guint32 ip4_route_metric,
-                                  guint32 ip6_route_table,
-                                  guint32 ip6_route_metric)
+_ppp_manager_set_route_parameters (NMPPPManager *self,
+                                   guint32 ip4_route_table,
+                                   guint32 ip4_route_metric,
+                                   guint32 ip6_route_table,
+                                   guint32 ip6_route_metric)
 {
 	NMPPPManagerPrivate *priv;
 
@@ -1359,7 +1359,7 @@ nm_ppp_manager_class_init (NMPPPManagerClass *manager_class)
 
 NMPPPOps ppp_ops = {
 	.create               = _ppp_manager_new,
-	.set_route_parameters = _ppp_manager_set_route_paramters,
+	.set_route_parameters = _ppp_manager_set_route_parameters,
 	.start                = _ppp_manager_start,
 	.stop_async           = _ppp_manager_stop_async,
 	.stop_finish          = _ppp_manager_stop_finish,
diff --git a/src/ppp/nm-pppd-plugin.c b/src/ppp/nm-pppd-plugin.c
index 9c47c339..0ac8f907 100644
--- a/src/ppp/nm-pppd-plugin.c
+++ b/src/ppp/nm-pppd-plugin.c
@@ -281,7 +281,6 @@ get_credentials (char *username, char *password)
 {
 	const char *my_username = NULL;
 	const char *my_password = NULL;
-	size_t len;
 	GVariant *ret;
 	GError *err = NULL;
 
@@ -313,21 +312,11 @@ get_credentials (char *username, char *password)
 
 	g_variant_get (ret, "(&s&s)", &my_username, &my_password);
 
-	if (my_username) {
-		len = strlen (my_username) + 1;
-		len = len < MAXNAMELEN ? len : MAXNAMELEN;
+	if (my_username)
+		g_strlcpy (username, my_username, MAXNAMELEN);
 
-		strncpy (username, my_username, len);
-		username[len - 1] = '\0';
-	}
-
-	if (my_password) {
-		len = strlen (my_password) + 1;
-		len = len < MAXSECRETLEN ? len : MAXSECRETLEN;
-
-		strncpy (password, my_password, len);
-		password[len - 1] = '\0';
-	}
+	if (my_password)
+		g_strlcpy (password, my_password, MAXSECRETLEN);
 
 	g_variant_unref (ret);
 
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 37a0b3a6..58626372 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -608,6 +608,7 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	gboolean replaced = FALSE;
 	gs_free char *logmsg_change = NULL;
 	GError *local = NULL;
+	gs_unref_variant GVariant *con_agent_secrets = NULL;
 
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
@@ -658,9 +659,20 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	    && !nm_connection_compare (NM_CONNECTION (self),
 	                               replace_connection,
 	                               NM_SETTING_COMPARE_FLAG_EXACT)) {
+		gs_unref_object NMConnection *simple = NULL;
+
 		if (log_diff_name)
 			nm_utils_log_connection_diff (replace_connection, NM_CONNECTION (self), LOGL_DEBUG, LOGD_CORE, log_diff_name, "++ ");
 
+		/* Make a copy of agent-owned secrets because they won't be present in
+		 * the connection returned by plugins, as plugins return only what was
+		 * reread from the file. */
+		simple = nm_simple_connection_new_clone (NM_CONNECTION (self));
+		nm_connection_clear_secrets_with_flags (simple,
+		                                        secrets_filter_cb,
+		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+		con_agent_secrets = nm_connection_to_dbus (simple, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
+
 		nm_connection_replace_settings_from_connection (NM_CONNECTION (self), replace_connection);
 
 		replaced = TRUE;
@@ -688,6 +700,8 @@ nm_settings_connection_update (NMSettingsConnection *self,
 				g_variant_unref (dict);
 			}
 		}
+		if (con_agent_secrets)
+			(void) nm_connection_update_secrets (NM_CONNECTION (self), NULL, con_agent_secrets, NULL);
 	}
 
 	nm_settings_connection_recheck_visibility (self);
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 51b7bea7..8e3fc582 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -1337,7 +1337,7 @@ impl_settings_add_connection_helper (NMSettings *self,
                                      GVariant *settings,
                                      gboolean save_to_disk)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
 	GError *error = NULL;
 
 	connection = _nm_simple_connection_new_from_dbus (settings,
@@ -1345,23 +1345,18 @@ impl_settings_add_connection_helper (NMSettings *self,
 	                                                  | NM_SETTING_PARSE_FLAGS_NORMALIZE,
 	                                                  &error);
 
-	if (connection) {
-		if (!nm_connection_verify_secrets (connection, &error))
-			goto failure;
-
-		nm_settings_add_connection_dbus (self,
-		                                 connection,
-		                                 save_to_disk,
-		                                 context,
-		                                 impl_settings_add_connection_add_cb,
-		                                 NULL);
-		g_object_unref (connection);
+	if (   !connection
+	    || !nm_connection_verify_secrets (connection, &error)) {
+		g_dbus_method_invocation_take_error (context, error);
 		return;
 	}
 
-failure:
-	g_assert (error);
-	g_dbus_method_invocation_take_error (context, error);
+	nm_settings_add_connection_dbus (self,
+	                                 connection,
+	                                 save_to_disk,
+	                                 context,
+	                                 impl_settings_add_connection_add_cb,
+	                                 NULL);
 }
 
 static void
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index bdd3ee0a..c91cd253 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -558,7 +558,7 @@ enum {
 	PARSE_LINE_ATTR_ROUTE_VIA,
 	PARSE_LINE_ATTR_ROUTE_METRIC,
 
-	/* iproute2 paramters that are well known and that we silently ignore. */
+	/* iproute2 parameters that are well known and that we silently ignore. */
 	PARSE_LINE_ATTR_ROUTE_DEV,
 };
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index cb5f2c9f..e6299d1e 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -143,7 +143,7 @@ find_by_path (NMSKeyfilePlugin *self, const char *path)
  *   an existing connection with the same UUID.
  *   If %TRUE and @connection, allow updating only if the reload would modify
  *   @connection (without changing its UUID) or if we would create a new connection.
- *   In other words, if this paramter is %TRUE, we only allow creating a
+ *   In other words, if this parameter is %TRUE, we only allow creating a
  *   new connection (with an unseen UUID) or updating the passed in @connection
  *   (whereas the UUID cannot change).
  *   Note, that this allows for @connection to be replaced by a new connection.