about summary refs log tree commit diff
path: root/src/nm-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-manager.c')
-rw-r--r--src/nm-manager.c486
1 files changed, 386 insertions, 100 deletions
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 3ddc3b92..0bf6a751 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -25,8 +25,6 @@
 
 #include <stdlib.h>
 #include <fcntl.h>
-#include <errno.h>
-#include <string.h>
 #include <unistd.h>
 
 #include "nm-utils/nm-c-list.h"
@@ -39,6 +37,7 @@
 #include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
 #include "nm-hostname-manager.h"
+#include "nm-keep-alive.h"
 #include "nm-rfkill-manager.h"
 #include "dhcp/nm-dhcp-manager.h"
 #include "settings/nm-settings.h"
@@ -79,6 +78,7 @@ typedef enum {
 	ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL,
 	ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER,
 	ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
+	ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2,
 } AsyncOpType;
 
 typedef struct {
@@ -95,6 +95,7 @@ typedef struct {
 				struct {
 					GDBusMethodInvocation *invocation;
 					NMConnection *connection;
+					NMSettingsConnectionPersistMode persist;
 				} add_and_activate;
 			};
 		} ac_auth;
@@ -318,6 +319,7 @@ static NMActiveConnection *_new_active_connection (NMManager *self,
                                                    NMAuthSubject *subject,
                                                    NMActivationType activation_type,
                                                    NMActivationReason activation_reason,
+                                                   NMActivationStateFlags initial_state_flags,
                                                    GError **error);
 
 static void policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data);
@@ -367,9 +369,11 @@ static void _internal_activation_auth_done (NMManager *self,
                                             gboolean success,
                                             const char *error_desc);
 static void _add_and_activate_auth_done (NMManager *self,
+                                         AsyncOpType async_op_type,
                                          NMActiveConnection *active,
                                          NMConnection *connection,
                                          GDBusMethodInvocation *invocation,
+                                         NMSettingsConnectionPersistMode persist,
                                          gboolean success,
                                          const char *error_desc);
 static void _activation_auth_done (NMManager *self,
@@ -395,7 +399,7 @@ _connection_is_vpn (NMConnection *connection)
 
 	/* we have an incomplete (invalid) connection at hand. That can only
 	 * happen during AddAndActivate. Determine whether it's VPN type based
-	 * on the existance of a [vpn] section. */
+	 * on the existence of a [vpn] section. */
 	return !!nm_connection_get_setting_vpn (connection);
 }
 
@@ -482,18 +486,24 @@ _async_op_data_new_ac_auth_activate_user (NMManager *self,
 
 static AsyncOpData *
 _async_op_data_new_ac_auth_add_and_activate (NMManager *self,
+                                             AsyncOpType async_op_type,
                                              NMActiveConnection *active_take,
                                              GDBusMethodInvocation *invocation_take,
-                                             NMConnection *connection_take)
+                                             NMConnection *connection_take,
+                                             NMSettingsConnectionPersistMode persist)
 {
 	AsyncOpData *async_op_data;
 
+	nm_assert (NM_IN_SET (async_op_type, ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
+	                                     ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2));
+
 	async_op_data = g_slice_new0 (AsyncOpData);
-	async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE;
+	async_op_data->async_op_type = async_op_type;
 	async_op_data->self = g_object_ref (self);
 	async_op_data->ac_auth.active = active_take;
 	async_op_data->ac_auth.add_and_activate.invocation = invocation_take;
 	async_op_data->ac_auth.add_and_activate.connection = connection_take;
+	async_op_data->ac_auth.add_and_activate.persist = persist;
 	c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
 	return async_op_data;
 }
@@ -529,10 +539,13 @@ _async_op_complete_ac_auth_cb (NMActiveConnection *active,
 		                       error_desc);
 		break;
 	case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE:
+	case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2:
 		_add_and_activate_auth_done (async_op_data->self,
+		                             async_op_data->async_op_type,
 		                             async_op_data->ac_auth.active,
 		                             async_op_data->ac_auth.add_and_activate.connection,
 		                             async_op_data->ac_auth.add_and_activate.invocation,
+		                             async_op_data->ac_auth.add_and_activate.persist,
 		                             success,
 		                             error_desc);
 		g_object_unref (async_op_data->ac_auth.add_and_activate.connection);
@@ -1176,7 +1189,7 @@ _reload_auth_cb (NMAuthChain *chain,
 		goto out;
 	}
 
-	nm_config_reload (priv->config, reload_type);
+	nm_config_reload (priv->config, reload_type, TRUE);
 	g_dbus_method_invocation_return_value (context, NULL);
 
 out:
@@ -1440,6 +1453,14 @@ nm_manager_update_metered (NMManager *self)
 	}
 }
 
