summary refs log tree commit diff
path: root/src/nm-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-device.c')
-rw-r--r--src/nm-device.c1010
1 files changed, 737 insertions, 273 deletions
diff --git a/src/nm-device.c b/src/nm-device.c
index cfd3ba56..c4cfbce0 100644
--- a/src/nm-device.c
+++ b/src/nm-device.c
@@ -40,7 +40,7 @@
 #include "nm-glib-compat.h"
 #include "nm-device.h"
 #include "nm-device-private.h"
-#include "backends/nm-backend.h"
+#include "nm-device-ethernet.h"
 #include "NetworkManagerUtils.h"
 #include "nm-system.h"
 #include "nm-dhcp-manager.h"
@@ -65,6 +65,8 @@
 #include "nm-connection-provider.h"
 #include "nm-posix-signals.h"
 #include "nm-manager-auth.h"
+#include "nm-dbus-glib-types.h"
+#include "nm-dispatcher.h"
 
 static void impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context);
 
@@ -123,6 +125,7 @@ enum {
 	PROP_TYPE_DESC,
 	PROP_RFKILL_TYPE,
 	PROP_IFINDEX,
+	PROP_AVAILABLE_CONNECTIONS,
 	LAST_PROP
 };
 
@@ -136,6 +139,7 @@ G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, G_TYPE_OBJECT)
 
 typedef enum {
 	IP_NONE = 0,
+	IP_WAIT,
 	IP_CONF,
 	IP_DONE
 } IpState;
@@ -147,6 +151,12 @@ typedef struct {
 } QueuedState;
 
 typedef struct {
+	NMDevice *slave;
+	gboolean enslaved;
+	guint watch_id;
+} SlaveInfo;
+
+typedef struct {
 	gboolean disposed;
 	gboolean initialized;
 
@@ -169,6 +179,7 @@ typedef struct {
 	gboolean      managed; /* whether managed by NM or not */
 	RfKillType    rfkill_type;
 	gboolean      firmware_missing;
+	GHashTable *  available_connections;
 
 	guint32         ip4_address;
 
@@ -177,8 +188,6 @@ typedef struct {
 	gpointer        act_source_func;
 	guint           act_source6_id;
 	gpointer        act_source6_func;
-	guint           act_dep_result_id;
-	guint           act_dep_timeout_id;
 	gulong          secrets_updated_id;
 	gulong          secrets_failed_id;
 
@@ -237,10 +246,20 @@ typedef struct {
 	/* allow autoconnect feature */
 	gboolean        autoconnect;
 
-	/* master interface for bridge, bond, vlan, etc */
-	NMDevice *	master;
+	/* master interface for bridge/bond slave */
+	NMDevice *      master;
+	gboolean        enslaved;
+
+	/* list of SlaveInfo for bond/bridge master */
+	GSList *        slaves;
 
 	NMConnectionProvider *con_provider;
+
+	/* connection provider signals for available connections property */
+	guint cp_added_id;
+	guint cp_loaded_id;
+	guint cp_removed_id;
+	guint cp_updated_id;
 } NMDevicePrivate;
 
 static void nm_device_take_down (NMDevice *dev, gboolean wait, NMDeviceStateReason reason);
@@ -258,10 +277,21 @@ static gboolean nm_device_set_ip6_config (NMDevice *dev,
 
 static gboolean nm_device_activate_ip6_config_commit (gpointer user_data);
 
+static gboolean check_connection_available (NMDevice *device, NMConnection *connection);
+
+static void _clear_available_connections (NMDevice *device, gboolean do_signal);
+
 static void dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release);
 
 static const char *reason_to_string (NMDeviceStateReason reason);
 
+static void cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
+static void cp_connections_loaded (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
+static void cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
+static void cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
+
+static const char *state_to_string (NMDeviceState state);
+
 static void
 nm_device_init (NMDevice *self)
 {
@@ -274,6 +304,7 @@ nm_device_init (NMDevice *self)
 	priv->dhcp_timeout = 0;
 	priv->rfkill_type = RFKILL_TYPE_UNKNOWN;
 	priv->autoconnect = DEFAULT_AUTOCONNECT;
+	priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
 }
 
 static void
@@ -468,7 +499,7 @@ nm_device_hw_is_up (NMDevice *self)
 }
 
 static guint32
-real_get_generic_capabilities (NMDevice *dev)
+get_generic_capabilities (NMDevice *dev)
 {
 	return 0;
 }
@@ -685,7 +716,7 @@ nm_device_get_type_capabilities (NMDevice *self)
 }
 
 static guint32
-real_get_type_capabilities (NMDevice *self)
+get_type_capabilities (NMDevice *self)
 {
 	return NM_DEVICE_CAP_NONE;
 }
@@ -713,6 +744,25 @@ nm_device_set_connection_provider (NMDevice *device,
 	g_return_if_fail (priv->con_provider == NULL);
 
 	priv->con_provider = provider;
+	priv->cp_added_id = g_signal_connect (priv->con_provider,
+	                                      NM_CP_SIGNAL_CONNECTION_ADDED,
+	                                      G_CALLBACK (cp_connection_added),
+	                                      device);
+
+	priv->cp_loaded_id = g_signal_connect (priv->con_provider,
+	                                       NM_CP_SIGNAL_CONNECTIONS_LOADED,
+	                                       G_CALLBACK (cp_connections_loaded),
+	                                       device);
+
+	priv->cp_removed_id = g_signal_connect (priv->con_provider,
+	                                        NM_CP_SIGNAL_CONNECTION_REMOVED,
+	                                        G_CALLBACK (cp_connection_removed),
+	                                        device);
+
+	priv->cp_updated_id = g_signal_connect (priv->con_provider,
+	                                        NM_CP_SIGNAL_CONNECTION_UPDATED,
+	                                        G_CALLBACK (cp_connection_updated),
+	                                        device);
 }
 
 NMConnectionProvider *
@@ -723,10 +773,35 @@ nm_device_get_connection_provider (NMDevice *device)
 	return NM_DEVICE_GET_PRIVATE (device)->con_provider;
 }
 
+static SlaveInfo *
+find_slave_info (NMDevice *self, NMDevice *slave)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	SlaveInfo *info;
+	GSList *iter;
+
+	for (iter = priv->slaves; iter; iter = g_slist_next (iter)) {
+		info = iter->data;
+		if (info->slave == slave)
+			return info;
+	}
+	return NULL;
+}
+
+static void
+free_slave_info (SlaveInfo *info)
+{
+	g_signal_handler_disconnect (info->slave, info->watch_id);
+	g_clear_object (&info->slave);
+	memset (info, 0, sizeof (*info));
+	g_free (info);
+}
+
 /**
  * nm_device_enslave_slave:
  * @dev: the master device
  * @slave: the slave device to enslave
+ * @connection: the slave device's connection
  *
  * If @dev is capable of enslaving other devices (ie it's a bridge, bond, etc)
  * then this function enslaves @slave.
@@ -734,22 +809,35 @@ nm_device_get_connection_provider (NMDevice *device)
  * Returns: %TRUE on success, %FALSE on failure or if this device cannot enslave
  *  other devices.
  */
-gboolean
-nm_device_enslave_slave (NMDevice *dev, NMDevice *slave)
+static gboolean
+nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connection)
 {
+	SlaveInfo *info;
+	gboolean success = FALSE;
+
 	g_return_val_if_fail (dev != NULL, FALSE);
 	g_return_val_if_fail (slave != NULL, FALSE);
 	g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
+	g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE);
 