+NMMetered
+nm_manager_get_metered (NMManager *self)
+{
+	g_return_val_if_fail (NM_IS_MANAGER (self), NM_METERED_UNKNOWN);
+
+	return NM_MANAGER_GET_PRIVATE (self)->metered;
+}
+
 static void
 nm_manager_update_state (NMManager *self)
 {
@@ -1518,6 +1539,7 @@ check_if_startup_complete (NMManager *self)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMDevice *device;
+	const char *reason;
 
 	if (!priv->startup)
 		return;
@@ -1525,15 +1547,19 @@ check_if_startup_complete (NMManager *self)
 	if (!priv->devices_inited)
 		return;
 
-	if (!nm_settings_get_startup_complete (priv->settings)) {
-		_LOGD (LOGD_CORE, "check_if_startup_complete returns FALSE because of NMSettings");
+	reason = nm_settings_get_startup_complete_blocked_reason (priv->settings);
+	if (reason) {
+		_LOGD (LOGD_CORE, "startup complete is waiting for connection (%s)",
+		       reason);
 		return;
 	}
 
 	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
-		if (nm_device_has_pending_action (device)) {
-			_LOGD (LOGD_CORE, "check_if_startup_complete returns FALSE because of %s",
-			       nm_device_get_iface (device));
+		reason = nm_device_has_pending_action_reason (device);
+		if (reason) {
+			_LOGD (LOGD_CORE, "startup complete is waiting for device '%s' (%s)",
+			       nm_device_get_iface (device),
+			       reason);
 			return;
 		}
 	}
@@ -1733,7 +1759,7 @@ find_parent_device_for_connection (NMManager *self, NMConnection *connection, NM
 	if (!parent_connection)
 		return NULL;
 
-	/* Check if the parent connection is currently activated or is comaptible
+	/* Check if the parent connection is currently activated or is compatible
 	 * with some known device.
 	 */
 	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
@@ -2461,7 +2487,12 @@ get_existing_connection (NMManager *self,
 	if (ifindex) {
 		int master_ifindex = nm_platform_link_get_master (priv->platform, ifindex);
 
-		if (master_ifindex) {
+		/* Check that the master is activating before assuming a
+		 * slave connection. However, ignore ovs-system master as
+		 * we never manage it.
+		 */
+		if (   master_ifindex
+		    && nm_platform_link_get_type (priv->platform, master_ifindex) != NM_LINK_TYPE_OPENVSWITCH) {
 			master = nm_manager_get_device_by_ifindex (self, master_ifindex);
 			if (!master) {
 				_LOG2D (LOGD_DEVICE, device, "assume: don't assume because "
@@ -2679,6 +2710,18 @@ recheck_assume_connection (NMManager *self,
 		GError *error = NULL;
 
 		subject = nm_auth_subject_new_internal ();
+
+		/* Note: the lifetime of the activation connection is always bound to the profiles visibility
+		 * via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY.
+		 *
+		 * This only makes a difference, if the profile actually has "connection.permissions"
+		 * set to limit visibility (which is not the case for externally managed, generated profiles).
+		 *
+		 * If we assume a previously active connection whose lifetime was unbound, we now bind it
+		 * after restart. That is not correct, and can mean that the profile becomes subject to
+		 * deactivation after restart (if the user logs out).
+		 *
+		 * This should be improved, but it's unclear how. */
 		active = _new_active_connection (self,
 		                                 FALSE,
 		                                 sett_conn,
@@ -2689,6 +2732,7 @@ recheck_assume_connection (NMManager *self,
 		                                 subject,
 		                                 generated ? NM_ACTIVATION_TYPE_EXTERNAL : NM_ACTIVATION_TYPE_ASSUME,
 		                                 generated ? NM_ACTIVATION_REASON_EXTERNAL : NM_ACTIVATION_REASON_ASSUME,
+		                                 NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
 		                                 &error);
 
 		if (!active) {
@@ -2810,43 +2854,91 @@ device_realized (NMDevice *device,
 	_emit_device_added_removed (self, device, nm_device_is_real (device));
 }
 
-static void
-device_connectivity_changed (NMDevice *device,
-                             NMManager *self)
+static NMConnectivityState
+_get_best_connectivity (NMManager *self, int addr_family)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMConnectivityState best_state = NM_CONNECTIVITY_UNKNOWN;
-	NMConnectivityState state;
+	NMConnectivityState best_state;
 	NMDevice *dev;
+	gint64 best_metric;
+
+	if (addr_family == AF_UNSPEC) {
+		best_state = _get_best_connectivity (self, AF_INET);
+		if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
+			/* already FULL IPv4 connectivity. No need to check IPv6, it doesn't get
+			 * better. */
+			return best_state;
+		}
+		return NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
+		                        best_state,
+		                        _get_best_connectivity (self, AF_INET6));
+	}
 
-	best_state = nm_device_get_connectivity_state (device);
-	if (best_state < NM_CONNECTIVITY_FULL) {
-		/* FIXME: is this really correct, to considere devices that don't have
-		 * (the best) default route for connectivity checking? */
-		c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) {
-			state = nm_device_get_connectivity_state (dev);
-			if (nm_connectivity_state_cmp (state, best_state) <= 0)
-				continue;
+	nm_assert_addr_family (addr_family);
+
+	best_state = NM_CONNECTIVITY_UNKNOWN;
+	best_metric = G_MAXINT64;
+	c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) {
+		const NMPObject *r;
+		NMConnectivityState state;
+		gint64 metric;
+
+		r = nm_device_get_best_default_route (dev, addr_family);
+		if (r) {
+			metric = nm_utils_ip_route_metric_normalize (addr_family,
+			                                             NMP_OBJECT_CAST_IP_ROUTE (r)->metric);
+		} else {
+			/* if all devices have no default-route, we still include the best
+			 * of all connectivity state of all the devices. */
+			metric = G_MAXINT64;
+		}
+
+		if (metric > best_metric) {
+			/* we already have a default route with better metric. The connectivity state
+			 * of this device is irreleavnt. */
+			continue;
+		}
+
+		state = nm_device_get_connectivity_state (dev, addr_family);
+		if (metric < best_metric) {
+			/* this device has a better default route. It wins. */
+			best_metric = metric;
 			best_state = state;
-			if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
-				/* it doesn't get better than this. */
-				break;
-			}
+		} else {
+			best_state = NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
+			                              best_state,
+			                              state);
+		}
+
+		if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
+			/* it doesn't get better than FULL. We are done. */
+			break;
 		}
 	}
-	nm_assert (best_state <= NM_CONNECTIVITY_FULL);
-	nm_assert (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) <= 0);
 
-	if (best_state != priv->connectivity_state) {
-		priv->connectivity_state = best_state;
+	return best_state;
+}
 
-		_LOGD (LOGD_CORE, "connectivity checking indicates %s",
-		       nm_connectivity_state_to_string (priv->connectivity_state));
+static void
+device_connectivity_changed (NMDevice *device,
+                             GParamSpec *pspec,
+                             NMManager *self)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMConnectivityState best_state;
 
-		nm_manager_update_state (self);
-		_notify (self, PROP_CONNECTIVITY);
-		nm_dispatcher_call_connectivity (priv->connectivity_state, NULL, NULL, NULL);
-	}
+	best_state = _get_best_connectivity (self, AF_UNSPEC);
+	if (best_state == priv->connectivity_state)
+		return;
+
+	priv->connectivity_state = best_state;
+
+	_LOGD (LOGD_CORE, "connectivity checking indicates %s",
+	       nm_connectivity_state_to_string (priv->connectivity_state));
+
+	nm_manager_update_state (self);
+	_notify (self, PROP_CONNECTIVITY);
+	nm_dispatcher_call_connectivity (priv->connectivity_state, NULL, NULL, NULL);
 }
 
 static void
@@ -2957,7 +3049,10 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 	                  G_CALLBACK (device_realized),
 	                  self);
 
-	g_signal_connect (device, NM_DEVICE_CONNECTIVITY_CHANGED,
+	g_signal_connect (device, "notify::" NM_DEVICE_IP4_CONNECTIVITY,
+	                  G_CALLBACK (device_connectivity_changed),
+	                  self);
+	g_signal_connect (device, "notify::" NM_DEVICE_IP6_CONNECTIVITY,
 	                  G_CALLBACK (device_connectivity_changed),
 	                  self);
 
@@ -3409,7 +3504,7 @@ nm_manager_get_best_device_for_connection (NMManager *self,
 		flags = NM_DEVICE_CHECK_CON_AVAILABLE_NONE;
 	else {
 		/* if the profile is multi-connect=single, we also consider devices which
-		 * are marked as unmanaged. And explicit user-request shows sufficent user
+		 * are marked as unmanaged. And explicit user-request shows sufficient user
 		 * intent to make the device managed.
 		 * That is also, because we expect that such profile is suitably tied
 		 * to the intended device. So when an unmanaged device matches, the user's
@@ -3872,11 +3967,16 @@ ensure_master_active_connection (NMManager *self,
                                  GError **error)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMActiveConnection *ac;
 	NMActiveConnection *master_ac = NULL;
 	NMDeviceState master_state;
+	gboolean bind_lifetime_to_profile_visibility;
+
+	g_return_val_if_fail (connection, NULL);
+	g_return_val_if_fail (master_connection || master_device, FALSE);
 
-	g_assert (connection);
-	g_assert (master_connection || master_device);
+	bind_lifetime_to_profile_visibility = NM_FLAGS_HAS (nm_device_get_activation_state_flags (device),
+	                                                    NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
 
 	/* If the master device isn't activated then we need to activate it using
 	 * compatible connection.  If it's already activating we can just proceed.
@@ -3901,8 +4001,16 @@ ensure_master_active_connection (NMManager *self,
 		if (   (master_state == NM_DEVICE_STATE_ACTIVATED)
 		    || nm_device_is_activating (master_device)) {
 			/* Device already using master_connection */
-			g_assert (device_connection);
-			return NM_ACTIVE_CONNECTION (nm_device_get_act_request (master_device));
+			ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (master_device));
+			g_return_val_if_fail (device_connection, ac);
+
+			if (!bind_lifetime_to_profile_visibility) {
+				/* unbind the lifetime. */
+				nm_active_connection_set_state_flags_clear (ac,
+				                                            NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
+			}
+
+			return ac;
 		}
 
 		/* If the device is disconnected, find a compatible connection and
@@ -3939,6 +4047,9 @@ ensure_master_active_connection (NMManager *self,
 					                                            subject,
 					                                            NM_ACTIVATION_TYPE_MANAGED,
 					                                            activation_reason,
+					                                              bind_lifetime_to_profile_visibility
+					                                            ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
+					                                            : NM_ACTIVATION_STATE_FLAG_NONE,
 					                                            error);
 					return master_ac;
 				}
@@ -3987,6 +4098,9 @@ ensure_master_active_connection (NMManager *self,
 			                                            subject,
 			                                            NM_ACTIVATION_TYPE_MANAGED,
 			                                            activation_reason,
+			                                              bind_lifetime_to_profile_visibility
+			                                            ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
+			                                            : NM_ACTIVATION_STATE_FLAG_NONE,
 			                                            error);
 			return master_ac;
 		}
@@ -4104,7 +4218,7 @@ should_connect_slaves (NMConnection *connection, NMDevice *device)
 		goto out;
 
 	val = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                   "connection.autoconnect-slaves",
+	                                                   NM_CON_DEFAULT ("connection.autoconnect-slaves"),
 	                                                   device,
 	                                                   0, 1, -1);
 
@@ -4148,6 +4262,7 @@ autoconnect_slaves (NMManager *self,
 	                           master_device)) {
 		gs_free SlaveConnectionInfo *slaves = NULL;
 		guint i, n_slaves = 0;
+		gboolean bind_lifetime_to_profile_visibility;
 
 		slaves = find_slaves (self, master_connection, master_device, &n_slaves);
 		if (n_slaves > 1) {
@@ -4162,6 +4277,10 @@ autoconnect_slaves (NMManager *self,
 			                   GINT_TO_POINTER (!nm_streq0 (value, "index")));
 		}
 
+		bind_lifetime_to_profile_visibility =    n_slaves > 0
+		                                      && NM_FLAGS_HAS (nm_device_get_activation_state_flags (master_device),
+		                                                       NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
+
 		for (i = 0; i < n_slaves; i++) {
 			SlaveConnectionInfo *slave = &slaves[i];
 			const char *uuid;
@@ -4216,6 +4335,9 @@ autoconnect_slaves (NMManager *self,
 			                                subject,
 			                                NM_ACTIVATION_TYPE_MANAGED,
 			                                NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES,
+			                                  bind_lifetime_to_profile_visibility
+			                                ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
+			                                : NM_ACTIVATION_STATE_FLAG_NONE,
 			                                &local_err);
 			if (local_err) {
 				_LOGW (LOGD_CORE, "Slave connection activation failed: %s", local_err->message);
@@ -4276,6 +4398,40 @@ unmanaged_to_disconnected (NMDevice *device)
 	}
 }
 
+static NMActivationStateFlags
+_activation_bind_lifetime_to_profile_visibility (NMAuthSubject *subject)
+{
+	if (   nm_auth_subject_is_internal (subject)
+	    || nm_auth_subject_get_unix_process_uid (subject) == 0) {
+		/* internal requests and requests from root are always unbound. */
+		return NM_ACTIVATION_STATE_FLAG_NONE;
+	}
+
+	/* if the activation was not done by internal decision nor root, there
+	 * are the following cases:
+	 *
+	 * - the connection has "connection.permissions" unset and the profile
+	 *   is not restricted to a user and commonly always visible. It does
+	 *   not hurt to bind the lifetime, because we expect the profile to be
+	 *   visible at the moment. If the profile changes (while still being active),
+	 *   we want to pick-up changes to the visibility and possibly disconnect.
+	 *
+	 * - the connection has "connection.permissions" set, and the current user
+	 *   is the owner:
+	 *
+	 *      - Usually, we would expect that the profile is visible at the moment,
+	 *        and of course we want to bind the lifetime. The moment the user
+	 *        logs out, the connection becomes invisible and disconnects.
+	 *
+	 *      - the profile at this time could already be invisible (e.g. if the
+	 *        user didn't create a proper session (sudo) and manually activates
+	 *        an invisible profile. In this case, we still want to bind the
+	 *        lifetime, and it will disconnect after the user logs in and logs
+	 *        out again. NMKeepAlive takes care of that.
+	 */
+	return NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY;
+}
+
 /* The parent connection is ready; we can proceed realizing the device and
  * progressing the device to disconencted state.
  */
@@ -4405,6 +4561,8 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			                                            subject,
 			                                            NM_ACTIVATION_TYPE_MANAGED,
 			                                            nm_active_connection_get_activation_reason (active),
+			                                              nm_active_connection_get_state_flags (active)
+			                                            & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
 			                                            error);
 			if (!parent_ac) {
 				g_prefix_error (error, "%s failed to activate parent: ", nm_device_get_iface (device));
@@ -4530,7 +4688,9 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			for (i = 0; i < n_all; i++) {
 				nm_device_disconnect_active_connection (  all_ac_arr
 				                                        ? all_ac_arr->pdata[i]
-				                                        : ac);
+				                                        : ac,
+				                                        NM_DEVICE_STATE_REASON_NEW_ACTIVATION,
+				                                        NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN);
 			}
 		}
 	}
@@ -4603,6 +4763,7 @@ _new_active_connection (NMManager *self,
                         NMAuthSubject *subject,
                         NMActivationType activation_type,
                         NMActivationReason activation_reason,
+                        NMActivationStateFlags initial_state_flags,
                         GError **error)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
@@ -4674,6 +4835,7 @@ _new_active_connection (NMManager *self,
 		                                                     parent_device,
 		                                                     nm_dbus_object_get_path (NM_DBUS_OBJECT (parent)),
 		                                                     activation_reason,
+		                                                     initial_state_flags,
 		                                                     subject);
 	}
 
@@ -4683,6 +4845,7 @@ _new_active_connection (NMManager *self,
 	                                                  subject,
 	                                                  activation_type,
 	                                                  activation_reason,
+	                                                  initial_state_flags,
 	                                                  device);
 }
 
@@ -4746,6 +4909,7 @@ fail:
  * @activation_type: whether to assume the connection. That is, take over gracefully,
  *   non-destructible.
  * @activation_reason: the reason for activation
+ * @initial_state_flags: the initial state flags for the activation.
  * @error: return location for an error
  *
  * Begins a new internally-initiated activation of @sett_conn on @device.
@@ -4767,6 +4931,7 @@ nm_manager_activate_connection (NMManager *self,
                                 NMAuthSubject *subject,
                                 NMActivationType activation_type,
                                 NMActivationReason activation_reason,
+                                NMActivationStateFlags initial_state_flags,
                                 GError **error)
 {
 	NMManagerPrivate *priv;
@@ -4820,6 +4985,7 @@ nm_manager_activate_connection (NMManager *self,
 	                                 subject,
 	                                 activation_type,
 	                                 activation_reason,
+	                                 initial_state_flags,
 	                                 error);
 	if (!active)
 		return NULL;
@@ -4840,7 +5006,7 @@ nm_manager_activate_connection (NMManager *self,
  *   is only a partial activation.
  * @connection: the partial #NMConnection to be activated (if @sett_conn is unspecified)
  * @device_path: the object path of the device to be activated, or NULL
- * @out_device: on successful reutrn, the #NMDevice to be activated with @connection
+ * @out_device: on successful return, the #NMDevice to be activated with @connection
  *   The caller may pass in a device which shortcuts the lookup by path.
  *   In this case, the passed in device must have the matching @device_path
  *   already.
@@ -5079,6 +5245,7 @@ impl_manager_activate_connection (NMDBusObject *obj,
 	                                 subject,
 	                                 NM_ACTIVATION_TYPE_MANAGED,
 	                                 NM_ACTIVATION_REASON_USER_REQUEST,
+	                                 _activation_bind_lifetime_to_profile_visibility (subject),
 	                                 &error);
 	if (!active)
 		goto error;
@@ -5116,35 +5283,54 @@ activation_add_done (NMSettings *settings,
 	NMManager *self;
 	gs_unref_object NMActiveConnection *active = NULL;
 	gs_free_error GError *local = NULL;
+	gpointer persist_ptr;
+	NMSettingsConnectionPersistMode persist;
+	gpointer async_op_type_ptr;
+	AsyncOpType async_op_type;
+	GVariant *result_floating;
 
-	nm_utils_user_data_unpack (user_data, &self, &active);
-
-	if (!error) {
-		nm_active_connection_set_settings_connection (active, new_connection);
-
-		if (_internal_activate_generic (self, active, &local)) {
-			nm_settings_connection_update (new_connection,
-			                               NULL,
-			                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-			                               NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION | NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED,
-			                               "add-and-activate",
-			                               NULL);
-			g_dbus_method_invocation_return_value (
-			    context,
-			    g_variant_new ("(oo)",
-			                   nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
-			                   nm_dbus_object_get_path (NM_DBUS_OBJECT (active))));
-			nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
-			                            nm_active_connection_get_settings_connection (active),
-			                            TRUE,
-			                            NULL,
-			                            nm_active_connection_get_subject (active),
-			                            NULL);
-			return;
-		}
+	nm_utils_user_data_unpack (user_data, &self, &active, &persist_ptr, &async_op_type_ptr);
+	persist = GPOINTER_TO_INT (persist_ptr);
+	async_op_type = GPOINTER_TO_INT (async_op_type_ptr);
+
+	if (error)
+		goto fail;
+
+	nm_active_connection_set_settings_connection (active, new_connection);
+
+	if (!_internal_activate_generic (self, active, &local)) {
 		error = local;
+		goto fail;
+	}
+
+	nm_settings_connection_update (new_connection,
+	                               NULL,
+	                               persist,
+	                               NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION | NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED,
+	                               "add-and-activate",
+	                               NULL);
+
+	if (async_op_type == ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE) {
+		result_floating = g_variant_new ("(oo)",
+		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
+		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (active)));
+	} else {
+		result_floating = g_variant_new ("(oo@a{sv})",
+		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
+		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (active)),
+		                                 g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
 	}
+	g_dbus_method_invocation_return_value (context, result_floating);
 
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
+	                            nm_active_connection_get_settings_connection (active),
+	                            TRUE,
+	                            NULL,
+	                            nm_active_connection_get_subject (active),
+	                            NULL);
+	return;
+
+fail:
 	nm_assert (error);
 
 	nm_active_connection_set_state_fail (active,
@@ -5163,9 +5349,11 @@ activation_add_done (NMSettings *settings,
 
 static void
 _add_and_activate_auth_done (NMManager *self,
+                             AsyncOpType async_op_type,
                              NMActiveConnection *active,
                              NMConnection *connection,
                              GDBusMethodInvocation *invocation,
+                             NMSettingsConnectionPersistMode persist,
                              gboolean success,
                              const char *error_desc)
 {
@@ -5198,7 +5386,9 @@ _add_and_activate_auth_done (NMManager *self,
 	                                 invocation,
 	                                 activation_add_done,
 	                                 nm_utils_user_data_pack (self,
-	                                                          g_object_ref (active)));
+	                                                          g_object_ref (active),
+	                                                          GINT_TO_POINTER (persist),
+	                                                          GINT_TO_POINTER (async_op_type)));
 }
 
 static void
@@ -5213,17 +5403,79 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	gs_unref_object NMConnection *incompl_conn = NULL;
-	NMActiveConnection *active = NULL;
+	gs_unref_object NMActiveConnection *active = NULL;
 	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 	NMDevice *device = NULL;
 	gboolean is_vpn = FALSE;
 	gs_unref_variant GVariant *settings = NULL;
+	gs_unref_variant GVariant *options = NULL;
 	const char *device_path;
 	const char *specific_object_path;
 	gs_free NMConnection **conns = NULL;
+	NMSettingsConnectionPersistMode persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK;
+	gboolean bind_dbus_client = FALSE;
+	AsyncOpType async_op_type;
 
-	g_variant_get (parameters, "(@a{sa{sv}}&o&o)", &settings, &device_path, &specific_object_path);
+	if (nm_streq (method_info->parent.name, "AddAndActivateConnection2")) {
+		async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2;
+		g_variant_get (parameters, "(@a{sa{sv}}&o&o@a{sv})", &settings, &device_path, &specific_object_path, &options);
+	} else {
+		nm_assert (nm_streq (method_info->parent.name, "AddAndActivateConnection"));
+		async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE;
+		g_variant_get (parameters, "(@a{sa{sv}}&o&o)", &settings, &device_path, &specific_object_path);
+	}
+
+	if (options) {
+		GVariantIter iter;
+		const char *option_name;
+		GVariant *option_value;
+
+		g_variant_iter_init (&iter, options);
+		while (g_variant_iter_next (&iter, "{&sv}", &option_name, &option_value)) {
+			gs_unref_variant GVariant *option_value_free = NULL;
+			const char *s;
+
+			option_value_free = option_value;
+
+			if (   nm_streq (option_name, "persist")
+			    && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) {
+				s = g_variant_get_string (option_value, NULL);
+
+				if (nm_streq (s, "volatile"))
+					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_ONLY;
+				else if (nm_streq (s, "memory"))
+					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+				else if (nm_streq (s, "disk"))
+					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK;
+				else {
+					error = g_error_new_literal (NM_MANAGER_ERROR,
+					                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+					                             "Option \"persist\" must be one of \"volatile\", \"memory\" or \"disk\"");
+					goto error;
+				}
+			} else if (   nm_streq (option_name, "bind-activation")
+			           && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) {
+				s = g_variant_get_string (option_value, NULL);
+
+				if (nm_streq (s, "dbus-client"))
+					bind_dbus_client = TRUE;
+				else if (nm_streq (s, "none"))
+					bind_dbus_client = FALSE;
+				else {
+					error = g_error_new_literal (NM_MANAGER_ERROR,
+					                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+					                             "Option \"bind-activation\" must be one of \"dbus-client\" or \"none\"");
+					goto error;
+				}
+			} else {
+				error = g_error_new_literal (NM_MANAGER_ERROR,
+				                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+				                             "Unknown extra option passed");
+				goto error;
+			}
+		}
+	}
 
 	specific_object_path = nm_utils_dbus_normalize_object_path (specific_object_path);
 	device_path = nm_utils_dbus_normalize_object_path (device_path);
@@ -5291,17 +5543,28 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	                                 subject,
 	                                 NM_ACTIVATION_TYPE_MANAGED,
 	                                 NM_ACTIVATION_REASON_USER_REQUEST,
+	                                 _activation_bind_lifetime_to_profile_visibility (subject),
 	                                 &error);
 	if (!active)
 		goto error;
 