-	if (NM_DEVICE_GET_CLASS (dev)->enslave_slave)
-		return NM_DEVICE_GET_CLASS (dev)->enslave_slave (dev, slave);
-	return FALSE;
+	info = find_slave_info (dev, slave);
+	if (!info)
+		return FALSE;
+
+	g_warn_if_fail (info->enslaved == FALSE);
+	success = NM_DEVICE_GET_CLASS (dev)->enslave_slave (dev, slave, connection);
+	if (success) {
+		info->enslaved = TRUE;
+		nm_device_slave_notify_enslaved (info->slave, TRUE, FALSE);
+	}
+	return success;
 }
 
 /**
- * nm_device_release_slave:
+ * nm_device_release_one_slave:
  * @dev: the master device
  * @slave: the slave device to release
+ * @failed: %TRUE if the release was unexpected, ie the master failed
  *
  * If @dev is capable of enslaving other devices (ie it's a bridge, bond, etc)
  * then this function releases the previously enslaved @slave.
@@ -757,15 +845,197 @@ nm_device_enslave_slave (NMDevice *dev, NMDevice *slave)
  * Returns: %TRUE on success, %FALSE on failure, if this device cannot enslave
  *  other devices, or if @slave was never enslaved.
  */
+static gboolean
+nm_device_release_one_slave (NMDevice *dev, NMDevice *slave, gboolean failed)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	SlaveInfo *info;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (slave != NULL, FALSE);
+	g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->release_slave != NULL, FALSE);
+
+	info = find_slave_info (dev, slave);
+	if (!info)
+		return FALSE;
+
+	if (info->enslaved) {
+		success = NM_DEVICE_GET_CLASS (dev)->release_slave (dev, slave);
+		g_warn_if_fail (success);
+	}
+	nm_device_slave_notify_enslaved (info->slave, FALSE, failed);
+
+	priv->slaves = g_slist_remove (priv->slaves, info);
+	free_slave_info (info);
+	return success;
+}
+
+static void
+slave_state_changed (NMDevice *slave,
+                     NMDeviceState slave_new_state,
+                     NMDeviceState slave_old_state,
+                     NMDeviceStateReason reason,
+                     NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean release = FALSE;
+
+	nm_log_dbg (LOGD_DEVICE, "(%s): slave %s state change %d (%s) -> %d (%s)",
+	            nm_device_get_iface (self),
+	            nm_device_get_iface (slave),
+	            slave_old_state,
+	            state_to_string (slave_old_state),
+	            slave_new_state,
+	            state_to_string (slave_new_state));
+
+	g_assert (priv->state > NM_DEVICE_STATE_DISCONNECTED);
+	g_assert (priv->state <= NM_DEVICE_STATE_ACTIVATED);
+
+	/* Don't try to enslave slaves until the master is ready */
+	if (priv->state < NM_DEVICE_STATE_CONFIG)
+		return;
+
+	if (slave_new_state == NM_DEVICE_STATE_IP_CONFIG)
+		nm_device_enslave_slave (self, slave, nm_device_get_connection (slave));
+	else if (slave_new_state > NM_DEVICE_STATE_ACTIVATED)
+		release = TRUE;
+	else if (   slave_new_state <= NM_DEVICE_STATE_DISCONNECTED
+	         && slave_old_state > NM_DEVICE_STATE_DISCONNECTED) {
+		/* Catch failures due to unavailable or unmanaged */
+		release = TRUE;
+	}
+
+	if (release) {
+		nm_device_release_one_slave (self, slave, FALSE);
+		if (priv->slaves == NULL) {
+			/* FIXME: all slaves gone; do something? */
+		}
+	}
+}
+
+/**
+ * nm_device_master_add_slave:
+ * @dev: the master device
+ * @slave: the slave device to enslave
+ *
+ * If @dev is capable of enslaving other devices (ie it's a bridge, bond, etc)
+ * then this function adds @slave to the slave list for later enslavement.
+ *
+ * Returns: %TRUE on success, %FALSE on failure
+ */
 gboolean
-nm_device_release_slave (NMDevice *dev, NMDevice *slave)
+nm_device_master_add_slave (NMDevice *dev, NMDevice *slave)
 {
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	SlaveInfo *info;
+
 	g_return_val_if_fail (dev != NULL, FALSE);
 	g_return_val_if_fail (slave != NULL, FALSE);
+	g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
+	g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE);
+
+	if (!find_slave_info (dev, slave)) {
+		info = g_malloc0 (sizeof (SlaveInfo));
+		info->slave = g_object_ref (slave);
+		info->watch_id = g_signal_connect (slave, "state-changed",
+		                                   G_CALLBACK (slave_state_changed), dev);
+		priv->slaves = g_slist_prepend (priv->slaves, info);
+	}
 
-	if (NM_DEVICE_GET_CLASS (dev)->release_slave)
-		return NM_DEVICE_GET_CLASS (dev)->release_slave (dev, slave);
-	return FALSE;
+	return TRUE;
+}
+
+
+/**
+ * nm_device_master_get_slaves:
+ * @dev: the master device
+ *
+ * Returns: any slaves of which @device is the master.  Caller owns returned list.
+ */
+GSList *
+nm_device_master_get_slaves (NMDevice *dev)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	GSList *slaves = NULL, *iter;
+
+	for (iter = priv->slaves; iter; iter = g_slist_next (iter))
+		slaves = g_slist_prepend (slaves, ((SlaveInfo *) iter->data)->slave);
+
+	return slaves;
+}
+
+/* release all slaves */
+static void
+nm_device_master_release_slaves (NMDevice *self, gboolean failed)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	GSList *iter;
+
+	for (iter = priv->slaves; iter; iter = g_slist_next (iter))
+		nm_device_release_one_slave (self, ((SlaveInfo *) iter->data)->slave, failed);
+	g_slist_free (priv->slaves);
+	priv->slaves = NULL;
+}
+
+
+/**
+ * nm_device_slave_notify_enslaved:
+ * @dev: the slave device
+ * @enslaved: %TRUE if the device is now enslaved, %FALSE if released
+ * @master_failed: if released, indicates whether the release was unexpected,
+ *   ie the master device failed.
+ *
+ * Notifies a slave that it has been enslaved or released.  If released, provides
+ * information on whether the release was expected or not, and thus whether the
+ * slave should fail it's activation or gracefully deactivate.
+ */
+void
+nm_device_slave_notify_enslaved (NMDevice *dev,
+                                 gboolean enslaved,
+                                 gboolean master_failed)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	NMConnection *connection = nm_device_get_connection (dev);
+
+	g_assert (priv->master);
+
+	if (enslaved) {
+		g_warn_if_fail (priv->enslaved == FALSE);
+		g_warn_if_fail (priv->state == NM_DEVICE_STATE_IP_CONFIG);
+
+		nm_log_info (LOGD_DEVICE,
+				     "Activation (%s) connection '%s' enslaved, continuing activation",
+				     nm_device_get_iface (dev),
+				     nm_connection_get_id (connection));
+
+		/* Now that we're enslaved, proceed with activation.  Remember, slaves
+		 * don't have any IP configuration, so they skip directly to SECONDARIES.
+		 */
+		priv->enslaved = TRUE;
+		priv->ip4_state = IP_DONE;
+		priv->ip6_state = IP_DONE;
+		nm_device_queue_state (dev, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE);
+	} else {
+		NMDeviceState new_state = NM_DEVICE_STATE_DISCONNECTED;
+		NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+
+		if (master_failed) {
+			new_state = NM_DEVICE_STATE_FAILED;
+			reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
+
+			nm_log_warn (LOGD_DEVICE,
+					     "Activation (%s) connection '%s' master failed",
+					     nm_device_get_iface (dev),
+					     nm_connection_get_id (connection));
+		} else {
+			nm_log_dbg (LOGD_DEVICE,
+					    "Activation (%s) connection '%s' master deactivated",
+					    nm_device_get_iface (dev),
+					    nm_connection_get_id (connection));
+		}
+
+		nm_device_queue_state (dev, new_state, reason);
+	}
 }
 
 /*
@@ -1055,28 +1325,9 @@ ip6_method_matches (NMConnection *connection, const char *match)
 }
 
 static NMActStageReturn
-real_act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason)
+act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason)
 {
-	NMActRequest *req;
-	NMActiveConnection *master_ac;
-	NMDevice *master;
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
-
-	req = nm_device_get_act_request (self);
-	g_assert (req);
-
-	/* If the interface is going to be a slave, let the master enslave it here */
-	master_ac = nm_act_request_get_dependency (req);
-	if (master_ac && NM_IS_ACT_REQUEST (master_ac)) {
-		/* FIXME: handle VPNs here too */
-
-		master = NM_DEVICE (nm_act_request_get_device (NM_ACT_REQUEST (master_ac)));
-		g_assert (master);
-		if (!nm_device_enslave_slave (master, self))
-			ret = NM_ACT_STAGE_RETURN_FAILURE;
-	}
-
-	return ret;
+	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 /*
@@ -1097,11 +1348,6 @@ nm_device_activate_stage1_device_prepare (gpointer user_data)
 	/* Clear the activation source ID now that this stage has run */
 	activation_source_clear (self, FALSE, 0);
 