+	if (bind_dbus_client) {
+		NMKeepAlive *keep_alive;
+
+		keep_alive = nm_active_connection_get_keep_alive (active);
+		nm_keep_alive_set_dbus_client_watch (keep_alive, dbus_connection, sender);
+		nm_keep_alive_arm (keep_alive);
+	}
+
 	nm_active_connection_authorize (active,
 	                                incompl_conn,
 	                                _async_op_complete_ac_auth_cb,
 	                                _async_op_data_new_ac_auth_add_and_activate (self,
+	                                                                             async_op_type,
 	                                                                             active,
 	                                                                             invocation,
-	                                                                             incompl_conn));
+	                                                                             incompl_conn,
+	                                                                             persist));
 
 	/* we passed the pointers on to _async_op_data_new_ac_auth_add_and_activate() */
 	g_steal_pointer (&incompl_conn);
@@ -5321,31 +5584,26 @@ nm_manager_deactivate_connection (NMManager *manager,
                                   NMDeviceStateReason reason,
                                   GError **error)
 {
-	gboolean success = FALSE;
-
 	if (NM_IS_VPN_CONNECTION (active)) {
 		NMActiveConnectionStateReason vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED;
 
 		if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_CONNECTION_REMOVED)
 			vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED;
 
-		if (nm_vpn_connection_deactivate (NM_VPN_CONNECTION (active), vpn_reason, FALSE))
-			success = TRUE;
-		else
+		if (!nm_vpn_connection_deactivate (NM_VPN_CONNECTION (active), vpn_reason, FALSE)) {
 			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
 			                     "The VPN connection was not active.");
+			return FALSE;
+		}
 	} else {
-		g_assert (NM_IS_ACT_REQUEST (active));
-		nm_device_state_changed (nm_active_connection_get_device (active),
-		                         NM_DEVICE_STATE_DEACTIVATING,
-		                         reason);
-		success = TRUE;
+		nm_assert (NM_IS_ACT_REQUEST (active));
+		nm_device_disconnect_active_connection (active,
+		                                        reason,
+		                                        NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN);
 	}
 
-	if (success)
-		_notify (manager, PROP_ACTIVE_CONNECTIONS);
-
-	return success;
+	_notify (manager, PROP_ACTIVE_CONNECTIONS);
+	return TRUE;
 }
 
 static void
@@ -6171,6 +6429,12 @@ check_connectivity_auth_done_cb (NMAuthChain *chain,
 
 	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 		if (nm_device_check_connectivity (device,
+		                                  AF_INET,
+		                                  device_connectivity_done,
+		                                  data))
+			data->remaining++;
+		if (nm_device_check_connectivity (device,
+		                                  AF_INET6,
 		                                  device_connectivity_done,
 		                                  data))
 			data->remaining++;
@@ -6573,6 +6837,7 @@ _dbus_set_property_auth_cb (NMAuthChain *chain,
 	gs_unref_object NMManager *self = handle_data->self;
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthCallResult result;
+	gs_free_error GError *local = NULL;
 	const char *error_name = NULL;
 	const char *error_message = NULL;
 	GValue gvalue;
@@ -6610,7 +6875,10 @@ _dbus_set_property_auth_cb (NMAuthChain *chain,
 	}
 
 	g_dbus_gvariant_to_gvalue (value, &gvalue);
-	g_object_set_property (G_OBJECT (obj), property_info->property_name, &gvalue);
+	if (!nm_g_object_set_property (G_OBJECT (obj), property_info->property_name, &gvalue, &local)) {
+		error_name = "org.freedesktop.DBus.Error.InvalidArgs";
+		error_message = local->message;
+	}
 	g_value_unset (&gvalue);
 
 out:
@@ -6921,10 +7189,10 @@ rfkill_change (NMManager *self, const char *desc, RfKillType rtype, gboolean ena
 	int fd;
 	struct rfkill_event event;
 	ssize_t len;
+	int errsv;
 
 	g_return_if_fail (rtype == RFKILL_TYPE_WLAN || rtype == RFKILL_TYPE_WWAN);
 
-	errno = 0;
 	fd = open ("/dev/rfkill", O_RDWR | O_CLOEXEC);
 	if (fd < 0) {
 		if (errno == EACCES)
@@ -6955,14 +7223,15 @@ rfkill_change (NMManager *self, const char *desc, RfKillType rtype, gboolean ena
 
 	len = write (fd, &event, sizeof (event));
 	if (len < 0) {
-		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change WiFi killswitch state: (%d) %s",
-		       desc, errno, g_strerror (errno));
+		errsv = errno;
+		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state: (%d) %s",
+		       desc, errsv, nm_strerror_native (errsv));
 	} else if (len == sizeof (event)) {
 		_LOGI (LOGD_RFKILL, "rfkill: %s hardware radio set %s",
 		       desc, enabled ? "enabled" : "disabled");
 	} else {
 		/* Failed to write full structure */
-		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change WiFi killswitch state", desc);
+		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state", desc);
 	}
 
 	nm_close (fd);
@@ -7159,7 +7428,7 @@ constructed (GObject *object)
 	                  G_CALLBACK (rfkill_manager_rfkill_changed_cb),
 	                  self);
 
-	/* Force kernel WiFi/WWAN rfkill state to follow NM saved WiFi/WWAN state
+	/* Force kernel Wi-Fi/WWAN rfkill state to follow NM saved Wi-Fi/WWAN state
 	 * in case the BIOS doesn't save rfkill state, and to be consistent with user
 	 * changes to the WirelessEnabled/WWANEnabled properties which toggle kernel
 	 * rfkill.
@@ -7192,7 +7461,7 @@ nm_manager_init (NMManager *self)
 	priv->radio_states[RFKILL_TYPE_WLAN].key = NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED;
 	priv->radio_states[RFKILL_TYPE_WLAN].prop = NM_MANAGER_WIRELESS_ENABLED;
 	priv->radio_states[RFKILL_TYPE_WLAN].hw_prop = NM_MANAGER_WIRELESS_HARDWARE_ENABLED;
-	priv->radio_states[RFKILL_TYPE_WLAN].desc = "WiFi";
+	priv->radio_states[RFKILL_TYPE_WLAN].desc = "Wi-Fi";
 	priv->radio_states[RFKILL_TYPE_WLAN].rtype = RFKILL_TYPE_WLAN;
 
 	priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = TRUE;
@@ -7388,7 +7657,7 @@ set_property (GObject *object, guint prop_id,
 		                            g_value_get_boolean (value));
 		break;
 	case PROP_WIMAX_ENABLED:
-		/* WIMAX is depreacted. This does nothing. */
+		/* WIMAX is deprecated. This does nothing. */
 		break;
 	case PROP_CONNECTIVITY_CHECK_ENABLED:
 		nm_config_set_connectivity_check_enabled (priv->config,
@@ -7644,6 +7913,23 @@ static const NMDBusInterfaceInfoExtended interface_info_manager = {
 			),
 			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
 				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"AddAndActivateConnection2",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection",      "a{sa{sv}}"),
+						NM_DEFINE_GDBUS_ARG_INFO ("device",          "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("options",         "a{sv}"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("path",              "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("result",            "a{sv}"),
+					),
+				),
+				.handle = impl_manager_add_and_activate_connection,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
 					"DeactivateConnection",
 					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
 						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),