-	if (priv->act_dep_timeout_id) {
-		g_source_remove (priv->act_dep_timeout_id);
-		priv->act_dep_timeout_id = 0;
-	}
-
 	priv->ip4_state = priv->ip6_state = IP_NONE;
 
 	iface = nm_device_get_iface (self);
@@ -1148,8 +1394,19 @@ nm_device_activate_schedule_stage1_device_prepare (NMDevice *self)
 }
 
 static NMActStageReturn
-real_act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason)
+act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason)
 {
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	GSList *iter;
+
+	/* If we have slaves that aren't yet enslaved, do that now */
+	for (iter = priv->slaves; iter; iter = g_slist_next (iter)) {
+		SlaveInfo *info = iter->data;
+
+		if (nm_device_get_state (info->slave) == NM_DEVICE_STATE_IP_CONFIG)
+			nm_device_enslave_slave (dev, info->slave, nm_device_get_connection (info->slave));
+	}
+
 	/* Nothing to do */
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
@@ -1550,14 +1807,14 @@ dhcp4_lease_change (NMDevice *device, NMIP4Config *config)
 	nm_utils_merge_ip4_config (config, nm_connection_get_setting_ip4_config (connection));
 
 	if (!nm_device_set_ip4_config (device, config, FALSE, &reason)) {
-		nm_log_warn (LOGD_DHCP6, "(%s): failed to update IPv4 config in response to DHCP event.",
+		nm_log_warn (LOGD_DHCP4, "(%s): failed to update IPv4 config in response to DHCP event.",
 		             nm_device_get_ip_iface (device));
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
 		return;
 	}
 
 	/* Notify dispatcher scripts of new DHCP4 config */
-	nm_utils_call_dispatcher ("dhcp4-change", connection, device, NULL, NULL, NULL);
+	nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE, connection, device, NULL, NULL);
 }
 
 static void
@@ -1770,9 +2027,9 @@ shared4_new_config (NMDevice *self, NMDeviceStateReason *reason)
 /*********************************************/
 
 static NMActStageReturn
-real_act_stage3_ip4_config_start (NMDevice *self,
-                                  NMIP4Config **out_config,
-                                  NMDeviceStateReason *reason)
+act_stage3_ip4_config_start (NMDevice *self,
+                             NMIP4Config **out_config,
+                             NMDeviceStateReason *reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
@@ -1932,7 +2189,7 @@ dhcp6_lease_change (NMDevice *device)
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
 	} else {
 		/* Notify dispatcher scripts of new DHCPv6 config */
-		nm_utils_call_dispatcher ("dhcp6-change", connection, device, NULL, NULL, NULL);
+		nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, connection, device, NULL, NULL);
 	}
 }
 
@@ -2030,8 +2287,6 @@ dhcp6_start (NMDevice *self,
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	guint8 *anycast = NULL;
 	const char *ip_iface;
-	const struct in6_addr dest = { { { 0xFF,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } };
-	int err;
 
 	if (!connection) {
 		connection = nm_device_get_connection (self);
@@ -2054,18 +2309,6 @@ dhcp6_start (NMDevice *self,
 		priv->dhcp6_ip6_config = NULL;
 	}
 
-	/* DHCPv6 communicates with the DHCPv6 server via two multicast addresses,
-	 * ff02::1:2 (link-scope) and ff05::1:3 (site-scope).  Make sure we have
-	 * a multicast route (ff00::/8) for client <-> server communication.
-	 */
-	err = nm_system_set_ip6_route (priv->ip_iface ? priv->ip_ifindex : priv->ifindex,
-	                               &dest, 8, NULL, 256, 0, RTPROT_BOOT, RT_TABLE_LOCAL, NULL);
-	if (err && (err != -NLE_EXIST)) {
-		nm_log_err (LOGD_DEVICE | LOGD_IP6,
-		            "(%s): failed to add IPv6 multicast route: %s",
-		            priv->ip_iface ? priv->ip_iface : priv->iface, nl_geterror (err));
-	}
-
 	ip_iface = nm_device_get_ip_iface (self);
 	priv->dhcp6_client = nm_dhcp_manager_start_ip6 (priv->dhcp_manager,
 	                                                ip_iface,
@@ -2270,10 +2513,52 @@ addrconf6_cleanup (NMDevice *self)
 
 /******************************************/
 
+/* Get net.ipv6.conf.default.use_tempaddr value from /etc/sysctl.conf or
+ * /lib/sysctl.d/sysctl.conf
+ */
+static int
+ip6_use_tempaddr (void)
+{
+	char *contents = NULL;
+	gsize len = 0;
+	const char *group_name = "[forged_group]\n";
+	char *sysctl_data = NULL;
+	GKeyFile *keyfile;
+	GError *error = NULL;
+	int tmp, ret = -1;
+
+	/* Read file contents to a string. */
+	if (!g_file_get_contents ("/etc/sysctl.conf", &contents, &len, NULL))
+		if (!g_file_get_contents ("/lib/sysctl.d/sysctl.conf", &contents, &len, NULL))
+			return -1;
+
+	/* Prepend a group so that we can use GKeyFile parser. */
+	sysctl_data = g_strdup_printf ("%s%s", group_name, contents);
+
+	keyfile = g_key_file_new ();
+	if (keyfile == NULL)
+		goto done;
+
+	if (!g_key_file_load_from_data (keyfile, sysctl_data, len + strlen (group_name), G_KEY_FILE_NONE, NULL))
+		goto done;
+
+	tmp = g_key_file_get_integer (keyfile, "forged_group", "net.ipv6.conf.default.use_tempaddr", &error);
+	if (error == NULL)
+		ret = tmp;
+
+done:
+	g_free (contents);
+	g_free (sysctl_data);
+	g_clear_error (&error);
+	g_key_file_free (keyfile);
+
+	return ret;
+}
+
 static NMActStageReturn
-real_act_stage3_ip6_config_start (NMDevice *self,
-                                  NMIP6Config **out_config,
-                                  NMDeviceStateReason *reason)
+act_stage3_ip6_config_start (NMDevice *self,
+                             NMIP6Config **out_config,
+                             NMDeviceStateReason *reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const char *ip_iface;
@@ -2306,7 +2591,7 @@ real_act_stage3_ip6_config_start (NMDevice *self,
 	} else if (ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
 		/* Router advertisements shouldn't be used in pure DHCP mode */
 		if (priv->ip6_accept_ra_path)
-			nm_utils_do_sysctl (priv->ip6_accept_ra_path, "0\n");
+			nm_utils_do_sysctl (priv->ip6_accept_ra_path, "0");
 
 		priv->dhcp6_mode = IP6_DHCP_OPT_MANAGED;
 		ret = dhcp6_start (self, connection, priv->dhcp6_mode, reason);
@@ -2314,7 +2599,7 @@ real_act_stage3_ip6_config_start (NMDevice *self,
 		/* reset the saved RA value when ipv6 is ignored */
 		if (priv->ip6_accept_ra_path) {
 			nm_utils_do_sysctl (priv->ip6_accept_ra_path,
-			                    priv->ip6_accept_ra_save ? "1\n" : "0\n");
+			                    priv->ip6_accept_ra_save ? "1" : "0");
 		}
 		ret = NM_ACT_STAGE_RETURN_STOP;
 	} else if (ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
@@ -2324,7 +2609,7 @@ real_act_stage3_ip6_config_start (NMDevice *self,
 
 		/* Router advertisements shouldn't be used in manual mode */
 		if (priv->ip6_accept_ra_path)
-			nm_utils_do_sysctl (priv->ip6_accept_ra_path, "0\n");
+			nm_utils_do_sysctl (priv->ip6_accept_ra_path, "0");
 		ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	} else {
 		nm_log_warn (LOGD_IP6, "(%s): unhandled IPv6 config method; will fail",
@@ -2337,7 +2622,7 @@ real_act_stage3_ip6_config_start (NMDevice *self,
 	 * If a global value is configured by sysadmin (e.g. /etc/sysctl.conf),
 	 * use that value instead of per-connection value.
 	 */
-	conf_use_tempaddr = nm_backend_ipv6_use_tempaddr ();
+	conf_use_tempaddr = ip6_use_tempaddr ();
 	if (conf_use_tempaddr >= 0)
 		ip6_privacy = conf_use_tempaddr;
 	else {
@@ -2350,13 +2635,13 @@ real_act_stage3_ip6_config_start (NMDevice *self,
 	switch (ip6_privacy) {
 	case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
 	case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
-		ip6_privacy_str = "0\n";
+		ip6_privacy_str = "0";
 	break;
 	case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
-		ip6_privacy_str = "1\n";
+		ip6_privacy_str = "1";
 	break;
 	case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
-		ip6_privacy_str = "2\n";
+		ip6_privacy_str = "2";
 	break;
 	}
 	if (priv->ip6_privacy_tempaddr_path)
@@ -2365,39 +2650,23 @@ real_act_stage3_ip6_config_start (NMDevice *self,
 	return ret;
 }
 
-
-/*
- * nm_device_activate_stage3_ip_config_start
- *
- * Begin automatic/manual IP configuration
+/**
+ * nm_device_activate_stage3_ip4_start:
+ * @self: the device
  *
+ * Try starting IPv4 configuration.
  */
-static gboolean
-nm_device_activate_stage3_ip_config_start (gpointer user_data)
+gboolean
+nm_device_activate_stage3_ip4_start (NMDevice *self)
 {
-	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *iface;
 	NMActStageReturn ret;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 	NMIP4Config *ip4_config = NULL;
-	NMIP6Config *ip6_config = NULL;
-	int ifindex;
 
-	/* Clear the activation source ID now that this stage has run */
-	activation_source_clear (self, FALSE, 0);
-
-	iface = nm_device_get_iface (self);
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) started...", iface);
-	nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
-
-	/* Make sure the interface is up before trying to do anything with it */
-	ifindex = nm_device_get_ip_ifindex (self);
-	if ((ifindex > 0) && (nm_system_iface_is_up (ifindex) == FALSE))
-		nm_system_iface_set_up (ifindex, TRUE, NULL);
-
-	priv->ip4_state = priv->ip6_state = IP_CONF;
+	g_assert (priv->ip4_state == IP_WAIT);
 
+	priv->ip4_state = IP_CONF;
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		g_assert (ip4_config);
@@ -2405,13 +2674,36 @@ nm_device_activate_stage3_ip_config_start (gpointer user_data)
 		g_object_unref (ip4_config);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-		goto out;
+		return FALSE;
 	} else if (ret == NM_ACT_STAGE_RETURN_STOP) {
-		/* Nothing to do */
+		/* Early finish */
 		priv->ip4_state = IP_DONE;
+	} else if (ret == NM_ACT_STAGE_RETURN_WAIT) {
+		/* Wait for something to try IP config again */
+		priv->ip4_state = IP_WAIT;
 	} else
 		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
 
+	return TRUE;
+}
+
+/**
+ * nm_device_activate_stage3_ip6_start:
+ * @self: the device
+ *
+ * Try starting IPv6 configuration.
+ */
+gboolean
+nm_device_activate_stage3_ip6_start (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMActStageReturn ret;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	NMIP6Config *ip6_config = NULL;
+
+	g_assert (priv->ip6_state == IP_WAIT);
+
+	priv->ip6_state = IP_CONF;
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		g_assert (ip6_config);
@@ -2419,24 +2711,77 @@ nm_device_activate_stage3_ip_config_start (gpointer user_data)
 		g_object_unref (ip6_config);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-		goto out;
+		return FALSE;
 	} else if (ret == NM_ACT_STAGE_RETURN_STOP) {
-		/* Nothing to do */
+		/* Early finish */
 		priv->ip6_state = IP_DONE;
+	} else if (ret == NM_ACT_STAGE_RETURN_WAIT) {
+		/* Wait for something to try IP config again */
+		priv->ip6_state = IP_WAIT;
 	} else
 		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
 
-out:
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) complete.", iface);
+	return TRUE;
+}
 
-	/* Handle interfaces (bond slaves, etc) that won't have any IP config; they
-	 * need to move to ACTIVATED.
+/*
+ * nm_device_activate_stage3_ip_config_start
+ *
+ * Begin automatic/manual IP configuration
+ *
+ */
+static gboolean
+nm_device_activate_stage3_ip_config_start (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const char *iface;
+	int ifindex;
+	NMDevice *master;
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, 0);
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) started...", iface);
+	nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+
+	/* Make sure the interface is up before trying to do anything with it */
+	ifindex = nm_device_get_ip_ifindex (self);
+	if ((ifindex > 0) && (nm_system_iface_is_up (ifindex) == FALSE))
+		nm_system_iface_set_up (ifindex, TRUE, NULL);
+
+	priv->ip4_state = priv->ip6_state = IP_WAIT;
+
+	/* If the device is a slave, then we don't do any IP configuration but we
+	 * use the IP config stage to indicate to the master we're ready for
+	 * enslavement.  Either the master has already enslaved us, in which case
+	 * our state transition to SECONDARIES is already queued courtesy of
+	 * nm_device_slave_notify_enslaved(), or the master is still activating,
+	 * in which case we postpone activation here until the master enslaves us,
+	 * which calls nm_device_slave_notify_enslaved().
 	 */
-	if (priv->ip4_state == IP_DONE && priv->ip6_state == IP_DONE) {
-		/* FIXME: call layer2 stuff to set MTU? */
-		nm_device_state_changed (self, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE);
+	master = nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request));
+	if (master) {
+		if (priv->enslaved == FALSE) {
+			nm_log_info (LOGD_DEVICE, "Activation (%s) connection '%s' waiting on master '%s'",
+						 nm_device_get_iface (self),
+						 nm_connection_get_id (nm_device_get_connection (self)),
+						 nm_device_get_iface (master));
+		}
+		goto out;
 	}
 
+	/* IPv4 */
+	if (!nm_device_activate_stage3_ip4_start (self))
+		goto out;
+
+	/* IPv6 */
+	if (!nm_device_activate_stage3_ip6_start (self))
+		goto out;
+
+out:
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) complete.", iface);
 	return FALSE;
 }
 
@@ -2479,7 +2824,7 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 	g_return_if_fail (priv->act_request);
 
 	state = nm_device_get_state (self);
-	if (nm_act_request_get_assumed (priv->act_request) == FALSE)
+	if (nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (priv->act_request)) == FALSE)
 		g_warn_if_fail (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_NEED_AUTH);
 
 	/* Add the interface to the specified firewall zone */
@@ -2499,7 +2844,7 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 }
 
 static NMActStageReturn
-real_act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
+act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
 {
 	if (nm_device_ip_config_should_fail (self, FALSE)) {
 		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
@@ -2583,7 +2928,7 @@ nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
 
 
 static NMActStageReturn
-real_act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
+act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
 {
 	if (nm_device_ip_config_should_fail (self, TRUE)) {
 		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
@@ -2685,13 +3030,13 @@ share_init (void)
 	                    NULL };
 	char **iter;
 
-	if (!nm_utils_do_sysctl ("/proc/sys/net/ipv4/ip_forward", "1\n")) {
+	if (!nm_utils_do_sysctl ("/proc/sys/net/ipv4/ip_forward", "1")) {
 		nm_log_err (LOGD_SHARING, "Error starting IP forwarding: (%d) %s",
 					errno, strerror (errno));
 		return FALSE;
 	}
 
-	if (!nm_utils_do_sysctl ("/proc/sys/net/ipv4/ip_dynaddr", "1\n")) {
+	if (!nm_utils_do_sysctl ("/proc/sys/net/ipv4/ip_dynaddr", "1")) {
 		nm_log_err (LOGD_SHARING, "error starting IP forwarding: (%d) %s",
 					errno, strerror (errno));
 	}
@@ -2777,7 +3122,8 @@ start_sharing (NMDevice *self, NMIP4Config *config)
 
 	if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, config, &error)) {
 		nm_log_err (LOGD_SHARING, "(%s/%s): failed to start dnsmasq: %s",
-		            nm_device_get_iface (self), ip_iface, error->message);
+		            nm_device_get_iface (self), ip_iface,
+		            (error && error->message) ? error->message : "(unknown)");
 		g_error_free (error);
 		nm_act_request_set_shared (req, FALSE);
 		return FALSE;
@@ -2830,7 +3176,7 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 	/* Merge with user overrides */
 	nm_utils_merge_ip4_config (config, nm_connection_get_setting_ip4_config (connection));
 
-	assumed = nm_act_request_get_assumed (priv->act_request);
+	assumed = nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (priv->act_request));
 	if (!nm_device_set_ip4_config (self, config, assumed, &reason)) {
 		nm_log_info (LOGD_DEVICE | LOGD_IP4,
 			         "Activation (%s) Stage 5 of 5 (IPv4 Commit) failed",
@@ -2852,10 +3198,10 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 		}
 	}
 
-	/* Enter the ACTIVATED state if this is the first method to complete */
+	/* Enter the SECONDARIES state if this is the first method to complete */
 	priv->ip4_state = IP_DONE;
 	if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG)
-		nm_device_state_changed (self, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE);
+		nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE);
 
 out:
 	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv4 Commit) complete.",
@@ -2938,10 +3284,10 @@ nm_device_activate_ip6_config_commit (gpointer user_data)
 		NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self, config);
 
 	if (ip6_config_merge_and_apply (self, config, &reason)) {
-		/* Enter the ACTIVATED state if this is the first method to complete */
+		/* Enter the SECONDARIES state if this is the first method to complete */
 		priv->ip6_state = IP_DONE;
 		if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG)
-			nm_device_state_changed (self, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE);
+			nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE);
 	} else {
 		nm_log_info (LOGD_DEVICE | LOGD_IP6,
 			         "Activation (%s) Stage 5 of 5 (IPv6 Commit) failed",
@@ -3181,15 +3527,6 @@ nm_device_deactivate (NMDevice *self, NMDeviceStateReason reason)
 	activation_source_clear (self, TRUE, AF_INET);
 	activation_source_clear (self, TRUE, AF_INET6);
 
-	if (priv->act_dep_result_id) {
-		g_source_remove (priv->act_dep_result_id);
-		priv->act_dep_result_id = 0;
-	}
-	if (priv->act_dep_timeout_id) {
-		g_source_remove (priv->act_dep_timeout_id);
-		priv->act_dep_timeout_id = 0;
-	}
-
 	/* Clear any queued transitions */
 	nm_device_queued_state_clear (self);
 
@@ -3205,16 +3542,23 @@ nm_device_deactivate (NMDevice *self, NMDeviceStateReason reason)
 
 	/* Turn off router advertisements until they are needed */
 	if (priv->ip6_accept_ra_path)
-		nm_utils_do_sysctl (priv->ip6_accept_ra_path, "0\n");
+		nm_utils_do_sysctl (priv->ip6_accept_ra_path, "0");
 
 	/* Turn off IPv6 privacy extensions */
 	if (priv->ip6_privacy_tempaddr_path)
-		nm_utils_do_sysctl (priv->ip6_privacy_tempaddr_path, "0\n");
+		nm_utils_do_sysctl (priv->ip6_privacy_tempaddr_path, "0");
 
 	/* Call device type-specific deactivation */
 	if (NM_DEVICE_GET_CLASS (self)->deactivate)
 		NM_DEVICE_GET_CLASS (self)->deactivate (self);
 
+	/* master: release slaves */
+	g_clear_object (&priv->master);
+	nm_device_master_release_slaves (self, FALSE);
+
+	/* slave: mark no longer enslaved */
+	priv->enslaved = FALSE;
+
 	/* Tear down an existing activation request */
 	clear_act_request (self);
 
@@ -3284,71 +3628,16 @@ impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context)
 	               NULL);
 }
 
-static gboolean
-act_dep_timeout_cb (gpointer user_data)
-{
-	NMDevice *self = NM_DEVICE (user_data);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-
-	connection = nm_active_connection_get_connection (NM_ACTIVE_CONNECTION (priv->act_request));
-	nm_log_warn (LOGD_DEVICE,
-	             "Activation (%s) connection '%s' dependency timed out",
-	             nm_device_get_iface (self),
-	             nm_connection_get_id (connection));
-
-	nm_device_queue_state (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
-	return FALSE;
-}
-
-static void
-act_dep_result_cb (NMActRequest *req,
-                   NMActRequestDependencyResult result,
-                   NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-
-	connection = nm_active_connection_get_connection (NM_ACTIVE_CONNECTION (priv->act_request));
-
-	switch (result) {
-	case NM_ACT_REQUEST_DEP_RESULT_FAILED:
-		g_source_remove (priv->act_dep_result_id);
-		priv->act_dep_result_id = 0;
-
-		nm_log_warn (LOGD_DEVICE,
-			         "Activation (%s) connection '%s' dependency failed",
-			         nm_device_get_iface (self),
-			         nm_connection_get_id (connection));
-		nm_device_queue_state (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
-		break;
-	case NM_ACT_REQUEST_DEP_RESULT_READY:
-		g_warn_if_fail (priv->state == NM_DEVICE_STATE_PREPARE);
-		if (priv->state == NM_DEVICE_STATE_PREPARE) {
-			nm_log_info (LOGD_DEVICE,
-					     "Activation (%s) connection '%s' dependency ready, continuing activation",
-					     nm_device_get_iface (self),
-					     nm_connection_get_id (connection));
-			nm_device_activate_schedule_stage1_device_prepare (self);
-		}
-		break;
-	case NM_ACT_REQUEST_DEP_RESULT_WAIT:
-	default:
-		g_assert_not_reached ();
-		break;
-	}
-}
-
-gboolean
-nm_device_activate (NMDevice *self, NMActRequest *req, GError **error)
+void
+nm_device_activate (NMDevice *self, NMActRequest *req)
 {
 	NMDevicePrivate *priv;
 	NMConnection *connection;
 
-	g_return_val_if_fail (self != NULL, FALSE);
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-	g_return_val_if_fail (req != NULL, FALSE);
-	g_return_val_if_fail (NM_IS_ACT_REQUEST (req), FALSE);
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (req != NULL);
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
@@ -3359,27 +3648,20 @@ nm_device_activate (NMDevice *self, NMActRequest *req, GError **error)
 	             nm_device_get_iface (self),
 	             nm_connection_get_id (connection));
 
-	/* Make sure this connection isn't activated already, or in the process of
-	 * being activated.
-	 */
-	if (   nm_device_is_activating (self)
-	    || (nm_device_get_state (self) == NM_DEVICE_STATE_ACTIVATED)) {
-		NMConnection *new = nm_act_request_get_connection (req);
-		NMConnection *current = nm_act_request_get_connection (priv->act_request);
-
-		if (new == current) {
-			g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CONNECTION_ACTIVATING,
-			                     "Connection is already activating");
-			return FALSE;
-		}
-	}
+	g_warn_if_fail (priv->state == NM_DEVICE_STATE_DISCONNECTED);
 
 	priv->act_request = g_object_ref (req);
 	g_object_notify (G_OBJECT (self), NM_DEVICE_ACTIVE_CONNECTION);
 
-	if (!nm_act_request_get_assumed (req)) {
-		NMActiveConnection *dep_ac;
-		NMConnection *dep_con;
+	if (nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (req))) {
+		/* If it's an assumed connection, let the device subclass short-circuit
+		 * the normal connection process and just copy its IP configs from the
+		 * interface.
+		 */
+		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+		nm_device_activate_schedule_stage3_ip_config_start (self);
+	} else {
+		NMDevice *master;
 
 		/* HACK: update the state a bit early to avoid a race between the 
 		 * scheduled stage1 handler and nm_policy_device_change_check() thinking
@@ -3389,43 +3671,18 @@ nm_device_activate (NMDevice *self, NMActRequest *req, GError **error)
 		nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
 
 		/* Handle any dependencies this connection might have */
-		switch (nm_act_request_get_dependency_result (priv->act_request)) {
-		case NM_ACT_REQUEST_DEP_RESULT_FAILED:
-			nm_device_queue_state (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
-			break;
-		case NM_ACT_REQUEST_DEP_RESULT_WAIT:
-			dep_ac = nm_act_request_get_dependency (priv->act_request);
-			g_assert (dep_ac);
-			dep_con = nm_active_connection_get_connection (dep_ac);
-			g_assert (dep_con);
-			nm_log_info (LOGD_DEVICE, "Activation (%s) connection '%s' waiting on dependency '%s'",
-						 nm_device_get_iface (self),
-						 nm_connection_get_id (connection),
-						 nm_connection_get_id (dep_con));
-
-			priv->act_dep_result_id = g_signal_connect (priv->act_request,
-					                                    NM_ACT_REQUEST_DEPENDENCY_RESULT,
-					                                    G_CALLBACK (act_dep_result_cb),
-					                                    self);
-			priv->act_dep_timeout_id = g_timeout_add_seconds (60, act_dep_timeout_cb, self);
-			break;
-		default:
-			g_warn_if_reached ();
-			/* fall through */
-		case NM_ACT_REQUEST_DEP_RESULT_READY:
-			nm_device_activate_schedule_stage1_device_prepare (self);
-			break;
+		master = nm_active_connection_get_master (NM_ACTIVE_CONNECTION (req));
+		if (master) {
+			/* Master should at least already be activating */
+			g_assert (nm_device_get_state (master) > NM_DEVICE_STATE_DISCONNECTED);
+
+			g_assert (priv->master == NULL);
+			priv->master = g_object_ref (master);
+			nm_device_master_add_slave (master, self);
 		}
-	} else {
-		/* If it's an assumed connection, let the device subclass short-circuit
-		 * the normal connection process and just copy its IP configs from the
-		 * interface.
-		 */
-		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
-		nm_device_activate_schedule_stage3_ip_config_start (self);
-	}
 
-	return TRUE;
+		nm_device_activate_schedule_stage1_device_prepare (self);
+	}
 }
 
 /*
@@ -3743,7 +4000,6 @@ dispose (GObject *object)
 
 		connection = nm_device_get_connection (self);
 		if (connection) {
-
 			/* Only static or DHCP IPv4 connections can be left up.
 			 * All IPv6 connections can be left up, so we don't have
 			 * to check that.
@@ -3753,7 +4009,8 @@ dispose (GObject *object)
 				method = nm_setting_ip4_config_get_method (s_ip4);
 			if (   !method
 			    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)
-			    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+			    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)
+			    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
 				take_down = FALSE;
 		}
 	}
@@ -3767,6 +4024,8 @@ dispose (GObject *object)
 	addrconf6_cleanup (self);
 	dnsmasq_cleanup (self);
 
+	g_warn_if_fail (priv->slaves == NULL);
+
 	/* Take the device itself down and clear its IPv4 configuration */
 	if (priv->managed && take_down) {
 		NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE;
@@ -3778,7 +4037,7 @@ dispose (GObject *object)
 	/* reset the saved RA value */
 	if (priv->ip6_accept_ra_path) {
 		nm_utils_do_sysctl (priv->ip6_accept_ra_path,
-		                    priv->ip6_accept_ra_save ? "1\n" : "0\n");
+		                    priv->ip6_accept_ra_save ? "1" : "0");
 	}
 	g_free (priv->ip6_accept_ra_path);
 
@@ -3786,11 +4045,33 @@ dispose (GObject *object)
 	if (priv->ip6_privacy_tempaddr_path) {
 		char tmp[16];
 
-		snprintf (tmp, sizeof (tmp), "%d\n", priv->ip6_privacy_tempaddr_save);
+		snprintf (tmp, sizeof (tmp), "%d", priv->ip6_privacy_tempaddr_save);
 		nm_utils_do_sysctl (priv->ip6_privacy_tempaddr_path, tmp);
 	}
 	g_free (priv->ip6_privacy_tempaddr_path);
 
+	if (priv->cp_added_id) {
+	    g_signal_handler_disconnect (priv->con_provider, priv->cp_added_id);
+	    priv->cp_added_id = 0;
+	}
+
+	if (priv->cp_loaded_id) {
+	    g_signal_handler_disconnect (priv->con_provider, priv->cp_loaded_id);
+	    priv->cp_loaded_id = 0;
+	}
+
+	if (priv->cp_removed_id) {
+	    g_signal_handler_disconnect (priv->con_provider, priv->cp_removed_id);
+	    priv->cp_removed_id = 0;
+	}
+
+	if (priv->cp_updated_id) {
+	    g_signal_handler_disconnect (priv->con_provider, priv->cp_updated_id);
+	    priv->cp_updated_id = 0;
+	}
+
+	g_hash_table_unref (priv->available_connections);
+
 	activation_source_clear (self, TRUE, AF_INET);
 	activation_source_clear (self, TRUE, AF_INET6);
 
@@ -3916,6 +4197,9 @@ get_property (GObject *object, guint prop_id,
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceState state;
 	const char *ac_path = NULL;
+	GPtrArray *array;
+	GHashTableIter iter;
+	NMConnection *connection;
 
 	state = nm_device_get_state (self);
 
@@ -4008,6 +4292,13 @@ get_property (GObject *object, guint prop_id,
 	case PROP_RFKILL_TYPE:
 		g_value_set_uint (value, priv->rfkill_type);
 		break;
+	case PROP_AVAILABLE_CONNECTIONS:
+		array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections));
+		g_hash_table_iter_init (&iter, priv->available_connections);
+		while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL))
+			g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection)));
+		g_value_take_boxed (value, array);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -4030,14 +4321,15 @@ nm_device_class_init (NMDeviceClass *klass)
 	object_class->constructor = constructor;
 	object_class->constructed = constructed;
 
-	klass->get_type_capabilities = real_get_type_capabilities;
-	klass->get_generic_capabilities = real_get_generic_capabilities;
-	klass->act_stage1_prepare = real_act_stage1_prepare;
-	klass->act_stage2_config = real_act_stage2_config;
-	klass->act_stage3_ip4_config_start = real_act_stage3_ip4_config_start;
-	klass->act_stage3_ip6_config_start = real_act_stage3_ip6_config_start;
-	klass->act_stage4_ip4_config_timeout = real_act_stage4_ip4_config_timeout;
-	klass->act_stage4_ip6_config_timeout = real_act_stage4_ip6_config_timeout;
+	klass->get_type_capabilities = get_type_capabilities;
+	klass->get_generic_capabilities = get_generic_capabilities;
+	klass->act_stage1_prepare = act_stage1_prepare;
+	klass->act_stage2_config = act_stage2_config;
+	klass->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
+	klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	klass->check_connection_available = check_connection_available;
 
 	/* Properties */
 	g_object_class_install_property
@@ -4217,12 +4509,21 @@ nm_device_class_init (NMDeviceClass *klass)
 		                   0, G_MAXINT, 0,
 		                   G_PARAM_READABLE | NM_PROPERTY_PARAM_NO_EXPORT));
 
+	g_object_class_install_property
+		(object_class, PROP_AVAILABLE_CONNECTIONS,
+		 g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS,
+		                     "AvailableConnections",
+		                     "AvailableConnections",
+		                     DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH,
+		                     G_PARAM_READABLE));
+
 	/* Signals */
 	signals[STATE_CHANGED] =
 		g_signal_new ("state-changed",
 		              G_OBJECT_CLASS_TYPE (object_class),
-		              G_SIGNAL_RUN_FIRST,
-		              0, NULL, NULL,
+		              G_SIGNAL_RUN_LAST,
+		              G_STRUCT_OFFSET (NMDeviceClass, state_changed),
+		              NULL, NULL,
 		              _nm_marshal_VOID__UINT_UINT_UINT,
 		              G_TYPE_NONE, 3,
 		              G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
@@ -4427,7 +4728,13 @@ reason_to_string (NMDeviceStateReason reason)
 	case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
 		return "dependency-failed";
 	case NM_DEVICE_STATE_REASON_BR2684_FAILED:
-		return "br2684 bridge failed";
+		return "br2684-bridge-failed";
+	case NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE:
+		return "modem-manager-unavailable";
+	case NM_DEVICE_STATE_REASON_SSID_NOT_FOUND:
+		return "SSID not found";
+	case NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED:
+		return "secondary-connection-failed";
 	default:
 		break;
 	}
@@ -4445,6 +4752,12 @@ nm_device_state_changed (NMDevice *device,
 	gboolean no_firmware = FALSE;
 	NMConnection *connection;
 
+	/* Track re-entry */
+	static gboolean in_state_changed = FALSE;
+
+	g_warn_if_fail (in_state_changed == FALSE);
+	in_state_changed = TRUE;
+
 	g_return_if_fail (NM_IS_DEVICE (device));
 
 	/* Do nothing if state isn't changing, but as a special case allow
@@ -4452,8 +4765,10 @@ nm_device_state_changed (NMDevice *device,
 	 * can retry device initialization.
 	 */
 	if (   (priv->state == state)
-	    && !(state == NM_DEVICE_STATE_UNAVAILABLE && priv->firmware_missing))
+	    && !(state == NM_DEVICE_STATE_UNAVAILABLE && priv->firmware_missing)) {
+		in_state_changed = FALSE;
 		return;
+	}
 
 	old_state = priv->state;
 	priv->state = state;
@@ -4474,14 +4789,13 @@ nm_device_state_changed (NMDevice *device,
 	/* Cache the activation request for the dispatcher */
 	req = priv->act_request ? g_object_ref (priv->act_request) : NULL;
 
-	/* Update connection timestamps; do this before possibly deactivating the
-	 * device since that will clear the activation request and thus the
-	 * connection, which we need.
-	 */
-	if (state == NM_DEVICE_STATE_ACTIVATED || old_state == NM_DEVICE_STATE_ACTIVATED) {
-		nm_settings_connection_update_timestamp (NM_SETTINGS_CONNECTION (nm_act_request_get_connection (req)),
-		                                         (guint64) time (NULL), TRUE);
-	}
+	if (state <= NM_DEVICE_STATE_UNAVAILABLE)
+		_clear_available_connections (device, TRUE);
+
+	/* Update the available connections list when a device first becomes available */
+	if (   state >= NM_DEVICE_STATE_DISCONNECTED
+	    && old_state < NM_DEVICE_STATE_DISCONNECTED)
+		nm_device_recheck_available_connections (device);
 
 	/* Handle the new state here; but anything that could trigger
 	 * another state change should be done below.
@@ -4541,7 +4855,7 @@ nm_device_state_changed (NMDevice *device,
 	case NM_DEVICE_STATE_ACTIVATED:
 		nm_log_info (LOGD_DEVICE, "Activation (%s) successful, device activated.",
 		             nm_device_get_iface (device));
-		nm_utils_call_dispatcher ("up", nm_act_request_get_connection (req), device, NULL, NULL, NULL);
+		nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), device, NULL, NULL);
 		break;
 	case NM_DEVICE_STATE_FAILED:
 		connection = nm_act_request_get_connection (req);
@@ -4549,22 +4863,43 @@ nm_device_state_changed (NMDevice *device,
 		             "Activation (%s) failed for connection '%s'",
 		             nm_device_get_iface (device),
 		             nm_connection_get_id (connection));
+
+		/* Notify any slaves of the unexpected failure */
+		nm_device_master_release_slaves (device, TRUE);
+
+		/* If the connection doesn't yet have a timestamp, set it to zero so that
+		 * we can distinguish between connections we've tried to activate and have
+		 * failed (zero timestamp), connections that succeeded (non-zero timestamp),
+		 * and those we haven't tried yet (no timestamp).
+		 */
+		if (!nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), NULL)) {
+			nm_settings_connection_update_timestamp (NM_SETTINGS_CONNECTION (connection),
+			                                         (guint64) 0,
+			                                         TRUE);
+		}
+
 		/* Schedule the transition to DISCONNECTED.  The device can't transition
 		 * immediately because we can't change states again from the state
 		 * handler for a variety of reasons.
 		 */
 		nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
 		break;
+	case NM_DEVICE_STATE_SECONDARIES:
+		nm_log_dbg (LOGD_DEVICE, "(%s): device entered SECONDARIES state",
+		            nm_device_get_iface (device));
+		break;
 	default:
 		break;
 	}
 
 	if (old_state == NM_DEVICE_STATE_ACTIVATED)
-		nm_utils_call_dispatcher ("down", nm_act_request_get_connection (req), device, NULL, NULL, NULL);
+		nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device, NULL, NULL);
 
 	/* Dispose of the cached activation request */
 	if (req)
 		g_object_unref (req);
+
+	in_state_changed = FALSE;
 }
 
 static gboolean
@@ -4572,16 +4907,28 @@ queued_set_state (gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceState new_state;
+	NMDeviceStateReason new_reason;
 
 	if (priv->queued_state.id) {
 		nm_log_dbg (LOGD_DEVICE, "(%s): running queued state change to %s (id %d)",
 			        nm_device_get_iface (self),
 			        state_to_string (priv->queued_state.state),
 			        priv->queued_state.id);
+
+		/* Clear queued state struct before triggering state change, since
+		 * the state change may queue another state.
+		 */
 		priv->queued_state.id = 0;
-		nm_device_state_changed (self, priv->queued_state.state, priv->queued_state.reason);
+		new_state = priv->queued_state.state;
+		new_reason = priv->queued_state.reason;
+		nm_device_queued_state_clear (self);
+
+		nm_device_state_changed (self, new_state, new_reason);
+	} else {
+		g_warn_if_fail (priv->queued_state.state == NM_DEVICE_STATE_UNKNOWN);
+		g_warn_if_fail (priv->queued_state.reason == NM_DEVICE_STATE_REASON_NONE);
 	}
-	nm_device_queued_state_clear (self);
 	return FALSE;
 }
 
@@ -4707,7 +5054,8 @@ ip4_match_config (NMDevice *self, NMConnection *connection)
 	dhcp_mgr = nm_dhcp_manager_get ();
 	leases = nm_dhcp_manager_get_lease_config (dhcp_mgr,
 	                                           nm_device_get_iface (self),
-	                                           nm_connection_get_uuid (connection));
+	                                           nm_connection_get_uuid (connection),
+						   FALSE);
 	g_object_unref (dhcp_mgr);
 
 	method = s_ip4 ? nm_setting_ip4_config_get_method (s_ip4) : NM_SETTING_IP4_CONFIG_METHOD_AUTO;
@@ -4869,3 +5217,119 @@ nm_device_get_autoconnect (NMDevice *device)
 	return NM_DEVICE_GET_PRIVATE (device)->autoconnect;
 }
 
+static void
+_signal_available_connections_changed (NMDevice *device)
+{
+	g_object_notify (G_OBJECT (device), NM_DEVICE_AVAILABLE_CONNECTIONS);
+}
+
+static void
+_clear_available_connections (NMDevice *device, gboolean do_signal)
+{
+	g_hash_table_remove_all (NM_DEVICE_GET_PRIVATE (device)->available_connections);
+	if (do_signal == TRUE)
+		_signal_available_connections_changed (device);
+}
+
+static gboolean
+_try_add_available_connection (NMDevice *self, NMConnection *connection)
+{
+	if (nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED)
+		return FALSE;
+
+	if (nm_device_check_connection_compatible (self, connection, NULL)) {
+		/* Let subclasses implement additional checks on the connection */
+		if (   NM_DEVICE_GET_CLASS (self)->check_connection_available
+		    && NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection)) {
+
+			g_hash_table_insert (NM_DEVICE_GET_PRIVATE (self)->available_connections,
+					             g_object_ref (connection),
+					             GUINT_TO_POINTER (1));
+		}
+	}
+	return FALSE;
+}
+
+static gboolean
+_del_available_connection (NMDevice *device, NMConnection *connection)
+{
+	return g_hash_table_remove (NM_DEVICE_GET_PRIVATE (device)->available_connections, connection);
+}
+
+static gboolean
+check_connection_available (NMDevice *device, NMConnection *connection)
+{
+	/* Default is to assume the connection is available unless a subclass
+	 * overrides this with more specific checks.
+	 */
+	return TRUE;
+}
+
+void
+nm_device_recheck_available_connections (NMDevice *device)
+{
+	NMDevicePrivate *priv;
+	const GSList *connections, *iter;
+
+	g_return_if_fail (device != NULL);
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	priv = NM_DEVICE_GET_PRIVATE(device);
+
+	_clear_available_connections (device, FALSE);
+
+	connections = nm_connection_provider_get_connections (priv->con_provider);
+	for (iter = connections; iter; iter = g_slist_next (iter))
+		_try_add_available_connection (device, NM_CONNECTION (iter->data));
+
+	_signal_available_connections_changed (device);
+}
+
+static void
+cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	if (_try_add_available_connection (NM_DEVICE (user_data), connection))
+		_signal_available_connections_changed (NM_DEVICE (user_data));
+}
+
+static void
+cp_connections_loaded (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	const GSList *connections, *iter;
+	gboolean added = FALSE;
+
+	connections = nm_connection_provider_get_connections (cp);
+	for (iter = connections; iter; iter = g_slist_next (iter))
+		added |= _try_add_available_connection (NM_DEVICE (user_data), NM_CONNECTION (iter->data));
+
+	if (added)
+		_signal_available_connections_changed (NM_DEVICE (user_data));
+}
+
+static void
+cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	if (_del_available_connection (NM_DEVICE (user_data), connection))
+		_signal_available_connections_changed (NM_DEVICE (user_data));
+}
+
+static void
+cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	gboolean added, deleted;
+
+	/* FIXME: don't remove it from the hash if it's just going to get re-added */
+	deleted = _del_available_connection (NM_DEVICE (user_data), connection);
+	added = _try_add_available_connection (NM_DEVICE (user_data), connection);
+
+	/* Only signal if the connection was removed OR added, but not both */
+	if (added != deleted)
+		_signal_available_connections_changed (NM_DEVICE (user_data));
+}
+
+gboolean
+nm_device_supports_vlans (NMDevice *device)
+{
+	/* At the moment, NM's VLAN code assumes all VLANs are over ethernet. */
+	return NM_IS_DEVICE_ETHERNET (device);
+}