summary refs log tree commit diff
path: root/src/devices/nm-device.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-01-22 00:29:39 +0100
committerMichael Biebl <biebl@debian.org>2015-01-22 00:29:39 +0100
commit2c032d8f1c6292c1338a615e6ec40252889ba85c (patch)
tree1f77182220b2b0264288ba4a476ab47e5bc48716 /src/devices/nm-device.c
parent33491bc4279481db8ae47213e34a6d695a0e8830 (diff)
Imported Upstream version 1.0.0 upstream/1.0.0
Diffstat (limited to 'src/devices/nm-device.c')
-rw-r--r--src/devices/nm-device.c4392
1 files changed, 2608 insertions, 1784 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 4788a604..a9899535 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -19,7 +19,8 @@
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include <config.h>
+#include "config.h"
+
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <dbus/dbus.h>
@@ -35,10 +36,9 @@
 #include <sys/wait.h>
 #include <arpa/inet.h>
 #include <fcntl.h>
-#include <linux/if.h>
 #include <netlink/route/addr.h>
 
-#include "libgsystem.h"
+#include "gsystem-local-alloc.h"
 #include "nm-glib-compat.h"
 #include "nm-device.h"
 #include "nm-device-private.h"
@@ -49,13 +49,13 @@
 #include "nm-lndp-rdisc.h"
 #include "nm-dhcp-manager.h"
 #include "nm-dbus-manager.h"
-#include "nm-utils.h"
 #include "nm-logging.h"
-#include "nm-setting-ip4-config.h"
-#include "nm-setting-ip6-config.h"
-#include "nm-setting-connection.h"
+#include "nm-activation-request.h"
+#include "nm-ip4-config.h"
+#include "nm-ip6-config.h"
 #include "nm-dnsmasq-manager.h"
 #include "nm-dhcp4-config.h"
+#include "nm-dhcp6-config.h"
 #include "nm-rfkill-manager.h"
 #include "nm-firewall-manager.h"
 #include "nm-properties-changed-signal.h"
@@ -63,25 +63,23 @@
 #include "nm-settings-connection.h"
 #include "nm-connection-provider.h"
 #include "nm-posix-signals.h"
-#include "nm-manager-auth.h"
+#include "nm-auth-utils.h"
 #include "nm-dbus-glib-types.h"
 #include "nm-dispatcher.h"
-#include "nm-config-device.h"
 #include "nm-config.h"
 #include "nm-dns-manager.h"
+#include "nm-core-internal.h"
+#include "nm-default-route-manager.h"
 
-#include "nm-device-bridge.h"
-#include "nm-device-bond.h"
-#include "nm-device-team.h"
+#include "nm-device-logging.h"
+_LOG_DECLARE_SELF (NMDevice);
 
-static void impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context);
+static void impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context);
+static void impl_device_delete     (NMDevice *self, DBusGMethodInvocation *context);
 
 #include "nm-device-glue.h"
 
-static void nm_device_config_device_interface_init (NMConfigDeviceInterface *iface);
-
-G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMDevice, nm_device, G_TYPE_OBJECT,
-                                  G_IMPLEMENT_INTERFACE (NM_TYPE_CONFIG_DEVICE, nm_device_config_device_interface_init))
+G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, G_TYPE_OBJECT)
 
 #define NM_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE, NMDevicePrivate))
 
@@ -176,6 +174,7 @@ typedef struct {
 
 typedef struct {
 	gboolean in_state_changed;
+	gboolean initialized;
 
 	NMDeviceState state;
 	NMDeviceStateReason state_reason;
@@ -199,7 +198,7 @@ typedef struct {
 	RfKillType    rfkill_type;
 	gboolean      firmware_missing;
 	GHashTable *  available_connections;
-	guint8        hw_addr[NM_UTILS_HWADDR_LEN_MAX];
+	char *        hw_addr;
 	guint         hw_addr_len;
 	char *        physical_port_id;
 
@@ -230,22 +229,32 @@ typedef struct {
 	guint           carrier_wait_id;
 	gboolean        ignore_carrier;
 	guint32         mtu;
+	gboolean        up;   /* IFF_UP */
 
 	/* Generic DHCP stuff */
 	guint32         dhcp_timeout;
-	GByteArray *    dhcp_anycast_address;
+	char *          dhcp_anycast_address;
 
 	/* IP4 configuration info */
 	NMIP4Config *   ip4_config;     /* Combined config from VPN, settings, and device */
 	IpState         ip4_state;
 	NMIP4Config *   dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
 	NMIP4Config *   ext_ip4_config; /* Stuff added outside NM */
+	gboolean        ext_ip4_config_had_any_addresses;
+	NMIP4Config *   wwan_ip4_config; /* WWAN configuration */
+	struct {
+		gboolean v4_has;
+		gboolean v4_is_assumed;
+		NMPlatformIP4Route v4;
+		gboolean v6_has;
+		gboolean v6_is_assumed;
+		NMPlatformIP6Route v6;
+	} default_route;
 
 	/* DHCPv4 tracking */
-	NMDHCPClient *  dhcp4_client;
+	NMDhcpClient *  dhcp4_client;
 	gulong          dhcp4_state_sigid;
-	gulong          dhcp4_timeout_sigid;
-	NMDHCP4Config * dhcp4_config;
+	NMDhcp4Config * dhcp4_config;
 	NMIP4Config *   vpn4_config;  /* routes added by a VPN which uses this device */
 
 	guint           arp_round2_id;
@@ -255,9 +264,8 @@ typedef struct {
 	NMDnsMasqManager *dnsmasq_manager;
 	gulong            dnsmasq_state_id;
 
-	/* Firewall Manager */
-	NMFirewallManager *fw_manager;
-	DBusGProxyCall    *fw_call;
+	/* Firewall */
+	NMFirewallPendingCall fw_call;
 
 	/* avahi-autoipd stuff */
 	GPid    aipd_pid;
@@ -268,10 +276,14 @@ typedef struct {
 	NMIP6Config *  ip6_config;
 	IpState        ip6_state;
 	NMIP6Config *  vpn6_config;  /* routes added by a VPN which uses this device */
+	NMIP6Config *  wwan_ip6_config;
 	NMIP6Config *  ext_ip6_config; /* Stuff added outside NM */
+	gboolean       ext_ip6_config_had_any_addresses;
+	gboolean       nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
 
 	NMRDisc *      rdisc;
-	gulong         rdisc_config_changed_sigid;
+	gulong         rdisc_changed_id;
+	gulong         rdisc_timeout_id;
 	NMSettingIP6ConfigPrivacy rdisc_use_tempaddr;
 	/* IP6 config from autoconf */
 	NMIP6Config *  ac_ip6_config;
@@ -280,11 +292,10 @@ typedef struct {
 
 	GHashTable *   ip6_saved_properties;
 
-	NMDHCPClient *  dhcp6_client;
+	NMDhcpClient *  dhcp6_client;
 	NMRDiscDHCPLevel dhcp6_mode;
 	gulong          dhcp6_state_sigid;
-	gulong          dhcp6_timeout_sigid;
-	NMDHCP6Config * dhcp6_config;
+	NMDhcp6Config * dhcp6_config;
 	/* IP6 config from DHCP */
 	NMIP6Config *   dhcp6_ip6_config;
 
@@ -303,8 +314,9 @@ typedef struct {
 	NMConnectionProvider *con_provider;
 } NMDevicePrivate;
 
-static gboolean nm_device_set_ip4_config (NMDevice *dev,
+static gboolean nm_device_set_ip4_config (NMDevice *self,
                                           NMIP4Config *config,
+                                          guint32 default_route_metric,
                                           gboolean commit,
                                           NMDeviceStateReason *reason);
 static gboolean ip4_config_merge_and_apply (NMDevice *self,
@@ -312,36 +324,27 @@ static gboolean ip4_config_merge_and_apply (NMDevice *self,
                                             gboolean commit,
                                             NMDeviceStateReason *out_reason);
 
-static gboolean nm_device_set_ip6_config (NMDevice *dev,
+static gboolean nm_device_set_ip6_config (NMDevice *self,
                                           NMIP6Config *config,
                                           gboolean commit,
                                           NMDeviceStateReason *reason);
 
-static gboolean nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure);
-static void nm_device_slave_notify_enslave (NMDevice *dev, gboolean success);
-static void nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason);
+static gboolean nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure);
+static void nm_device_slave_notify_enslave (NMDevice *self, gboolean success);
+static void nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason);
 
-static void addrconf6_start_with_link_ready (NMDevice *self);
+static gboolean addrconf6_start_with_link_ready (NMDevice *self);
+static gboolean dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection);
+static NMActStageReturn linklocal6_start (NMDevice *self);
 
-static gboolean nm_device_get_default_unmanaged (NMDevice *device);
+static gboolean nm_device_get_default_unmanaged (NMDevice *self);
 
-static void _set_state_full (NMDevice *device,
+static void _set_state_full (NMDevice *self,
                              NMDeviceState state,
                              NMDeviceStateReason reason,
                              gboolean quitting);
 
-/***********************************************************/
-
-static GQuark
-nm_device_error_quark (void)
-{
-	static GQuark quark = 0;
-	if (!quark)
-		quark = g_quark_from_static_string ("nm-device-error");
-	return quark;
-}
-
-#define NM_DEVICE_ERROR (nm_device_error_quark ())
+static void nm_device_update_hw_address (NMDevice *self);
 
 /***********************************************************/
 
@@ -366,7 +369,7 @@ static const char *state_table[] = {
 static const char *
 queued_state_to_string (NMDeviceState state)
 {
-	if (state >= 0 && state < G_N_ELEMENTS (state_table))
+	if ((gsize) state < G_N_ELEMENTS (state_table))
 		return state_table[state];
 	return state_table[NM_DEVICE_STATE_UNKNOWN];
 }
@@ -442,41 +445,41 @@ static const char *reason_table[] = {
 static const char *
 reason_to_string (NMDeviceStateReason reason)
 {
-	if (reason >= 0 && reason < G_N_ELEMENTS (reason_table))
+	if ((gsize) reason < G_N_ELEMENTS (reason_table))
 		return reason_table[reason];
 	return reason_table[NM_DEVICE_STATE_REASON_UNKNOWN];
 }
 
 /***********************************************************/
 
-static inline gboolean
+gboolean
 nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value)
 {
 	return nm_platform_sysctl_set (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), property), value);
 }
 
 static gboolean
-device_has_capability (NMDevice *device, NMDeviceCapabilities caps)
+device_has_capability (NMDevice *self, NMDeviceCapabilities caps)
 {
-	return !!(NM_DEVICE_GET_PRIVATE (device)->capabilities & caps);
+	return !!(NM_DEVICE_GET_PRIVATE (self)->capabilities & caps);
 }
 
 /***********************************************************/
 
 void
-nm_device_dbus_export (NMDevice *device)
+nm_device_dbus_export (NMDevice *self)
 {
 	static guint32 devcount = 0;
 	NMDevicePrivate *priv;
 
-	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (NM_IS_DEVICE (self));
 
-	priv = NM_DEVICE_GET_PRIVATE (device);
+	priv = NM_DEVICE_GET_PRIVATE (self);
 	g_return_if_fail (priv->path == NULL);
 
 	priv->path = g_strdup_printf ("/org/freedesktop/NetworkManager/Devices/%d", devcount++);
-	nm_log_info (LOGD_DEVICE, "(%s): exported as %s", priv->iface, priv->path);
-	nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, device);
+	_LOGI (LOGD_DEVICE, "exported as %s", priv->path);
+	nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, self);
 }
 
 const char *
@@ -498,7 +501,7 @@ nm_device_get_udi (NMDevice *self)
 const char *
 nm_device_get_iface (NMDevice *self)
 {
-	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (NM_IS_DEVICE (self), 0);
 
 	return NM_DEVICE_GET_PRIVATE (self)->iface;
 }
@@ -512,9 +515,9 @@ nm_device_get_ifindex (NMDevice *self)
 }
 
 gboolean
-nm_device_is_software (NMDevice *device)
+nm_device_is_software (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	return priv->is_software;
 }
@@ -562,11 +565,14 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface)
 	if (priv->ip_iface) {
 		priv->ip_ifindex = nm_platform_link_get_ifindex (priv->ip_iface);
 		if (priv->ip_ifindex > 0) {
+			if (nm_platform_check_support_user_ipv6ll ())
+				nm_platform_link_set_user_ipv6ll_enabled (priv->ip_ifindex, TRUE);
+
 			if (!nm_platform_link_is_up (priv->ip_ifindex))
 				nm_platform_link_set_up (priv->ip_ifindex);
 		} else {
 			/* Device IP interface must always be a kernel network interface */
-			nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", iface);
+			_LOGW (LOGD_HW, "failed to look up interface index");
 		}
 	}
 
@@ -579,6 +585,43 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface)
 	g_free (old_ip_iface);
 }
 
+static gboolean
+get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
+{
+	NMLinkType link_type;
+	const guint8 *hwaddr = NULL;
+	size_t hwaddr_len = 0;
+	int ifindex;
+	gboolean success;
+
+	/* If we get here, we *must* have a kernel netdev, which implies an ifindex */
+	ifindex = nm_device_get_ip_ifindex (self);
+	g_assert (ifindex);
+
+	link_type = nm_platform_link_get_type (ifindex);
+	g_return_val_if_fail (link_type > NM_LINK_TYPE_UNKNOWN, 0);
+
+	hwaddr = nm_platform_link_get_address (ifindex, &hwaddr_len);
+	if (!hwaddr_len)
+		return FALSE;
+
+	success = nm_utils_get_ipv6_interface_identifier (link_type,
+	                                                  hwaddr,
+	                                                  hwaddr_len,
+	                                                  out_iid);
+	if (!success) {
+		_LOGW (LOGD_HW, "failed to generate interface identifier "
+		       "for link type %u hwaddr_len %zu", link_type, hwaddr_len);
+	}
+	return success;
+}
+
+static gboolean
+nm_device_get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *iid)
+{
+	return NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, iid);
+}
+
 const char *
 nm_device_get_driver (NMDevice *self)
 {
@@ -606,54 +649,124 @@ nm_device_get_device_type (NMDevice *self)
 
 /**
  * nm_device_get_priority():
- * @dev: the #NMDevice
+ * @self: the #NMDevice
  *
  * Returns: the device's routing priority.  Lower numbers means a "better"
  *  device, eg higher priority.
  */
 int
-nm_device_get_priority (NMDevice *dev)
+nm_device_get_priority (NMDevice *self)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (dev), 100);
+	g_return_val_if_fail (NM_IS_DEVICE (self), 1000);
 
-	/* Device 'priority' is used for two things:
+	/* Device 'priority' is used for the default route-metric and is based on
+	 * the device type. The settings ipv4.route-metric and ipv6.route-metric
+	 * can overwrite this default.
+	 *
+	 * Currently for both IPv4 and IPv6 we use the same default values.
+	 *
+	 * The route-metric is used for the metric of the routes of device.
+	 * This also applies to the default route. Therefore it affects also
+	 * which device is the "best".
 	 *
-	 * a) two devices on the same IP subnet: the "better" (ie, lower number)
-	 *     device is the default outgoing device for that subnet
-	 * b) default route: the "better" device gets the default route.  This can
-	 *     always be modified by setting a connection to never-default=TRUE, in
-	 *     which case that device will never take the default route when
-	 *     it's using that connection.
+	 * For comparison, note that iproute2 by default adds IPv4 routes with
+	 * metric 0, and IPv6 routes with metric 1024. The latter is the IPv6
+	 * "user default" in the kernel (NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6).
+	 * In kernel, the full uint32_t range is available for route
+	 * metrics (except for IPv6, where 0 means 1024).
 	 */
 
-	switch (nm_device_get_device_type (dev)) {
+	switch (nm_device_get_device_type (self)) {
+	/* 50 is reserved for VPN (NM_VPN_ROUTE_METRIC_DEFAULT) */
 	case NM_DEVICE_TYPE_ETHERNET:
-		return 1;
+		return 100;
 	case NM_DEVICE_TYPE_INFINIBAND:
-		return 2;
+		return 150;
 	case NM_DEVICE_TYPE_ADSL:
-		return 3;
+		return 200;
 	case NM_DEVICE_TYPE_WIMAX:
-		return 4;
+		return 250;
 	case NM_DEVICE_TYPE_BOND:
-		return 5;
+		return 300;
 	case NM_DEVICE_TYPE_TEAM:
-		return 6;
+		return 350;
 	case NM_DEVICE_TYPE_VLAN:
-		return 7;
+		return 400;
 	case NM_DEVICE_TYPE_MODEM:
-		return 8;
+		return 450;
 	case NM_DEVICE_TYPE_BT:
-		return 9;
+		return 550;
 	case NM_DEVICE_TYPE_WIFI:
-		return 10;
+		return 600;
 	case NM_DEVICE_TYPE_OLPC_MESH:
-		return 11;
+		return 650;
 	default:
-		return 20;
+		return 950;
 	}
 }
 
+guint32
+nm_device_get_ip4_route_metric (NMDevice *self)
+{
+	NMConnection *connection;
+	gint64 route_metric = -1;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), G_MAXUINT32);
+
+	connection = nm_device_get_connection (self);
+
+	if (connection)
+		route_metric = nm_setting_ip_config_get_route_metric (nm_connection_get_setting_ip4_config (connection));
+
+	return route_metric >= 0 ? route_metric : nm_device_get_priority (self);
+}
+
+guint32
+nm_device_get_ip6_route_metric (NMDevice *self)
+{
+	NMConnection *connection;
+	gint64 route_metric = -1;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), G_MAXUINT32);
+
+	connection = nm_device_get_connection (self);
+
+	if (connection)
+		route_metric = nm_setting_ip_config_get_route_metric (nm_connection_get_setting_ip6_config (connection));
+
+	return route_metric >= 0 ? route_metric : nm_device_get_priority (self);
+}
+
+const NMPlatformIP4Route *
+nm_device_get_ip4_default_route (NMDevice *self, gboolean *out_is_assumed)
+{
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (out_is_assumed)
+		*out_is_assumed = priv->default_route.v4_is_assumed;
+
+	return priv->default_route.v4_has ? &priv->default_route.v4 : NULL;
+}
+
+const NMPlatformIP6Route *
+nm_device_get_ip6_default_route (NMDevice *self, gboolean *out_is_assumed)
+{
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (out_is_assumed)
+		*out_is_assumed = priv->default_route.v6_is_assumed;
+
+	return priv->default_route.v6_has ? &priv->default_route.v6 : NULL;
+}
+
 const char *
 nm_device_get_type_desc (NMDevice *self)
 {
@@ -663,9 +776,9 @@ nm_device_get_type_desc (NMDevice *self)
 }
 
 gboolean
-nm_device_has_carrier (NMDevice *device)
+nm_device_has_carrier (NMDevice *self)
 {
-	return NM_DEVICE_GET_PRIVATE (device)->carrier;
+	return NM_DEVICE_GET_PRIVATE (self)->carrier;
 }
 
 NMActRequest *
@@ -693,22 +806,38 @@ nm_device_get_rfkill_type (NMDevice *self)
 }
 
 static const char *
-nm_device_get_physical_port_id (NMDevice *device)
+nm_device_get_physical_port_id (NMDevice *self)
 {
-	return NM_DEVICE_GET_PRIVATE (device)->physical_port_id;
+	return NM_DEVICE_GET_PRIVATE (self)->physical_port_id;
 }
 
 /***********************************************************/
 
 static gboolean
-nm_device_uses_generated_connection (NMDevice *self)
+nm_device_uses_generated_assumed_connection (NMDevice *self)
 {
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 
-	connection = nm_device_get_connection (self);
-	if (!connection)
-		return FALSE;
-	return nm_settings_connection_get_nm_generated (NM_SETTINGS_CONNECTION (connection));
+	if (   priv->act_request
+	    && nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (priv->act_request))) {
+		connection = nm_act_request_get_connection (priv->act_request);
+		if (   connection
+		    && nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection)))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+gboolean
+nm_device_uses_assumed_connection (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (   priv->act_request
+	    && nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (priv->act_request)))
+		return TRUE;
+	return FALSE;
 }
 
 static SlaveInfo *
@@ -737,28 +866,28 @@ free_slave_info (SlaveInfo *info)
 
 /**
  * nm_device_enslave_slave:
- * @dev: the master device
+ * @self: the master device
  * @slave: the slave device to enslave
  * @connection: (allow-none): the slave device's connection
  *
- * If @dev is capable of enslaving other devices (ie it's a bridge, bond, team,
+ * If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
  * etc) then this function enslaves @slave.
  *
  * Returns: %TRUE on success, %FALSE on failure or if this device cannot enslave
  *  other devices.
  */
 static gboolean
-nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connection)
+nm_device_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connection)
 {
 	SlaveInfo *info;
 	gboolean success = FALSE;
 	gboolean configure;
 
-	g_return_val_if_fail (dev != NULL, FALSE);
+	g_return_val_if_fail (self != NULL, FALSE);
 	g_return_val_if_fail (slave != NULL, FALSE);
-	g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE);
+	g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->enslave_slave != NULL, FALSE);
 
-	info = find_slave_info (dev, slave);
+	info = find_slave_info (self, slave);
 	if (!info)
 		return FALSE;
 
@@ -769,7 +898,7 @@ nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connectio
 		if (configure)
 			g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
 
-		success = NM_DEVICE_GET_CLASS (dev)->enslave_slave (dev, slave, connection, configure);
+		success = NM_DEVICE_GET_CLASS (self)->enslave_slave (self, slave, connection, configure);
 		info->enslaved = success;
 	}
 
@@ -778,18 +907,18 @@ nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connectio
 	/* Ensure the device's hardware address is up-to-date; it often changes
 	 * when slaves change.
 	 */
-	nm_device_update_hw_address (dev);
+	nm_device_update_hw_address (self);
 
 	/* Restart IP configuration if we're waiting for slaves.  Do this
 	 * after updating the hardware address as IP config may need the
 	 * new address.
 	 */
 	if (success) {
-		if (NM_DEVICE_GET_PRIVATE (dev)->ip4_state == IP_WAIT)
-			nm_device_activate_stage3_ip4_start (dev);
+		if (NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT)
+			nm_device_activate_stage3_ip4_start (self);
 
-		if (NM_DEVICE_GET_PRIVATE (dev)->ip6_state == IP_WAIT)
-			nm_device_activate_stage3_ip6_start (dev);
+		if (NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT)
+			nm_device_activate_stage3_ip6_start (self);
 	}
 
 	return success;
@@ -797,42 +926,42 @@ nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connectio
 
 /**
  * nm_device_release_one_slave:
- * @dev: the master device
+ * @self: the master device
  * @slave: the slave device to release
- * @configure: whether @dev needs to actually release @slave
+ * @configure: whether @self needs to actually release @slave
  * @reason: the state change reason for the @slave
  *
- * If @dev is capable of enslaving other devices (ie it's a bridge, bond, team,
+ * If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
  * etc) then this function releases the previously enslaved @slave and/or
- * updates the state of @dev and @slave to reflect its release.
+ * updates the state of @self and @slave to reflect its release.
  *
  * 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 configure, NMDeviceStateReason reason)
+nm_device_release_one_slave (NMDevice *self, NMDevice *slave, gboolean configure, NMDeviceStateReason reason)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	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);
+	g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->release_slave != NULL, FALSE);
 
-	info = find_slave_info (dev, slave);
+	info = find_slave_info (self, slave);
 	if (!info)
 		return FALSE;
 	priv->slaves = g_slist_remove (priv->slaves, info);
 
 	if (info->enslaved) {
-		success = NM_DEVICE_GET_CLASS (dev)->release_slave (dev, slave, configure);
+		success = NM_DEVICE_GET_CLASS (self)->release_slave (self, slave, configure);
 		/* The release_slave() implementation logs success/failure (in the
 		 * correct device-specific log domain), so we don't have to do anything.
 		 */
 	}
 
 	if (!configure) {
-		g_warn_if_fail (reason == NM_DEVICE_STATE_REASON_NONE);
+		g_warn_if_fail (reason == NM_DEVICE_STATE_REASON_NONE || reason == NM_DEVICE_STATE_REASON_REMOVED);
 		reason = NM_DEVICE_STATE_REASON_NONE;
 	} else if (reason == NM_DEVICE_STATE_REASON_NONE) {
 		g_warn_if_reached ();
@@ -845,20 +974,53 @@ nm_device_release_one_slave (NMDevice *dev, NMDevice *slave, gboolean configure,
 	/* Ensure the device's hardware address is up-to-date; it often changes
 	 * when slaves change.
 	 */
-	nm_device_update_hw_address (dev);
+	nm_device_update_hw_address (self);
 
 	return success;
 }
 
+static gboolean
+is_software_external (NMDevice *self)
+{
+	return   nm_device_is_software (self)
+	      && !nm_device_get_is_nm_owned (self);
+}
+
+/**
+ * nm_device_finish_init:
+ * @self: the master device
+ *
+ * Whatever needs to be done post-initialization, when the device has a DBus
+ * object name.
+ */
+void
+nm_device_finish_init (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	g_assert (priv->initialized == FALSE);
+
+	/* Do not manage externally created software devices until they are IFF_UP */
+	if (   is_software_external (self)
+	    && !nm_platform_link_is_up (priv->ifindex)
+	    && priv->ifindex > 0)
+		nm_device_set_initial_unmanaged_flag (self, NM_UNMANAGED_EXTERNAL_DOWN, TRUE);
+
+	if (priv->master)
+		nm_device_enslave_slave (priv->master, self, NULL);
+
+	priv->initialized = TRUE;
+}
+
 static void
-carrier_changed (NMDevice *device, gboolean carrier)
+carrier_changed (NMDevice *self, gboolean carrier)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (!nm_device_get_managed (device))
+	if (!nm_device_get_managed (self))
 		return;
 
-	nm_device_recheck_available_connections (device);
+	nm_device_recheck_available_connections (self);
 
 	/* ignore-carrier devices ignore all carrier-down events */
 	if (priv->ignore_carrier && !carrier)
@@ -872,13 +1034,13 @@ carrier_changed (NMDevice *device, gboolean carrier)
 		if (!carrier)
 			return;
 
-		if (nm_device_activate_ip4_state_in_wait (device))
-			nm_device_activate_stage3_ip4_start (device);
-		if (nm_device_activate_ip6_state_in_wait (device))
-			nm_device_activate_stage3_ip6_start (device);
+		if (nm_device_activate_ip4_state_in_wait (self))
+			nm_device_activate_stage3_ip4_start (self);
+		if (nm_device_activate_ip6_state_in_wait (self))
+			nm_device_activate_stage3_ip6_start (self);
 
 		return;
-	} else if (nm_device_get_enslaved (device) && !carrier) {
+	} else if (nm_device_get_enslaved (self) && !carrier) {
 		/* Slaves don't deactivate when they lose carrier; for
 		 * bonds/teams in particular that would be actively
 		 * counterproductive.
@@ -890,7 +1052,7 @@ carrier_changed (NMDevice *device, gboolean carrier)
 		g_warn_if_fail (priv->state >= NM_DEVICE_STATE_UNAVAILABLE);
 
 		if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) {
-			nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED,
+			nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED,
 			                       NM_DEVICE_STATE_REASON_CARRIER);
 		} else if (priv->state == NM_DEVICE_STATE_DISCONNECTED) {
 			/* If the device is already in DISCONNECTED state without a carrier
@@ -898,14 +1060,16 @@ carrier_changed (NMDevice *device, gboolean carrier)
 			 * when the carrier appears, auto connections are rechecked for
 			 * the device.
 			 */
-			nm_device_emit_recheck_auto_activate (device);
+			nm_device_emit_recheck_auto_activate (self);
 		}
 	} else {
+		g_return_if_fail (priv->state >= NM_DEVICE_STATE_UNAVAILABLE);
+
 		if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) {
-			if (nm_device_queued_state_peek (device) >= NM_DEVICE_STATE_DISCONNECTED)
-				nm_device_queued_state_clear (device);
-		} else if (priv->state >= NM_DEVICE_STATE_DISCONNECTED) {
-			nm_device_queue_state (device, NM_DEVICE_STATE_UNAVAILABLE,
+			if (nm_device_queued_state_peek (self) >= NM_DEVICE_STATE_DISCONNECTED)
+				nm_device_queued_state_clear (self);
+		} else {
+			nm_device_queue_state (self, NM_DEVICE_STATE_UNAVAILABLE,
 			                       NM_DEVICE_STATE_REASON_CARRIER);
 		}
 	}
@@ -916,18 +1080,16 @@ carrier_changed (NMDevice *device, gboolean carrier)
 static gboolean
 link_disconnect_action_cb (gpointer user_data)
 {
-	NMDevice *device = NM_DEVICE (user_data);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (calling deferred action) (id=%u)",
-	             nm_device_get_iface (device), priv->carrier_defer_id);
+	_LOGD (LOGD_DEVICE, "link disconnected (calling deferred action) (id=%u)", priv->carrier_defer_id);
 
 	priv->carrier_defer_id = 0;
 
-	nm_log_info (LOGD_DEVICE, "(%s): link disconnected (calling deferred action)",
-	             nm_device_get_iface (device));
+	_LOGI (LOGD_DEVICE, "link disconnected (calling deferred action)");
 
-	NM_DEVICE_GET_CLASS (device)->carrier_changed (device, FALSE);
+	NM_DEVICE_GET_CLASS (self)->carrier_changed (self, FALSE);
 
 	return FALSE;
 }
@@ -939,67 +1101,64 @@ link_disconnect_action_cancel (NMDevice *self)
 
 	if (priv->carrier_defer_id) {
 		g_source_remove (priv->carrier_defer_id);
-		nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (canceling deferred action) (id=%u)",
-		            nm_device_get_iface (self), priv->carrier_defer_id);
+		_LOGD (LOGD_DEVICE, "link disconnected (canceling deferred action) (id=%u)", priv->carrier_defer_id);
 		priv->carrier_defer_id = 0;
 	}
 }
 
 void
-nm_device_set_carrier (NMDevice *device, gboolean carrier)
+nm_device_set_carrier (NMDevice *self, gboolean carrier)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
-	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
-	NMDeviceState state = nm_device_get_state (device);
-	const char *iface = nm_device_get_iface (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
+	NMDeviceState state = nm_device_get_state (self);
 
 	if (priv->carrier == carrier)
 		return;
 
 	priv->carrier = carrier;
-	g_object_notify (G_OBJECT (device), NM_DEVICE_CARRIER);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_CARRIER);
 
 	if (priv->carrier) {
-		nm_log_info (LOGD_DEVICE, "(%s): link connected", iface);
-		link_disconnect_action_cancel (device);
-		klass->carrier_changed (device, TRUE);
+		_LOGI (LOGD_DEVICE, "link connected");
+		link_disconnect_action_cancel (self);
+		klass->carrier_changed (self, TRUE);
 
 		if (priv->carrier_wait_id) {
 			g_source_remove (priv->carrier_wait_id);
 			priv->carrier_wait_id = 0;
-			nm_device_remove_pending_action (device, "carrier wait", TRUE);
+			nm_device_remove_pending_action (self, "carrier wait", TRUE);
 		}
 	} else if (state <= NM_DEVICE_STATE_DISCONNECTED) {
-		nm_log_info (LOGD_DEVICE, "(%s): link disconnected", iface);
-		klass->carrier_changed (device, FALSE);
+		_LOGI (LOGD_DEVICE, "link disconnected");
+		klass->carrier_changed (self, FALSE);
 	} else {
-		nm_log_info (LOGD_DEVICE, "(%s): link disconnected (deferring action for %d seconds)",
-		             iface, LINK_DISCONNECT_DELAY);
+		_LOGI (LOGD_DEVICE, "link disconnected (deferring action for %d seconds)", LINK_DISCONNECT_DELAY);
 		priv->carrier_defer_id = g_timeout_add_seconds (LINK_DISCONNECT_DELAY,
-		                                                link_disconnect_action_cb, device);
-		nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (deferring action for %d seconds) (id=%u)",
-		             iface, LINK_DISCONNECT_DELAY, priv->carrier_defer_id);
+		                                                link_disconnect_action_cb, self);
+		_LOGD (LOGD_DEVICE, "link disconnected (deferring action for %d seconds) (id=%u)",
+		       LINK_DISCONNECT_DELAY, priv->carrier_defer_id);
 	}
 }
 
 static void
-update_for_ip_ifname_change (NMDevice *device)
+update_for_ip_ifname_change (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
 	if (priv->dhcp4_client) {
-		if (!nm_device_dhcp4_renew (device, FALSE)) {
-			nm_device_state_changed (device,
+		if (!nm_device_dhcp4_renew (self, FALSE)) {
+			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_FAILED,
 			                         NM_DEVICE_STATE_REASON_DHCP_FAILED);
 			return;
 		}
 	}
 	if (priv->dhcp6_client) {
-		if (!nm_device_dhcp6_renew (device, FALSE)) {
-			nm_device_state_changed (device,
+		if (!nm_device_dhcp6_renew (self, FALSE)) {
+			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_FAILED,
 			                         NM_DEVICE_STATE_REASON_DHCP_FAILED);
 			return;
@@ -1014,98 +1173,152 @@ update_for_ip_ifname_change (NMDevice *device)
 }
 
 static void
-device_link_changed (NMDevice *device, NMPlatformLink *info)
+device_set_master (NMDevice *self, int ifindex)
+{
+	NMDevice *master;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	master = nm_manager_get_device_by_ifindex (nm_manager_get (), ifindex);
+	if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave) {
+		g_clear_object (&priv->master);
+		priv->master = g_object_ref (master);
+		nm_device_master_add_slave (master, self, FALSE);
+	} else if (master) {
+		_LOGI (LOGD_DEVICE, "enslaved to non-master-type device %s; ignoring",
+		       nm_device_get_iface (master));
+	} else {
+		_LOGW (LOGD_DEVICE, "enslaved to unknown device %d %s",
+		       ifindex,
+		       nm_platform_link_get_name (ifindex));
+	}
+}
+
+static void
+device_link_changed (NMDevice *self, NMPlatformLink *info)
 {
-	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean ip_ifname_changed = FALSE;
 
 	if (info->udi && g_strcmp0 (info->udi, priv->udi)) {
 		/* Update UDI to what udev gives us */
 		g_free (priv->udi);
 		priv->udi = g_strdup (info->udi);
-		g_object_notify (G_OBJECT (device), NM_DEVICE_UDI);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_UDI);
 	}
 
 	/* Update MTU if it has changed. */
 	if (priv->mtu != info->mtu) {
 		priv->mtu = info->mtu;
-		g_object_notify (G_OBJECT (device), NM_DEVICE_MTU);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_MTU);
 	}
 
 	if (info->name[0] && strcmp (priv->iface, info->name) != 0) {
-		nm_log_info (LOGD_DEVICE, "(%s): interface index %d renamed iface from '%s' to '%s'",
-		             priv->iface, priv->ifindex, priv->iface, info->name);
+		_LOGI (LOGD_DEVICE, "interface index %d renamed iface from '%s' to '%s'",
+		       priv->ifindex, priv->iface, info->name);
 		g_free (priv->iface);
 		priv->iface = g_strdup (info->name);
 
 		/* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */
 		ip_ifname_changed = !priv->ip_iface;
 
-		g_object_notify (G_OBJECT (device), NM_DEVICE_IFACE);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_IFACE);
 		if (ip_ifname_changed)
-			g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE);
+			g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE);
 
 		/* Re-match available connections against the new interface name */
-		nm_device_recheck_available_connections (device);
+		nm_device_recheck_available_connections (self);
 
 		/* Let any connections that use the new interface name have a chance
 		 * to auto-activate on the device.
 		 */
-		nm_device_emit_recheck_auto_activate (device);
+		nm_device_emit_recheck_auto_activate (self);
 	}
 
 	/* Update slave status for external changes */
+	if (priv->enslaved && info->master != nm_device_get_ifindex (priv->master))
+		nm_device_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_NONE);
 	if (info->master && !priv->enslaved) {
-		NMDevice *master;
-
-		master = nm_manager_get_device_by_ifindex (nm_manager_get (), info->master);
-		if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave) {
-			g_clear_object (&priv->master);
-			priv->master = g_object_ref (master);
-			nm_device_master_add_slave (master, device, FALSE);
-			nm_device_enslave_slave (master, device, NULL);
-		} else if (master) {
-			nm_log_info (LOGD_DEVICE, "(%s): enslaved to non-master-type device %s; ignoring",
-			             nm_device_get_iface (device),
-			             nm_device_get_iface (master));
-		} else {
-			nm_log_warn (LOGD_DEVICE, "(%s): enslaved to unknown device %d %s",
-			             nm_device_get_iface (device),
-			             info->master,
-			             nm_platform_link_get_name (info->master));
-		}
-	} else if (priv->enslaved && !info->master)
-		nm_device_release_one_slave (priv->master, device, FALSE, NM_DEVICE_STATE_REASON_NONE);
+		device_set_master (self, info->master);
+		if (priv->master)
+			nm_device_enslave_slave (priv->master, self, NULL);
+	}
 
 	if (klass->link_changed)
-		klass->link_changed (device, info);
-
+		klass->link_changed (self, info);
 
 	/* Update DHCP, etc, if needed */
 	if (ip_ifname_changed)
-		update_for_ip_ifname_change (device);
+		update_for_ip_ifname_change (self);
+
+	if (priv->up != info->up) {
+		priv->up = info->up;
+
+		/* Manage externally-created software interfaces only when they are IFF_UP */
+		g_assert (priv->ifindex > 0);
+		if (is_software_external (self)) {
+			gboolean external_down = nm_device_get_unmanaged_flag (self, NM_UNMANAGED_EXTERNAL_DOWN);
+
+			if (external_down && info->up) {
+				if (nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED) {
+					/* Ensure the assume check is queued before any queued state changes
+					 * from the transition to UNAVAILABLE.
+					 */
+					nm_device_queue_recheck_assume (self);
+
+					/* Resetting the EXTERNAL_DOWN flag may change the device's state
+					 * to UNAVAILABLE.  To ensure that the state change doesn't touch
+					 * the device before assumption occurs, pass
+					 * NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED as the reason.
+					 */
+					nm_device_set_unmanaged (self,
+					                         NM_UNMANAGED_EXTERNAL_DOWN,
+					                         FALSE,
+					                         NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
+				} else {
+					/* Don't trigger a state change; if the device is in a
+					 * state higher than UNAVAILABLE, it is already IFF_UP
+					 * or an explicit activation request was received.
+					 */
+					priv->unmanaged_flags &= ~NM_UNMANAGED_EXTERNAL_DOWN;
+				}
+			} else if (!external_down && !info->up && nm_device_get_state (self) <= NM_DEVICE_STATE_DISCONNECTED) {
+				/* If the device is already disconnected and is set !IFF_UP,
+				 * unmanage it.
+				 */
+				nm_device_set_unmanaged (self,
+				                         NM_UNMANAGED_EXTERNAL_DOWN,
+				                         TRUE,
+				                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
+			}
+		}
+	}
 }
 
 static void
-device_ip_link_changed (NMDevice *device, NMPlatformLink *info)
+device_ip_link_changed (NMDevice *self, NMPlatformLink *info)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (info->name[0] && g_strcmp0 (priv->ip_iface, info->name)) {
-		nm_log_info (LOGD_DEVICE, "(%s): interface index %d renamed ip_iface (%d) from '%s' to '%s'",
-		             priv->iface, priv->ifindex, nm_device_get_ip_ifindex (device),
-		             priv->ip_iface, info->name);
+		_LOGI (LOGD_DEVICE, "interface index %d renamed ip_iface (%d) from '%s' to '%s'",
+		       priv->ifindex, nm_device_get_ip_ifindex (self),
+		       priv->ip_iface, info->name);
 		g_free (priv->ip_iface);
 		priv->ip_iface = g_strdup (info->name);
 
-		g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE);
-		update_for_ip_ifname_change (device);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE);
+		update_for_ip_ifname_change (self);
 	}
 }
 
 static void
-link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformSignalChangeType change_type, NMPlatformReason reason, NMDevice *device)
+link_changed_cb (NMPlatform *platform,
+                 int ifindex,
+                 NMPlatformLink *info,
+                 NMPlatformSignalChangeType change_type,
+                 NMPlatformReason reason,
+                 NMDevice *self)
 {
 	if (change_type != NM_PLATFORM_SIGNAL_CHANGED)
 		return;
@@ -1116,24 +1329,24 @@ link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlat
 	 * and it results in also setting IFF_LOWER_UP.
 	 */
 
-	if (ifindex == nm_device_get_ifindex (device))
-		device_link_changed (device, info);
-	else if (ifindex == nm_device_get_ip_ifindex (device))
-		device_ip_link_changed (device, info);
+	if (ifindex == nm_device_get_ifindex (self))
+		device_link_changed (self, info);
+	else if (ifindex == nm_device_get_ip_ifindex (self))
+		device_ip_link_changed (self, info);
 }
 
 static void
-link_changed (NMDevice *device, NMPlatformLink *info)
+link_changed (NMDevice *self, NMPlatformLink *info)
 {
 	/* Update carrier from link event if applicable. */
-	if (   device_has_capability (device, NM_DEVICE_CAP_CARRIER_DETECT)
-	    && !device_has_capability (device, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
-		nm_device_set_carrier (device, info->connected);
+	if (   device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)
+	    && !device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
+		nm_device_set_carrier (self, info->connected);
 }
 
 /**
  * nm_device_notify_component_added():
- * @device: the #NMDevice
+ * @self: the #NMDevice
  * @component: the component being added by a plugin
  *
  * Called by the manager to notify the device that a new component has
@@ -1144,16 +1357,16 @@ link_changed (NMDevice *device, NMPlatformLink *info)
  * claimed.
  */
 gboolean
-nm_device_notify_component_added (NMDevice *device, GObject *component)
+nm_device_notify_component_added (NMDevice *self, GObject *component)
 {
-	if (NM_DEVICE_GET_CLASS (device)->component_added)
-		return NM_DEVICE_GET_CLASS (device)->component_added (device, component);
+	if (NM_DEVICE_GET_CLASS (self)->component_added)
+		return NM_DEVICE_GET_CLASS (self)->component_added (self, component);
 	return FALSE;
 }
 
 /**
  * nm_device_owns_iface():
- * @device: the #NMDevice
+ * @self: the #NMDevice
  * @iface: an interface name
  *
  * Called by the manager to ask if the device or any of its components owns
@@ -1162,17 +1375,25 @@ nm_device_notify_component_added (NMDevice *device, GObject *component)
  * because that ethernet interface is controlled by the WWAN device and cannot
  * be used independently of the WWAN device.
  *
- * Returns: %TRUE if @device or it's components owns the interface name,
+ * Returns: %TRUE if @self or it's components owns the interface name,
  * %FALSE if not
  */
 gboolean
-nm_device_owns_iface (NMDevice *device, const char *iface)
+nm_device_owns_iface (NMDevice *self, const char *iface)
 {
-	if (NM_DEVICE_GET_CLASS (device)->owns_iface)
-		return NM_DEVICE_GET_CLASS (device)->owns_iface (device, iface);
+	if (NM_DEVICE_GET_CLASS (self)->owns_iface)
+		return NM_DEVICE_GET_CLASS (self)->owns_iface (self, iface);
 	return FALSE;
 }
 
+NMConnection *
+nm_device_new_default_connection (NMDevice *self)
+{
+	if (NM_DEVICE_GET_CLASS (self)->new_default_connection)
+		return NM_DEVICE_GET_CLASS (self)->new_default_connection (self);
+	return NULL;
+}
+
 static void
 slave_state_changed (NMDevice *slave,
                      NMDeviceState slave_new_state,
@@ -1183,13 +1404,12 @@ slave_state_changed (NMDevice *slave,
 	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));
+	_LOGD (LOGD_DEVICE, "slave %s state change %d (%s) -> %d (%s)",
+	       nm_device_get_iface (slave),
+	       slave_old_state,
+	       state_to_string (slave_old_state),
+	       slave_new_state,
+	       state_to_string (slave_new_state));
 
 	/* Don't try to enslave slaves until the master is ready */
 	if (priv->state < NM_DEVICE_STATE_CONFIG)
@@ -1208,46 +1428,45 @@ slave_state_changed (NMDevice *slave,
 	if (release) {
 		nm_device_release_one_slave (self, slave, TRUE, reason);
 		/* Bridge/bond/team interfaces are left up until manually deactivated */
-		if (priv->slaves == NULL && priv->state == NM_DEVICE_STATE_ACTIVATED) {
-			nm_log_dbg (LOGD_DEVICE, "(%s): last slave removed; remaining activated",
-			            nm_device_get_iface (self));
-		}
+		if (priv->slaves == NULL && priv->state == NM_DEVICE_STATE_ACTIVATED)
+			_LOGD (LOGD_DEVICE, "last slave removed; remaining activated");
 	}
 }
 
 /**
  * nm_device_master_add_slave:
- * @dev: the master device
+ * @self: the master device
  * @slave: the slave device to enslave
  * @configure: pass %TRUE if the slave should be configured by the master, or
  * %FALSE if it is already configured outside NetworkManager
  *
- * If @dev is capable of enslaving other devices (ie it's a bridge, bond, team,
+ * If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
  * etc) then this function adds @slave to the slave list for later enslavement.
  *
  * Returns: %TRUE on success, %FALSE on failure
  */
 static gboolean
-nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure)
+nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	SlaveInfo *info;
 
-	g_return_val_if_fail (dev != NULL, FALSE);
+	g_return_val_if_fail (self != NULL, FALSE);
 	g_return_val_if_fail (slave != NULL, FALSE);
-	g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE);
+	g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->enslave_slave != NULL, FALSE);
 
 	if (configure)
 		g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
 
-	if (!find_slave_info (dev, slave)) {
+	if (!find_slave_info (self, slave)) {
 		info = g_malloc0 (sizeof (SlaveInfo));
 		info->slave = g_object_ref (slave);
 		info->configure = configure;
 		info->watch_id = g_signal_connect (slave, "state-changed",
-		                                   G_CALLBACK (slave_state_changed), dev);
+		                                   G_CALLBACK (slave_state_changed), self);
 		priv->slaves = g_slist_append (priv->slaves, info);
 	}
+	nm_device_queue_recheck_assume (self);
 
 	return TRUE;
 }
@@ -1255,14 +1474,14 @@ nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure)
 
 /**
  * nm_device_master_get_slaves:
- * @dev: the master device
+ * @self: the master device
  *
- * Returns: any slaves of which @device is the master.  Caller owns returned list.
+ * Returns: any slaves of which @self is the master.  Caller owns returned list.
  */
 GSList *
-nm_device_master_get_slaves (NMDevice *dev)
+nm_device_master_get_slaves (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	GSList *slaves = NULL, *iter;
 
 	for (iter = priv->slaves; iter; iter = g_slist_next (iter))
@@ -1273,18 +1492,18 @@ nm_device_master_get_slaves (NMDevice *dev)
 
 /**
  * nm_device_master_get_slave_by_ifindex:
- * @dev: the master device
+ * @self: the master device
  * @ifindex: the slave's interface index
  *
- * Returns: the slave with the given @ifindex of which @device is the master,
- *   or %NULL if no device with @ifindex is a slave of @device.
+ * Returns: the slave with the given @ifindex of which @self is the master,
+ *   or %NULL if no device with @ifindex is a slave of @self.
  */
 NMDevice *
-nm_device_master_get_slave_by_ifindex (NMDevice *dev, int ifindex)
+nm_device_master_get_slave_by_ifindex (NMDevice *self, int ifindex)
 {
 	GSList *iter;
 
-	for (iter = NM_DEVICE_GET_PRIVATE (dev)->slaves; iter; iter = g_slist_next (iter)) {
+	for (iter = NM_DEVICE_GET_PRIVATE (self)->slaves; iter; iter = g_slist_next (iter)) {
 		SlaveInfo *info = iter->data;
 
 		if (nm_device_get_ip_ifindex (info->slave) == ifindex)
@@ -1295,18 +1514,18 @@ nm_device_master_get_slave_by_ifindex (NMDevice *dev, int ifindex)
 
 /**
  * nm_device_master_check_slave_physical_port:
- * @dev: the master device
+ * @self: the master device
  * @slave: a slave device
  * @log_domain: domain to log a warning in
  *
- * Checks if @dev already has a slave with the same #NMDevice:physical-port-id
+ * Checks if @self already has a slave with the same #NMDevice:physical-port-id
  * as @slave, and logs a warning if so.
  */
 void
-nm_device_master_check_slave_physical_port (NMDevice *dev, NMDevice *slave,
+nm_device_master_check_slave_physical_port (NMDevice *self, NMDevice *slave,
                                             guint64 log_domain)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const char *slave_physical_port_id, *existing_physical_port_id;
 	SlaveInfo *info;
 	GSList *iter;
@@ -1322,10 +1541,9 @@ nm_device_master_check_slave_physical_port (NMDevice *dev, NMDevice *slave,
 
 		existing_physical_port_id = nm_device_get_physical_port_id (info->slave);
 		if (!g_strcmp0 (slave_physical_port_id, existing_physical_port_id)) {
-			nm_log_warn (log_domain, "(%s): slave %s shares a physical port with existing slave %s",
-			             nm_device_get_ip_iface (dev),
-			             nm_device_get_ip_iface (slave),
-			             nm_device_get_ip_iface (info->slave));
+			_LOGW (log_domain, "slave %s shares a physical port with existing slave %s",
+			       nm_device_get_ip_iface (slave),
+			       nm_device_get_ip_iface (info->slave));
 			/* Since this function will get called for every slave, we only have
 			 * to warn about the first match we find; if there are other matches
 			 * later in the list, we will have already warned about them matching
@@ -1344,7 +1562,7 @@ nm_device_master_release_slaves (NMDevice *self)
 	NMDeviceStateReason reason;
 
 	/* Don't release the slaves if this connection doesn't belong to NM. */
-	if (nm_device_uses_generated_connection (self))
+	if (nm_device_uses_generated_assumed_connection (self))
 		return;
 
 	reason = priv->state_reason;
@@ -1360,19 +1578,19 @@ nm_device_master_release_slaves (NMDevice *self)
 
 /**
  * nm_device_get_master:
- * @dev: the device
+ * @self: the device
  *
- * If @dev has been enslaved by another device, this returns that
+ * If @self has been enslaved by another device, this returns that
  * device. Otherwise it returns %NULL. (In particular, note that if
- * @dev is in the process of activating as a slave, but has not yet
+ * @self is in the process of activating as a slave, but has not yet
  * been enslaved by its master, this will return %NULL.)
  *
- * Returns: (transfer none): @dev's master, or %NULL
+ * Returns: (transfer none): @self's master, or %NULL
  */
 NMDevice *
-nm_device_get_master (NMDevice *dev)
+nm_device_get_master (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->enslaved)
 		return priv->master;
@@ -1382,17 +1600,17 @@ nm_device_get_master (NMDevice *dev)
 
 /**
  * nm_device_slave_notify_enslave:
- * @dev: the slave device
+ * @self: the slave device
  * @success: whether the enslaving operation succeeded
  *
  * Notifies a slave that either it has been enslaved, or else its master tried
  * to enslave it and failed.
  */
 static void
-nm_device_slave_notify_enslave (NMDevice *dev, gboolean success)
+nm_device_slave_notify_enslave (NMDevice *self, gboolean success)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
-	NMConnection *connection = nm_device_get_connection (dev);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMConnection *connection = nm_device_get_connection (self);
 	gboolean activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG);
 
 	g_assert (priv->master);
@@ -1400,49 +1618,41 @@ nm_device_slave_notify_enslave (NMDevice *dev, gboolean success)
 	if (!priv->enslaved) {
 		if (success) {
 			if (activating) {
-				nm_log_info (LOGD_DEVICE,
-				             "Activation (%s) connection '%s' enslaved, continuing activation",
-				             nm_device_get_iface (dev),
-				             nm_connection_get_id (connection));
-			} else {
-				nm_log_info (LOGD_DEVICE,
-				             "(%s): enslaved to %s",
-				             nm_device_get_iface (dev),
-				             nm_device_get_iface (priv->master));
-			}
+				_LOGI (LOGD_DEVICE, "Activation: connection '%s' enslaved, continuing activation",
+				       nm_connection_get_id (connection));
+			} else
+				_LOGI (LOGD_DEVICE, "enslaved to %s", nm_device_get_iface (priv->master));
 
 			priv->enslaved = TRUE;
-			g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER);
+			g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER);
 		} else if (activating) {
-			nm_log_warn (LOGD_DEVICE,
-			             "Activation (%s) connection '%s' could not be enslaved",
-			             nm_device_get_iface (dev),
-			             nm_connection_get_id (connection));
+			_LOGW (LOGD_DEVICE, "Activation: connection '%s' could not be enslaved",
+			       nm_connection_get_id (connection));
 		}
 	}
 
 	if (activating) {
 		priv->ip4_state = IP_DONE;
 		priv->ip6_state = IP_DONE;
-		nm_device_queue_state (dev,
+		nm_device_queue_state (self,
 		                       success ? NM_DEVICE_STATE_SECONDARIES : NM_DEVICE_STATE_FAILED,
 		                       NM_DEVICE_STATE_REASON_NONE);
 	} else
-		nm_device_queue_recheck_assume (dev);
+		nm_device_queue_recheck_assume (self);
 }
 
 /**
  * nm_device_slave_notify_release:
- * @dev: the slave device
+ * @self: the slave device
  * @reason: the reason associated with the state change
  *
  * Notifies a slave that it has been released, and why.
  */
 static void
-nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason)
+nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
-	NMConnection *connection = nm_device_get_connection (dev);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMConnection *connection = nm_device_get_connection (self);
 	NMDeviceState new_state;
 	const char *master_status;
 
@@ -1460,43 +1670,56 @@ nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason)
 			master_status = "deactivated";
 		}
 
-		nm_log_dbg (LOGD_DEVICE,
-		            "Activation (%s) connection '%s' master %s",
-		            nm_device_get_iface (dev),
-		            nm_connection_get_id (connection),
-		            master_status);
+		_LOGD (LOGD_DEVICE, "Activation: connection '%s' master %s",
+		       nm_connection_get_id (connection),
+		       master_status);
 
-		nm_device_queue_state (dev, new_state, reason);
-	} else {
-		nm_log_info (LOGD_DEVICE,
-		             "(%s): released from master %s",
-		             nm_device_get_iface (dev),
-		             nm_device_get_iface (priv->master));
-	}
+		nm_device_queue_state (self, new_state, reason);
+	} else if (priv->master)
+		_LOGI (LOGD_DEVICE, "released from master %s", nm_device_get_iface (priv->master));
+	else
+		_LOGD (LOGD_DEVICE, "released from master%s", priv->enslaved ? "" : " (was not enslaved)");
 
 	if (priv->enslaved) {
 		priv->enslaved = FALSE;
-		g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER);
 	}
 }
 
 /**
  * nm_device_get_enslaved:
- * @device: the #NMDevice
+ * @self: the #NMDevice
  *
  * Returns: %TRUE if the device is enslaved to a master device (eg bridge or
  * bond or team), %FALSE if not
  */
 gboolean
-nm_device_get_enslaved (NMDevice *device)
+nm_device_get_enslaved (NMDevice *self)
 {
-	return NM_DEVICE_GET_PRIVATE (device)->enslaved;
+	return NM_DEVICE_GET_PRIVATE (self)->enslaved;
 }
 
+/**
+ * nm_device_removed:
+ * @self: the #NMDevice
+ *
+ * Called by the manager when the device was removed. Releases the device from
+ * the master in case it's enslaved.
+ */
+void
+nm_device_removed (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->enslaved)
+		nm_device_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_REMOVED);
+}
+
+
 static gboolean
-is_available (NMDevice *device)
+is_available (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	return priv->carrier || priv->ignore_carrier;
 }
@@ -1548,12 +1771,44 @@ nm_device_set_enabled (NMDevice *self, gboolean enabled)
 		NM_DEVICE_GET_CLASS (self)->set_enabled (self, enabled);
 }
 
+/**
+ * nm_device_get_autoconnect:
+ * @self: the #NMDevice
+ *
+ * Returns: %TRUE if the device allows autoconnect connections, or %FALSE if the
+ * device is explicitly blocking all autoconnect connections.  Does not take
+ * into account transient conditions like companion devices that may wish to
+ * block the device.
+ */
 gboolean
-nm_device_get_autoconnect (NMDevice *device)
+nm_device_get_autoconnect (NMDevice *self)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	return NM_DEVICE_GET_PRIVATE (device)->autoconnect;
+	return NM_DEVICE_GET_PRIVATE (self)->autoconnect;
+}
+
+static void
+nm_device_set_autoconnect (NMDevice *self, gboolean autoconnect)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	if (priv->autoconnect == autoconnect)
+		return;
+
+	if (autoconnect) {
+		/* Default-unmanaged devices never autoconnect */
+		if (!nm_device_get_default_unmanaged (self)) {
+			priv->autoconnect = TRUE;
+			g_object_notify (G_OBJECT (self), NM_DEVICE_AUTOCONNECT);
+		}
+	} else {
+		priv->autoconnect = FALSE;
+		g_object_notify (G_OBJECT (self), NM_DEVICE_AUTOCONNECT);
+	}
 }
 
 static gboolean
@@ -1566,6 +1821,14 @@ autoconnect_allowed_accumulator (GSignalInvocationHint *ihint,
 	return TRUE;
 }
 
+/**
+ * nm_device_autoconnect_allowed:
+ * @self: the #NMDevice
+ *
+ * Returns: %TRUE if the device can be auto-connected immediately, taking
+ * transient conditions into account (like companion devices that may wish to
+ * block autoconnect for a time).
+ */
 gboolean
 nm_device_autoconnect_allowed (NMDevice *self)
 {
@@ -1573,6 +1836,16 @@ nm_device_autoconnect_allowed (NMDevice *self)
 	GValue instance = G_VALUE_INIT;
 	GValue retval = G_VALUE_INIT;
 
+	if (priv->state < NM_DEVICE_STATE_DISCONNECTED || !priv->autoconnect)
+		return FALSE;
+
+	/* The 'autoconnect-allowed' signal is emitted on a device to allow
+	 * other listeners to block autoconnect on the device if they wish.
+	 * This is mainly used by the OLPC Mesh devices to block autoconnect
+	 * on their companion WiFi device as they share radio resources and
+	 * cannot be connected at the same time.
+	 */
+
 	g_value_init (&instance, G_TYPE_OBJECT);
 	g_value_set_object (&instance, self);
 
@@ -1590,7 +1863,7 @@ nm_device_autoconnect_allowed (NMDevice *self)
 }
 
 static gboolean
-can_auto_connect (NMDevice *device,
+can_auto_connect (NMDevice *self,
                   NMConnection *connection,
                   char **specific_object)
 {
@@ -1600,13 +1873,44 @@ can_auto_connect (NMDevice *device,
 	if (!nm_setting_connection_get_autoconnect (s_con))
 		return FALSE;
 
-	return nm_device_connection_is_available (device, connection, FALSE);
+	return nm_device_connection_is_available (self, connection, FALSE);
+}
+
+/**
+ * nm_device_can_auto_connect:
+ * @self: an #NMDevice
+ * @connection: a #NMConnection
+ * @specific_object: (out) (transfer full): on output, the path of an
+ *   object associated with the returned connection, to be passed to
+ *   nm_manager_activate_connection(), or %NULL.
+ *
+ * Checks if @connection can be auto-activated on @self right now.
+ * This requires, at a minimum, that the connection be compatible with
+ * @self, and that it have the #NMSettingConnection:autoconnect property
+ * set, and that the device allow auto connections. Some devices impose
+ * additional requirements. (Eg, a Wi-Fi connection can only be activated
+ * if its SSID was seen in the last scan.)
+ *
+ * Returns: %TRUE, if the @connection can be auto-activated.
+ **/
+gboolean
+nm_device_can_auto_connect (NMDevice *self,
+                            NMConnection *connection,
+                            char **specific_object)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+	g_return_val_if_fail (specific_object && !*specific_object, FALSE);
+
+	if (nm_device_autoconnect_allowed (self))
+		return NM_DEVICE_GET_CLASS (self)->can_auto_connect (self, connection, specific_object);
+	return FALSE;
 }
 
 static gboolean
-device_has_config (NMDevice *device)
+device_has_config (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* Check for IP configuration. */
 	if (priv->ip4_config && nm_ip4_config_get_num_addresses (priv->ip4_config))
@@ -1615,7 +1919,7 @@ device_has_config (NMDevice *device)
 		return TRUE;
 
 	/* The existence of a software device is good enough. */
-	if (nm_device_is_software (device))
+	if (nm_device_is_software (self))
 		return TRUE;
 
 	/* Slaves are also configured by definition */
@@ -1625,20 +1929,64 @@ device_has_config (NMDevice *device)
 	return FALSE;
 }
 
+/**
+ * nm_device_master_update_slave_connection:
+ * @self: the master #NMDevice
+ * @slave: the slave #NMDevice
+ * @connection: the #NMConnection to update with the slave settings
+ * @GError: (out): error description
+ *
+ * Reads the slave configuration for @slave and updates @connection with those
+ * properties. This invokes a virtual function on the master device @self.
+ *
+ * Returns: %TRUE if the configuration was read and @connection updated,
+ * %FALSE on failure.
+ */
+gboolean
+nm_device_master_update_slave_connection (NMDevice *self,
+                                          NMDevice *slave,
+                                          NMConnection *connection,
+                                          GError **error)
+{
+	NMDeviceClass *klass;
+	gboolean success;
+
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (slave, FALSE);
+	g_return_val_if_fail (connection, FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	g_return_val_if_fail (nm_connection_get_setting_connection (connection), FALSE);
+
+	g_return_val_if_fail (nm_device_get_iface (self), FALSE);
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (klass->master_update_slave_connection) {
+		success = klass->master_update_slave_connection (self, slave, connection, error);
+
+		g_return_val_if_fail (!error || (success && !*error) || *error, success);
+		return success;
+	}
+
+	g_set_error (error,
+	             NM_DEVICE_ERROR,
+	             NM_DEVICE_ERROR_FAILED,
+	             "master device '%s' cannot update a slave connection for slave device '%s' (master type not supported?)",
+	             nm_device_get_iface (self), nm_device_get_iface (slave));
+	return FALSE;
+}
+
 NMConnection *
-nm_device_generate_connection (NMDevice *device)
+nm_device_generate_connection (NMDevice *self, NMDevice *master)
 {
-	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
-	const char *ifname = nm_device_get_iface (device);
-	int ifindex = nm_device_get_ifindex (device);
+	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const char *ifname = nm_device_get_iface (self);
 	NMConnection *connection;
 	NMSetting *s_con;
 	NMSetting *s_ip4;
 	NMSetting *s_ip6;
 	gs_free char *uuid = NULL;
-	gs_free char *name = NULL;
-	int master_ifindex = 0;
 	const char *ip4_method, *ip6_method;
 	GError *error = NULL;
 
@@ -1647,32 +1995,18 @@ nm_device_generate_connection (NMDevice *device)
 		return NULL;
 
 	/* Return NULL if device is unconfigured. */
-	if (!device_has_config (device)) {
-		nm_log_dbg (LOGD_DEVICE, "(%s): device has no existing configuration", ifname);
+	if (!device_has_config (self)) {
+		_LOGD (LOGD_DEVICE, "device has no existing configuration");
 		return NULL;
 	}
 
-	if (ifindex)
-		master_ifindex = nm_platform_link_get_master (ifindex);
-	if (master_ifindex) {
-		NMDevice *master;
-
-		master = nm_manager_get_device_by_ifindex (nm_manager_get (), master_ifindex);
-		if (!master || !nm_device_get_act_request (master)) {
-			nm_log_dbg (LOGD_DEVICE, "(%s): cannot generate connection for slave before its master (%s)",
-			            ifname, nm_platform_link_get_name (master_ifindex));
-			return NULL;
-		}
-	}
-
-	connection = nm_connection_new ();
+	connection = nm_simple_connection_new ();
 	s_con = nm_setting_connection_new ();
 	uuid = nm_utils_uuid_generate ();
-	name = g_strdup_printf ("%s", ifname);
 
 	g_object_set (s_con,
 	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_ID, name,
+	              NM_SETTING_CONNECTION_ID, ifname,
 	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
 	              NM_SETTING_CONNECTION_INTERFACE_NAME, ifname,
 	              NM_SETTING_CONNECTION_TIMESTAMP, (guint64) time (NULL),
@@ -1682,36 +2016,18 @@ nm_device_generate_connection (NMDevice *device)
 	nm_connection_add_setting (connection, s_con);
 
 	/* If the device is a slave, update various slave settings */
-	if (master_ifindex) {
-		const char *master_iface = nm_platform_link_get_name (master_ifindex);
-		const char *slave_type = NULL;
-		gboolean success = FALSE;
-
-		switch (nm_platform_link_get_type (master_ifindex)) {
-		case NM_LINK_TYPE_BRIDGE:
-			slave_type = NM_SETTING_BRIDGE_SETTING_NAME;
-			success = nm_bridge_update_slave_connection (device, connection);
-			break;
-		case NM_LINK_TYPE_BOND:
-			slave_type = NM_SETTING_BOND_SETTING_NAME;
-			success = TRUE;
-			break;
-		case NM_LINK_TYPE_TEAM:
-			slave_type = NM_SETTING_TEAM_SETTING_NAME;
-			success = nm_team_update_slave_connection (device, connection);
-			break;
-		default:
-			g_warn_if_reached ();
-			break;
+	if (master) {
+		if (!nm_device_master_update_slave_connection (master,
+		                                               self,
+		                                               connection,
+		                                               &error))
+		{
+			_LOGE (LOGD_DEVICE, "master device '%s' failed to update slave connection: %s",
+			       nm_device_get_iface (master), error ? error->message : "(unknown error)");
+			g_error_free (error);
+			g_object_unref (connection);
+			return NULL;
 		}
-
-		if (!success)
-			nm_log_err (LOGD_DEVICE, "(%s): failed to read slave configuration", ifname);
-
-		g_object_set (s_con,
-		              NM_SETTING_CONNECTION_MASTER, master_iface,
-		              NM_SETTING_CONNECTION_SLAVE_TYPE, slave_type,
-		              NULL);
 	} else {
 		/* Only regular and master devices get IP configuration; slaves do not */
 		s_ip4 = nm_ip4_config_create_setting (priv->ip4_config);
@@ -1721,12 +2037,11 @@ nm_device_generate_connection (NMDevice *device)
 		nm_connection_add_setting (connection, s_ip6);
 	}
 
-	klass->update_connection (device, connection);
+	klass->update_connection (self, connection);
 
 	/* Check the connection in case of update_connection() bug. */
 	if (!nm_connection_verify (connection, &error)) {
-		nm_log_err (LOGD_DEVICE, "(%s): Generated connection does not verify: %s",
-		            nm_device_get_iface (device), error->message);
+		_LOGE (LOGD_DEVICE, "Generated connection does not verify: %s", error->message);
 		g_clear_error (&error);
 		g_object_unref (connection);
 		return NULL;
@@ -1739,8 +2054,9 @@ nm_device_generate_connection (NMDevice *device)
 	ip6_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 	if (   g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
 	    && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0
-	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))) {
-		nm_log_dbg (LOGD_DEVICE, "(%s): ignoring generated connection (no IP and not slave)", ifname);
+	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
+	    && !priv->slaves) {
+		_LOGD (LOGD_DEVICE, "ignoring generated connection (no IP and not in master-slave relationship)");
 		g_object_unref (connection);
 		connection = NULL;
 	}
@@ -1748,46 +2064,6 @@ nm_device_generate_connection (NMDevice *device)
 	return connection;
 }
 
-/**
- * nm_device_get_best_auto_connection:
- * @dev: an #NMDevice
- * @connections: (element-type #NMConnection): a list of connections
- * @specific_object: (out) (transfer full): on output, the path of an
- *   object associated with the returned connection, to be passed to
- *   nm_manager_activate_connection(), or %NULL.
- *
- * Looks through @connections to see if there is a connection that can
- * be auto-activated on @dev right now. This requires, at a minimum,
- * that the connection be compatible with @dev, and that it have the
- * #NMSettingConnection:autoconnect property set. Some devices impose
- * additional requirements. (Eg, a Wi-Fi connection can only be
- * activated if its SSID was seen in the last scan.)
- *
- * Returns: an auto-activatable #NMConnection, or %NULL if none are
- * available.
- */
-
-NMConnection *
-nm_device_get_best_auto_connection (NMDevice *dev,
-                                    GSList *connections,
-                                    char **specific_object)
-{
-	GSList *iter;
-
-	g_return_val_if_fail (NM_IS_DEVICE (dev), NULL);
-	g_return_val_if_fail (specific_object != NULL, NULL);
-	g_return_val_if_fail (*specific_object == NULL, NULL);
-
-	for (iter = connections; iter; iter = iter->next) {
-		NMConnection *connection = NM_CONNECTION (iter->data);
-
-		if (NM_DEVICE_GET_CLASS (dev)->can_auto_connect (dev, connection, specific_object))
-			return connection;
-	}
-
-	return NULL;
-}
-
 gboolean
 nm_device_complete_connection (NMDevice *self,
                                NMConnection *connection,
@@ -1801,7 +2077,7 @@ nm_device_complete_connection (NMDevice *self,
 	g_return_val_if_fail (connection != NULL, FALSE);
 
 	if (!NM_DEVICE_GET_CLASS (self)->complete_connection) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CONNECTION_INVALID,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
 		             "Device class %s had no complete_connection method",
 		             G_OBJECT_TYPE_NAME (self));
 		return FALSE;
@@ -1819,7 +2095,7 @@ nm_device_complete_connection (NMDevice *self,
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *self, NMConnection *connection)
 {
 	NMSettingConnection *s_con;
 	const char *config_iface, *device_iface;
@@ -1828,7 +2104,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 	g_assert (s_con);
 
 	config_iface = nm_setting_connection_get_interface_name (s_con);
-	device_iface = nm_device_get_iface (device);
+	device_iface = nm_device_get_iface (self);
 	if (config_iface && strcmp (config_iface, device_iface) != 0)
 		return FALSE;
 
@@ -1837,43 +2113,31 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 
 /**
  * nm_device_check_connection_compatible:
- * @device: an #NMDevice
+ * @self: an #NMDevice
  * @connection: an #NMConnection
  *
- * Checks if @connection could potentially be activated on @device.
- * This means only that @device has the proper capabilities, and that
+ * Checks if @connection could potentially be activated on @self.
+ * This means only that @self has the proper capabilities, and that
  * @connection is not locked to some other device. It does not
- * necessarily mean that @connection could be activated on @device
+ * necessarily mean that @connection could be activated on @self
  * right now. (Eg, it might refer to a Wi-Fi network that is not
  * currently available.)
  *
  * Returns: #TRUE if @connection could potentially be activated on
- *   @device.
+ *   @self.
  */
 gboolean
-nm_device_check_connection_compatible (NMDevice *device, NMConnection *connection)
+nm_device_check_connection_compatible (NMDevice *self, NMConnection *connection)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
 
-	return NM_DEVICE_GET_CLASS (device)->check_connection_compatible (device, connection);
-}
-
-static gboolean
-string_in_list (const char *str, const char **array, gsize array_len)
-{
-	gsize i;
-
-	for (i = 0; i < array_len; i++) {
-		if (strcmp (str, array[i]) == 0)
-			return TRUE;
-	}
-	return FALSE;
+	return NM_DEVICE_GET_CLASS (self)->check_connection_compatible (self, connection);
 }
 
 /**
  * nm_device_can_assume_connections:
- * @device: #NMDevice instance
+ * @self: #NMDevice instance
  *
  * This is a convenience function to determine whether connection assumption
  * is available for this device.
@@ -1881,14 +2145,14 @@ string_in_list (const char *str, const char **array, gsize array_len)
  * Returns: %TRUE if the device is capable of assuming connections, %FALSE if not
  */
 static gboolean
-nm_device_can_assume_connections (NMDevice *device)
+nm_device_can_assume_connections (NMDevice *self)
 {
-	return !!NM_DEVICE_GET_CLASS (device)->update_connection;
+	return !!NM_DEVICE_GET_CLASS (self)->update_connection;
 }
 
 /**
  * nm_device_can_assume_active_connection:
- * @device: #NMDevice instance
+ * @self: #NMDevice instance
  *
  * This is a convenience function to determine whether the device's active
  * connection can be assumed if NetworkManager restarts.  This method returns
@@ -1900,9 +2164,9 @@ nm_device_can_assume_connections (NMDevice *device)
  * assumed.
  */
 gboolean
-nm_device_can_assume_active_connection (NMDevice *device)
+nm_device_can_assume_active_connection (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	const char *method;
 	const char *assumable_ip6_methods[] = {
@@ -1911,17 +2175,19 @@ nm_device_can_assume_active_connection (NMDevice *device)
 		NM_SETTING_IP6_CONFIG_METHOD_DHCP,
 		NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
 		NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+		NULL
 	};
 	const char *assumable_ip4_methods[] = {
 		NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
 		NM_SETTING_IP6_CONFIG_METHOD_AUTO,
 		NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+		NULL
 	};
 
-	if (!nm_device_can_assume_connections (device))
+	if (!nm_device_can_assume_connections (self))
 		return FALSE;
 
-	connection = nm_device_get_connection (device);
+	connection = nm_device_get_connection (self);
 	if (!connection)
 		return FALSE;
 
@@ -1934,11 +2200,11 @@ nm_device_can_assume_active_connection (NMDevice *device)
 		return FALSE;
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	if (!string_in_list (method, assumable_ip6_methods, G_N_ELEMENTS (assumable_ip6_methods)))
+	if (!_nm_utils_string_in_list (method, assumable_ip6_methods))
 		return FALSE;
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (!string_in_list (method, assumable_ip4_methods, G_N_ELEMENTS (assumable_ip4_methods)))
+	if (!_nm_utils_string_in_list (method, assumable_ip4_methods))
 		return FALSE;
 
 	return TRUE;
@@ -1950,8 +2216,10 @@ nm_device_emit_recheck_assume (gpointer self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	priv->recheck_assume_id = 0;
-	if (!nm_device_get_act_request (self) && (priv->ip4_config || priv->ip6_config))
+	if (!nm_device_get_act_request (self)) {
+		_LOGD (LOGD_DEVICE, "emit RECHECK_ASSUME signal");
 		g_signal_emit (self, signals[RECHECK_ASSUME], 0);
+	}
 	return G_SOURCE_REMOVE;
 }
 
@@ -2022,9 +2290,8 @@ activation_source_schedule (NMDevice *self, GSourceFunc func, int family)
 		act_source_func = &priv->act_source_func;
 	}
 
-	if (*act_source_id) {
-		nm_log_err (LOGD_DEVICE, "activation stage already scheduled");
-	}
+	if (*act_source_id)
+		_LOGE (LOGD_DEVICE, "activation stage already scheduled");
 
 	/* Don't bother rescheduling the same function that's about to
 	 * run anyway.  Fixes issues with crappy wireless drivers sending
@@ -2038,12 +2305,11 @@ activation_source_schedule (NMDevice *self, GSourceFunc func, int family)
 	}
 }
 
-gboolean
-nm_device_ip_config_should_fail (NMDevice *self, gboolean ip6)
+static gboolean
+get_ip_config_may_fail (NMDevice *self, int family)
 {
 	NMConnection *connection;
-	NMSettingIP4Config *s_ip4;
-	NMSettingIP6Config *s_ip6;
+	NMSettingIPConfig *s_ip = NULL;
 
 	g_return_val_if_fail (self != NULL, TRUE);
 
@@ -2051,17 +2317,18 @@ nm_device_ip_config_should_fail (NMDevice *self, gboolean ip6)
 	g_assert (connection);
 
 	/* Fail the connection if the failed IP method is required to complete */
-	if (ip6) {
-		s_ip6 = nm_connection_get_setting_ip6_config (connection);
-		if (!nm_setting_ip6_config_get_may_fail (s_ip6))
-			return TRUE;
-	} else {
-		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		if (!nm_setting_ip4_config_get_may_fail (s_ip4))
-			return TRUE;
+	switch (family) {
+	case AF_INET:
+		s_ip = nm_connection_get_setting_ip4_config (connection);
+		break;
+	case AF_INET6:
+		s_ip = nm_connection_get_setting_ip6_config (connection);
+		break;
+	default:
+		g_assert_not_reached ();
 	}
 
-	return FALSE;
+	return nm_setting_ip_config_get_may_fail (s_ip);
 }
 
 static void
@@ -2083,9 +2350,8 @@ master_ready_cb (NMActiveConnection *active,
 	                            self,
 	                            nm_active_connection_get_assumed (active) ? FALSE : TRUE);
 
-	nm_log_dbg (LOGD_DEVICE, "(%s): master connection ready; master device %s",
-	            nm_device_get_iface (self),
-	            nm_device_get_iface (priv->master));
+	_LOGD (LOGD_DEVICE, "master connection ready; master device %s",
+	       nm_device_get_iface (priv->master));
 
 	if (priv->master_ready_id) {
 		g_signal_handler_disconnect (active, priv->master_ready_id);
@@ -2112,7 +2378,6 @@ nm_device_activate_stage1_device_prepare (gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *iface;
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 	NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request);
@@ -2125,8 +2390,7 @@ nm_device_activate_stage1_device_prepare (gpointer user_data)
 	/* Notify the new ActiveConnection along with the state change */
 	g_object_notify (G_OBJECT (self), NM_DEVICE_ACTIVE_CONNECTION);
 
-	iface = nm_device_get_iface (self);
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) started...", iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 1 of 5 (Device Prepare) started...");
 	nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
 
 	/* Assumed connections were already set up outside NetworkManager */
@@ -2146,8 +2410,7 @@ nm_device_activate_stage1_device_prepare (gpointer user_data)
 		if (nm_active_connection_get_master_ready (active))
 			master_ready_cb (active, NULL, self);
 		else {
-			nm_log_dbg (LOGD_DEVICE, "(%s): waiting for master connection to become ready",
-			            nm_device_get_iface (self));
+			_LOGD (LOGD_DEVICE, "waiting for master connection to become ready");
 
 			/* Attach a signal handler and wait for the master connection to begin activating */
 			g_assert (priv->master_ready_id == 0);
@@ -2161,7 +2424,7 @@ nm_device_activate_stage1_device_prepare (gpointer user_data)
 		nm_device_activate_schedule_stage2_device_config (self);
 
 out:
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) complete.", iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 1 of 5 (Device Prepare) complete.");
 	return FALSE;
 }
 
@@ -2184,12 +2447,11 @@ nm_device_activate_schedule_stage1_device_prepare (NMDevice *self)
 
 	activation_source_schedule (self, nm_device_activate_stage1_device_prepare, 0);
 
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) scheduled...",
-	             nm_device_get_iface (self));
+	_LOGI (LOGD_DEVICE, "Activation: Stage 1 of 5 (Device Prepare) scheduled...");
 }
 
 static NMActStageReturn
-act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason)
+act_stage2_config (NMDevice *self, NMDeviceStateReason *reason)
 {
 	/* Nothing to do */
 	return NM_ACT_STAGE_RETURN_SUCCESS;
@@ -2207,7 +2469,6 @@ nm_device_activate_stage2_device_config (gpointer user_data)
 {
 	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;
 	gboolean no_firmware = FALSE;
@@ -2217,8 +2478,7 @@ nm_device_activate_stage2_device_config (gpointer user_data)
 	/* 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 2 of 5 (Device Configure) starting...", iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) starting...");
 	nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
 	/* Assumed connections were already set up outside NetworkManager */
@@ -2248,17 +2508,17 @@ nm_device_activate_stage2_device_config (gpointer user_data)
 
 		if (slave_state == NM_DEVICE_STATE_IP_CONFIG)
 			nm_device_enslave_slave (self, info->slave, nm_device_get_connection (info->slave));
-		else if (   nm_device_uses_generated_connection (self)
+		else if (   nm_device_uses_generated_assumed_connection (self)
 		         && slave_state <= NM_DEVICE_STATE_DISCONNECTED)
 			nm_device_queue_recheck_assume (info->slave);
 	}
 
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) successful.", iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) successful.");
 
 	nm_device_activate_schedule_stage3_ip_config_start (self);
 
 out:
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) complete.", iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) complete.");
 	return FALSE;
 }
 
@@ -2281,8 +2541,7 @@ nm_device_activate_schedule_stage2_device_config (NMDevice *self)
 
 	activation_source_schedule (self, nm_device_activate_stage2_device_config, 0);
 
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) scheduled...",
-	         nm_device_get_iface (self));
+	_LOGI (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) scheduled...");
 }
 
 /*********************************************/
@@ -2310,13 +2569,7 @@ aipd_cleanup (NMDevice *self)
 	}
 
 	if (priv->aipd_pid > 0) {
-		kill (priv->aipd_pid, SIGKILL);
-
-		/* ensure the child is reaped */
-		nm_log_dbg (LOGD_AUTOIP4, "waiting for avahi-autoipd pid %d to exit", priv->aipd_pid);
-		waitpid (priv->aipd_pid, NULL, 0);
-		nm_log_dbg (LOGD_AUTOIP4, "avahi-autoip pid %d cleaned up", priv->aipd_pid);
-
+		nm_utils_kill_child_sync (priv->aipd_pid, SIGKILL, LOGD_AUTOIP4, "avahi-autoipd", NULL, 0, 0);
 		priv->aipd_pid = -1;
 	}
 
@@ -2336,15 +2589,15 @@ aipd_get_ip4_config (NMDevice *self, guint32 lla)
 	memset (&address, 0, sizeof (address));
 	address.address = lla;
 	address.plen = 16;
-	address.source = NM_PLATFORM_SOURCE_IP4LL;
+	address.source = NM_IP_CONFIG_SOURCE_IP4LL;
 	nm_ip4_config_add_address (config, &address);
 
 	/* Add a multicast route for link-local connections: destination= 224.0.0.0, netmask=240.0.0.0 */
 	memset (&route, 0, sizeof (route));
 	route.network = htonl (0xE0000000L);
 	route.plen = 4;
-	route.source = NM_PLATFORM_SOURCE_IP4LL;
-	route.metric = nm_device_get_priority (self);
+	route.source = NM_IP_CONFIG_SOURCE_IP4LL;
+	route.metric = nm_device_get_ip4_route_metric (self);
 	nm_ip4_config_add_route (config, &route);
 
 	return config;
@@ -2360,7 +2613,7 @@ nm_device_handle_autoip4_event (NMDevice *self,
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection = NULL;
-	const char *iface, *method;
+	const char *method;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 
 	g_return_if_fail (event != NULL);
@@ -2376,29 +2629,25 @@ nm_device_handle_autoip4_event (NMDevice *self,
 	if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) != 0)
 		return;
 
-	iface = nm_device_get_iface (self);
-
 	if (strcmp (event, "BIND") == 0) {
 		guint32 lla;
 		NMIP4Config *config;
 
 		if (inet_pton (AF_INET, address, &lla) <= 0) {
-			nm_log_err (LOGD_AUTOIP4, "(%s): invalid address %s received from avahi-autoipd.",
-			            iface, address);
+			_LOGE (LOGD_AUTOIP4, "invalid address %s received from avahi-autoipd.", address);
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
 			return;
 		}
 
 		if ((lla & IPV4LL_NETMASK) != IPV4LL_NETWORK) {
-			nm_log_err (LOGD_AUTOIP4, "(%s): invalid address %s received from avahi-autoipd (not link-local).",
-			            iface, address);
+			_LOGE (LOGD_AUTOIP4, "invalid address %s received from avahi-autoipd (not link-local).", address);
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
 			return;
 		}
 
 		config = aipd_get_ip4_config (self, lla);
 		if (config == NULL) {
-			nm_log_err (LOGD_AUTOIP4, "failed to get autoip config");
+			_LOGE (LOGD_AUTOIP4, "failed to get autoip config");
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 			return;
 		}
@@ -2408,8 +2657,7 @@ nm_device_handle_autoip4_event (NMDevice *self,
 			nm_device_activate_schedule_ip4_config_result (self, config);
 		} else if (priv->ip4_state == IP_DONE) {
 			if (!ip4_config_merge_and_apply (self, config, TRUE, &reason)) {
-				nm_log_err (LOGD_AUTOIP4, "(%s): failed to update IP4 config for autoip change.",
-							nm_device_get_iface (self));
+				_LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 			}
 		} else
@@ -2417,8 +2665,7 @@ nm_device_handle_autoip4_event (NMDevice *self,
 
 		g_object_unref (config);
 	} else {
-		nm_log_warn (LOGD_AUTOIP4, "(%s): autoip address %s no longer valid because '%s'.",
-		            iface, address, event);
+		_LOGW (LOGD_AUTOIP4, "autoip address %s no longer valid because '%s'.", address, event);
 
 		/* The address is gone; terminate the connection or fail activation */
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
@@ -2431,26 +2678,19 @@ aipd_watch_cb (GPid pid, gint status, gpointer user_data)
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceState state;
-	const char *iface;
 
 	if (!priv->aipd_watch)
 		return;
 	priv->aipd_watch = 0;
 
-	iface = nm_device_get_iface (self);
-
-	if (WIFEXITED (status)) {
-		nm_log_dbg (LOGD_AUTOIP4, "(%s): avahi-autoipd exited with error code %d",
-		            iface, WEXITSTATUS (status));
-	} else if (WIFSTOPPED (status)) {
-		nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd stopped unexpectedly with signal %d",
-		            iface, WSTOPSIG (status));
-	} else if (WIFSIGNALED (status)) {
-		nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd died with signal %d",
-		             iface, WTERMSIG (status));
-	} else {
-		nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd died from an unknown cause", iface);
-	}
+	if (WIFEXITED (status))
+		_LOGD (LOGD_AUTOIP4, "avahi-autoipd exited with error code %d", WEXITSTATUS (status));
+	else if (WIFSTOPPED (status))
+		_LOGW (LOGD_AUTOIP4, "avahi-autoipd stopped unexpectedly with signal %d", WSTOPSIG (status));
+	else if (WIFSIGNALED (status))
+		_LOGW (LOGD_AUTOIP4, "avahi-autoipd died with signal %d", WTERMSIG (status));
+	else
+		_LOGW (LOGD_AUTOIP4, "avahi-autoipd died from an unknown cause");
 
 	aipd_cleanup (self);
 
@@ -2466,7 +2706,7 @@ aipd_timeout_cb (gpointer user_data)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->aipd_timeout) {
-		nm_log_info (LOGD_AUTOIP4, "(%s): avahi-autoipd timed out.", nm_device_get_iface (self));
+		_LOGI (LOGD_AUTOIP4, "avahi-autoipd timed out.");
 		priv->aipd_timeout = 0;
 		aipd_cleanup (self);
 
@@ -2501,63 +2741,51 @@ static NMActStageReturn
 aipd_start (NMDevice *self, NMDeviceStateReason *reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *iface = nm_device_get_iface (self);
-	char *argv[6], *cmdline;
-	const char **aipd_binary = NULL;
-	static const char *aipd_paths[] = {
-		"/usr/sbin/avahi-autoipd",
-		"/usr/local/sbin/avahi-autoipd",
-		NULL
-	};
+	const char *argv[6];
+	char *cmdline;
+	const char *aipd_binary;
 	int i = 0;
 	GError *error = NULL;
 
 	aipd_cleanup (self);
 
 	/* Find avahi-autoipd */
-	aipd_binary = aipd_paths;
-	while (*aipd_binary != NULL) {
-		if (g_file_test (*aipd_binary, G_FILE_TEST_EXISTS))
-			break;
-		aipd_binary++;
-	}
-
-	if (!*aipd_binary) {
-		nm_log_warn (LOGD_DEVICE | LOGD_AUTOIP4,
-		             "Activation (%s) Stage 3 of 5 (IP Configure Start) failed"
-		             " to start avahi-autoipd: not found", iface);
+	aipd_binary = nm_utils_find_helper ("avahi-autoipd", NULL, NULL);
+	if (!aipd_binary) {
+		_LOGW (LOGD_DEVICE | LOGD_AUTOIP4,
+		       "Activation: Stage 3 of 5 (IP Configure Start) failed"
+		       " to start avahi-autoipd: not found");
 		*reason = NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED;
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	argv[i++] = (char *) (*aipd_binary);
+	argv[i++] = aipd_binary;
 	argv[i++] = "--script";
-	argv[i++] = (char *) nm_device_autoipd_helper_path;
+	argv[i++] = nm_device_autoipd_helper_path;
 
 	if (nm_logging_enabled (LOGL_DEBUG, LOGD_AUTOIP4))
 		argv[i++] = "--debug";
-	argv[i++] = (char *) nm_device_get_ip_iface (self);
+	argv[i++] = nm_device_get_ip_iface (self);
 	argv[i++] = NULL;
 
-	cmdline = g_strjoinv (" ", argv);
-	nm_log_dbg (LOGD_AUTOIP4, "running: %s", cmdline);
+	cmdline = g_strjoinv (" ", (char **) argv);
+	_LOGD (LOGD_AUTOIP4, "running: %s", cmdline);
 	g_free (cmdline);
 
-	if (!g_spawn_async ("/", argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+	if (!g_spawn_async ("/", (char **) argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
 	                    &aipd_child_setup, NULL, &(priv->aipd_pid), &error)) {
-		nm_log_warn (LOGD_DEVICE | LOGD_AUTOIP4,
-		             "Activation (%s) Stage 3 of 5 (IP Configure Start) failed"
-		             " to start avahi-autoipd: %s",
-		             iface,
-		             error && error->message ? error->message : "(unknown)");
+		_LOGW (LOGD_DEVICE | LOGD_AUTOIP4,
+		       "Activation: Stage 3 of 5 (IP Configure Start) failed"
+		       " to start avahi-autoipd: %s",
+		       error && error->message ? error->message : "(unknown)");
 		g_clear_error (&error);
 		aipd_cleanup (self);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	nm_log_info (LOGD_DEVICE | LOGD_AUTOIP4,
-	             "Activation (%s) Stage 3 of 5 (IP Configure Start) started"
-	             " avahi-autoipd...", iface);
+	_LOGI (LOGD_DEVICE | LOGD_AUTOIP4,
+	       "Activation: Stage 3 of 5 (IP Configure Start) started"
+	       " avahi-autoipd...");
 
 	/* Monitor the child process so we know when it dies */
 	priv->aipd_watch = g_child_watch_add (priv->aipd_pid, aipd_watch_cb, self);
@@ -2569,6 +2797,52 @@ aipd_start (NMDevice *self, NMDeviceStateReason *reason)
 }
 
 /*********************************************/
+
+static gboolean
+_device_get_default_route_from_platform (NMDevice *self, int addr_family, NMPlatformIPRoute *out_route)
+{
+	gboolean success = FALSE;
+	int ifindex = nm_device_get_ip_ifindex (self);
+	GArray *routes;
+
+	if (addr_family == AF_INET)
+		routes = nm_platform_ip4_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT);
+	else
+		routes = nm_platform_ip6_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT);
+
+	if (routes) {
+		guint route_metric = G_MAXUINT32, m;
+		const NMPlatformIPRoute *route = NULL, *r;
+		guint i;
+
+		/* if there are several default routes, find the one with the best metric */
+		for (i = 0; i < routes->len; i++) {
+			if (addr_family == AF_INET) {
+				r = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, i);
+				m = r->metric;
+			} else {
+				r = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, i);
+				m = nm_utils_ip6_route_metric_normalize (r->metric);
+			}
+			if (!route || m < route_metric) {
+				route = r;
+				route_metric = m;
+			}
+		}
+
+		if (route) {
+			if (addr_family == AF_INET)
+				*((NMPlatformIP4Route *) out_route) = *((NMPlatformIP4Route *) route);
+			else
+				*((NMPlatformIP6Route *) out_route) = *((NMPlatformIP6Route *) route);
+			success = TRUE;
+		}
+		g_array_free (routes, TRUE);
+	}
+	return success;
+}
+
+/*********************************************/
 /* DHCPv4 stuff */
 
 static void
@@ -2583,11 +2857,6 @@ dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release)
 			priv->dhcp4_state_sigid = 0;
 		}
 
-		if (priv->dhcp4_timeout_sigid) {
-			g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_timeout_sigid);
-			priv->dhcp4_timeout_sigid = 0;
-		}
-
 		nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE);
 
 		if (stop)
@@ -2602,14 +2871,6 @@ dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release)
 	}
 }
 
-static void
-dhcp4_add_option_cb (gpointer key, gpointer value, gpointer user_data)
-{
-	nm_dhcp4_config_add_option (NM_DHCP4_CONFIG (user_data),
-	                            (const char *) key,
-	                            (const char *) value);
-}
-
 static gboolean
 ip4_config_merge_and_apply (NMDevice *self,
                             NMIP4Config *config,
@@ -2620,6 +2881,7 @@ ip4_config_merge_and_apply (NMDevice *self,
 	NMConnection *connection;
 	gboolean success;
 	NMIP4Config *composite;
+	const guint32 default_route_metric = nm_device_get_ip4_route_metric (self);
 
 	/* Merge all the configs into the composite config */
 	if (config) {
@@ -2635,12 +2897,79 @@ ip4_config_merge_and_apply (NMDevice *self,
 	if (priv->ext_ip4_config)
 		nm_ip4_config_merge (composite, priv->ext_ip4_config);
 
-	/* Merge user overrides into the composite config */
+	/* Merge WWAN config *last* to ensure modem-given settings overwrite
+	 * any external stuff set by pppd or other scripts.
+	 */
+	if (priv->wwan_ip4_config)
+		nm_ip4_config_merge (composite, priv->wwan_ip4_config);
+
+	/* Merge user overrides into the composite config.  Generated+assumed
+	 * connections come from the system not the user and merging them would
+	 * be redundant, so don't bother.
+	 */
 	connection = nm_device_get_connection (self);
+	priv->default_route.v4_has = FALSE;
+	priv->default_route.v4_is_assumed = TRUE;
 	if (connection) {
-		nm_ip4_config_merge_setting (composite,
-		                             nm_connection_get_setting_ip4_config (connection),
-		                             nm_device_get_priority (self));
+		gboolean assumed = nm_device_uses_assumed_connection (self);
+		NMPlatformIP4Route *route = &priv->default_route.v4;
+
+		if (!nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection))) {
+			nm_ip4_config_merge_setting (composite,
+			                             nm_connection_get_setting_ip4_config (connection),
+			                             default_route_metric);
+		}
+
+		/* Add the default route.
+		 *
+		 * We keep track of the default route of a device in a private field.
+		 * NMDevice needs to know the default route at this point, because the gateway
+		 * might require a direct route (see below).
+		 *
+		 * But also, we don't want to add the default route to priv->ip4_config,
+		 * because the default route from the setting might not be the same that
+		 * NMDefaultRouteManager eventually configures (because the it might
+		 * tweak the effective metric).
+		 */
+		if (   !assumed
+		    && nm_default_route_manager_ip4_connection_has_default_route (nm_default_route_manager_get (), connection)) {
+			guint32 gateway = 0;
+
+			priv->default_route.v4_is_assumed = FALSE;
+			if (   (!commit && priv->ext_ip4_config_had_any_addresses)
+			    || ( commit && nm_ip4_config_get_num_addresses (composite))) {
+				/* For managed interfaces, we can only configure a gateway, if either the external config indicates
+				 * that we already have addresses, or if we are about to commit any addresses.
+				 * Otherwise adding a default route will fail, because NMDefaultRouteManager does not add any
+				 * addresses for the route. */
+				gateway = nm_ip4_config_get_gateway (composite);
+				if (   gateway
+				    || nm_device_get_device_type (self) == NM_DEVICE_TYPE_MODEM) {
+					memset (route, 0, sizeof (*route));
+					route->source = NM_IP_CONFIG_SOURCE_USER;
+					route->gateway = gateway;
+					route->metric = default_route_metric;
+					route->mss = nm_ip4_config_get_mss (composite);
+					priv->default_route.v4_has = TRUE;
+
+					if (   gateway
+					    && !nm_ip4_config_get_subnet_for_host (composite, gateway)
+					    && !nm_ip4_config_get_direct_route_for_host (composite, gateway)) {
+						/* add a direct route to the gateway */
+						NMPlatformIP4Route r = *route;
+
+						r.network = gateway;
+						r.plen = 32;
+						r.gateway = 0;
+						nm_ip4_config_add_route (composite, &r);
+					}
+				}
+			}
+		} else {
+			/* For interfaces that are assumed and that have no default-route by configuration, we assume
+			 * the default connection and pick up whatever is configured. */
+			priv->default_route.v4_has = _device_get_default_route_from_platform (self, AF_INET, (NMPlatformIPRoute *) route);
+		}
 	}
 
 	/* Allow setting MTU etc */
@@ -2649,7 +2978,7 @@ ip4_config_merge_and_apply (NMDevice *self,
 			NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, composite);
 	}
 
-	success = nm_device_set_ip4_config (self, composite, commit, out_reason);
+	success = nm_device_set_ip4_config (self, composite, default_route_metric, commit, out_reason);
 	g_object_unref (composite);
 	return success;
 }
@@ -2662,8 +2991,7 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 	g_return_if_fail (config != NULL);
 
 	if (!ip4_config_merge_and_apply (self, config, TRUE, &reason)) {
-		nm_log_warn (LOGD_DHCP4, "(%s): failed to update IPv4 config for DHCP change.",
-		             nm_device_get_ip_iface (self));
+		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 	} else {
 		/* Notify dispatcher scripts of new DHCP4 config */
@@ -2677,94 +3005,93 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 }
 
 static void
-dhcp4_fail (NMDevice *device, gboolean timeout)
+dhcp4_fail (NMDevice *self, gboolean timeout)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
-
-	nm_dhcp4_config_reset (priv->dhcp4_config);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	dhcp4_cleanup (self, TRUE, FALSE);
 	if (timeout || (priv->ip4_state == IP_CONF))
-		nm_device_activate_schedule_ip4_config_timeout (device);
-	else if (priv->ip4_state == IP_FAIL)
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+		nm_device_activate_schedule_ip4_config_timeout (self);
+	else if (priv->ip4_state == IP_DONE)
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+	else
+		g_warn_if_reached ();
+}
+
+static void
+dhcp4_update_config (NMDevice *self, NMDhcp4Config *config, GHashTable *options)
+{
+	GHashTableIter iter;
+	const char *key, *value;
+
+	/* Update the DHCP4 config object with new DHCP options */
+	nm_dhcp4_config_reset (config);
+
+	g_hash_table_iter_init (&iter, options);
+	while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value))
+		nm_dhcp4_config_add_option (config, key, value);
+
+	g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG);
 }
 
 static void
-dhcp4_state_changed (NMDHCPClient *client,
-                     NMDHCPState state,
+dhcp4_state_changed (NMDhcpClient *client,
+                     NMDhcpState state,
+                     NMIP4Config *ip4_config,
+                     GHashTable *options,
                      gpointer user_data)
 {
-	NMDevice *device = NM_DEVICE (user_data);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
-	NMIP4Config *config;
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE);
+	g_return_if_fail (!ip4_config || NM_IS_IP4_CONFIG (ip4_config));
 
-	nm_log_dbg (LOGD_DHCP4, "(%s): new DHCPv4 client state %d",
-	            nm_device_get_iface (device), state);
+	_LOGD (LOGD_DHCP4, "new DHCPv4 client state %d", state);
 
 	switch (state) {
-	case DHC_BOUND4:     /* lease obtained */
-	case DHC_RENEW4:     /* lease renewed */
-	case DHC_REBOOT:     /* have valid lease, but now obtained a different one */
-	case DHC_REBIND4:    /* new, different lease */
-		config = nm_dhcp_client_get_ip4_config (priv->dhcp4_client, FALSE);
-		if (!config) {
-			nm_log_warn (LOGD_DHCP4, "(%s): failed to get IPv4 config in response to DHCP event.",
-					     nm_device_get_ip_iface (device));
-			nm_device_state_changed (device,
+	case NM_DHCP_STATE_BOUND:
+		if (!ip4_config) {
+			_LOGW (LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event.");
+			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_FAILED,
 			                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 			break;
 		}
 
-		/* Update the DHCP4 config object with new DHCP options */
-		nm_dhcp4_config_reset (priv->dhcp4_config);
-		nm_dhcp_client_foreach_option (priv->dhcp4_client,
-			                           dhcp4_add_option_cb,
-			                           priv->dhcp4_config);
-		g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG);
+		dhcp4_update_config (self, priv->dhcp4_config, options);
 
 		if (priv->ip4_state == IP_CONF)
-			nm_device_activate_schedule_ip4_config_result (device, config);
+			nm_device_activate_schedule_ip4_config_result (self, ip4_config);
 		else if (priv->ip4_state == IP_DONE)
-			dhcp4_lease_change (device, config);
-		g_object_unref (config);
-
+			dhcp4_lease_change (self, ip4_config);
 		break;
-	case DHC_TIMEOUT: /* timed out contacting DHCP server */
-		dhcp4_fail (device, TRUE);
+	case NM_DHCP_STATE_TIMEOUT:
+		dhcp4_fail (self, TRUE);
 		break;
-	case DHC_END: /* dhclient exited normally */
-	case DHC_FAIL: /* all attempts to contact server timed out, sleeping */
-	case DHC_ABEND: /* dhclient exited abnormally */
-		/* dhclient quit and can't get/renew a lease; so kill the connection */
-		dhcp4_fail (device, FALSE);
+	case NM_DHCP_STATE_EXPIRE:
+		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
+		if (priv->ip4_state == IP_CONF)
+			break;
+		/* Fall through */
+	case NM_DHCP_STATE_DONE:
+	case NM_DHCP_STATE_FAIL:
+		dhcp4_fail (self, FALSE);
 		break;
 	default:
 		break;
 	}
 }
 
-static void
-dhcp4_timeout (NMDHCPClient *client, gpointer user_data)
-{
-	NMDevice *device = NM_DEVICE (user_data);
-
-	g_return_if_fail (nm_device_get_act_request (device) != NULL);
-	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE);
-
-	nm_dhcp_client_stop (client, FALSE);
-	dhcp4_fail (device, TRUE);
-}
-
 static NMActStageReturn
 dhcp4_start (NMDevice *self,
              NMConnection *connection,
              NMDeviceStateReason *reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMSettingIP4Config *s_ip4;
+	NMSettingIPConfig *s_ip4;
+	const guint8 *hw_addr;
+	size_t hw_addr_len = 0;
 	GByteArray *tmp = NULL;
 
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
@@ -2774,21 +3101,26 @@ dhcp4_start (NMDevice *self,
 		g_object_unref (priv->dhcp4_config);
 	priv->dhcp4_config = nm_dhcp4_config_new ();
 
-	if (priv->hw_addr_len) {
-		tmp = g_byte_array_sized_new (priv->hw_addr_len);
-		g_byte_array_append (tmp, priv->hw_addr, priv->hw_addr_len);
+	hw_addr = nm_platform_link_get_address (nm_device_get_ip_ifindex (self), &hw_addr_len);
+	if (hw_addr_len) {
+		tmp = g_byte_array_sized_new (hw_addr_len);
+		g_byte_array_append (tmp, hw_addr, hw_addr_len);
 	}
 
 	/* Begin DHCP on the interface */
 	g_warn_if_fail (priv->dhcp4_client == NULL);
 	priv->dhcp4_client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
 	                                                nm_device_get_ip_iface (self),
+	                                                nm_device_get_ip_ifindex (self),
 	                                                tmp,
 	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_priority (self),
-	                                                s_ip4,
+	                                                nm_device_get_ip4_route_metric (self),
+	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip4),
+	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip4),
+	                                                nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4)),
 	                                                priv->dhcp_timeout,
-	                                                priv->dhcp_anycast_address);
+	                                                priv->dhcp_anycast_address,
+	                                                NULL);
 
 	if (tmp)
 		g_byte_array_free (tmp, TRUE);
@@ -2802,10 +3134,6 @@ dhcp4_start (NMDevice *self,
 	                                            NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
 	                                            G_CALLBACK (dhcp4_state_changed),
 	                                            self);
-	priv->dhcp4_timeout_sigid = g_signal_connect (priv->dhcp4_client,
-	                                              NM_DHCP_CLIENT_SIGNAL_TIMEOUT,
-	                                              G_CALLBACK (dhcp4_timeout),
-	                                              self);
 
 	nm_device_add_pending_action (self, PENDING_ACTION_DHCP4, TRUE);
 
@@ -2823,8 +3151,7 @@ nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 
 	g_return_val_if_fail (priv->dhcp4_client != NULL, FALSE);
 
-	nm_log_info (LOGD_DHCP4, "(%s): DHCPv4 lease renewal requested",
-	             nm_device_get_iface (self));
+	_LOGI (LOGD_DHCP4, "DHCPv4 lease renewal requested");
 
 	/* Terminate old DHCP instance and release the old lease */
 	dhcp4_cleanup (self, TRUE, release);
@@ -2849,20 +3176,20 @@ release_shared_ip (gpointer data)
 }
 
 static gboolean
-reserve_shared_ip (NMSettingIP4Config *s_ip4, NMPlatformIP4Address *address)
+reserve_shared_ip (NMDevice *self, NMSettingIPConfig *s_ip4, NMPlatformIP4Address *address)
 {
 	if (G_UNLIKELY (shared_ips == NULL))
 		shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
 
 	memset (address, 0, sizeof (*address));
 
-	if (s_ip4 && nm_setting_ip4_config_get_num_addresses (s_ip4)) {
+	if (s_ip4 && nm_setting_ip_config_get_num_addresses (s_ip4)) {
 		/* Use the first user-supplied address */
-		NMIP4Address *user = nm_setting_ip4_config_get_address (s_ip4, 0);
+		NMIPAddress *user = nm_setting_ip_config_get_address (s_ip4, 0);
 
 		g_assert (user);
-		address->address = nm_ip4_address_get_address (user);
-		address->plen = nm_ip4_address_get_prefix (user);
+		nm_ip_address_get_address_binary (user, &address->address);
+		address->plen = nm_ip_address_get_prefix (user);
 	} else {
 		/* Find an unused address in the 10.42.x.x range */
 		guint32 start = (guint32) ntohl (0x0a2a0001); /* 10.42.0.1 */
@@ -2871,7 +3198,7 @@ reserve_shared_ip (NMSettingIP4Config *s_ip4, NMPlatformIP4Address *address)
 		while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
 			count += ntohl (0x100);
 			if (count > ntohl (0xFE00)) {
-				nm_log_err (LOGD_SHARING, "ran out of shared IP addresses!");
+				_LOGE (LOGD_SHARING, "ran out of shared IP addresses!");
 				return FALSE;
 			}
 		}
@@ -2894,13 +3221,13 @@ shared4_new_config (NMDevice *self, NMConnection *connection, NMDeviceStateReaso
 
 	g_return_val_if_fail (self != NULL, NULL);
 
-	if (!reserve_shared_ip (nm_connection_get_setting_ip4_config (connection), &address)) {
+	if (!reserve_shared_ip (self, nm_connection_get_setting_ip4_config (connection), &address)) {
 		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
 		return NULL;
 	}
 
 	config = nm_ip4_config_new ();
-	address.source = NM_PLATFORM_SOURCE_SHARED;
+	address.source = NM_IP_CONFIG_SOURCE_SHARED;
 	nm_ip4_config_add_address (config, &address);
 
 	/* Remove the address lock when the object gets disposed */
@@ -2914,7 +3241,79 @@ shared4_new_config (NMDevice *self, NMConnection *connection, NMDeviceStateReaso
 /*********************************************/
 
 static gboolean
-have_any_ready_slaves (NMDevice *device, const GSList *slaves)
+connection_ip4_method_requires_carrier (NMConnection *connection,
+                                        gboolean *out_ip4_enabled)
+{
+	const char *method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	static const char *ip4_carrier_methods[] = {
+		NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+		NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL,
+		NULL
+	};
+
+	if (out_ip4_enabled)
+		*out_ip4_enabled = !!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
+	return _nm_utils_string_in_list (method, ip4_carrier_methods);
+}
+
+static gboolean
+connection_ip6_method_requires_carrier (NMConnection *connection,
+                                        gboolean *out_ip6_enabled)
+{
+	const char *method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	static const char *ip6_carrier_methods[] = {
+		NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		NM_SETTING_IP6_CONFIG_METHOD_DHCP,
+		NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+		NULL
+	};
+
+	if (out_ip6_enabled)
+		*out_ip6_enabled = !!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+	return _nm_utils_string_in_list (method, ip6_carrier_methods);
+}
+
+static gboolean
+connection_requires_carrier (NMConnection *connection)
+{
+	NMSettingIPConfig *s_ip4, *s_ip6;
+	gboolean ip4_carrier_wanted, ip6_carrier_wanted;
+	gboolean ip4_used = FALSE, ip6_used = FALSE;
+
+	ip4_carrier_wanted = connection_ip4_method_requires_carrier (connection, &ip4_used);
+	if (ip4_carrier_wanted) {
+		/* If IPv4 wants a carrier and cannot fail, the whole connection
+		 * requires a carrier regardless of the IPv6 method.
+		 */
+		s_ip4 = nm_connection_get_setting_ip4_config (connection);
+		if (s_ip4 && !nm_setting_ip_config_get_may_fail (s_ip4))
+			return TRUE;
+	}
+
+	ip6_carrier_wanted = connection_ip6_method_requires_carrier (connection, &ip6_used);
+	if (ip6_carrier_wanted) {
+		/* If IPv6 wants a carrier and cannot fail, the whole connection
+		 * requires a carrier regardless of the IPv4 method.
+		 */
+		s_ip6 = nm_connection_get_setting_ip6_config (connection);
+		if (s_ip6 && !nm_setting_ip_config_get_may_fail (s_ip6))
+			return TRUE;
+	}
+
+	/* If an IP version wants a carrier and and the other IP version isn't
+	 * used, the connection requires carrier since it will just fail without one.
+	 */
+	if (ip4_carrier_wanted && !ip6_used)
+		return TRUE;
+	if (ip6_carrier_wanted && !ip4_used)
+		return TRUE;
+
+	/* If both want a carrier, the whole connection wants a carrier */
+	return ip4_carrier_wanted && ip6_carrier_wanted;
+}
+
+static gboolean
+have_any_ready_slaves (NMDevice *self, const GSList *slaves)
 {
 	const GSList *iter;
 
@@ -2955,16 +3354,11 @@ act_stage3_ip4_config_start (NMDevice *self,
 	connection = nm_device_get_connection (self);
 	g_assert (connection);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (priv->master)
-		g_assert_cmpstr (method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
-
-	if (   strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) != 0
+	if (   connection_ip4_method_requires_carrier (connection, NULL)
 	    && priv->is_master
 	    && !priv->carrier) {
-		nm_log_info (LOGD_IP4 | LOGD_DEVICE,
-		             "(%s): IPv4 config waiting until carrier is on",
-		             nm_device_get_ip_iface (self));
+		_LOGI (LOGD_IP4 | LOGD_DEVICE,
+		       "IPv4 config waiting until carrier is on");
 		return NM_ACT_STAGE_RETURN_WAIT;
 	}
 
@@ -2977,13 +3371,14 @@ act_stage3_ip4_config_start (NMDevice *self,
 		g_slist_free (slaves);
 
 		if (ready_slaves == FALSE) {
-			nm_log_info (LOGD_DEVICE | LOGD_IP4,
-			             "(%s): IPv4 config waiting until slaves are ready",
-			             nm_device_get_ip_iface (self));
+			_LOGI (LOGD_DEVICE | LOGD_IP4,
+			       "IPv4 config waiting until slaves are ready");
 			return NM_ACT_STAGE_RETURN_WAIT;
 		}
 	}
 
+	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+
 	/* Start IPv4 addressing based on the method requested */
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
 		ret = dhcp4_start (self, connection, reason);
@@ -3004,10 +3399,8 @@ act_stage3_ip4_config_start (NMDevice *self,
 	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0) {
 		/* Nothing to do... */
 		ret = NM_ACT_STAGE_RETURN_STOP;
-	} else {
-		nm_log_warn (LOGD_IP4, "(%s): unhandled IPv4 config method '%s'; will fail",
-		             nm_device_get_ip_iface (self), method);
-	}
+	} else
+		_LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
 
 	return ret;
 }
@@ -3029,33 +3422,20 @@ dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release)
 			priv->dhcp6_state_sigid = 0;
 		}
 
-		if (priv->dhcp6_timeout_sigid) {
-			g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_timeout_sigid);
-			priv->dhcp6_timeout_sigid = 0;
-		}
-
-		nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE);
-
 		if (stop)
 			nm_dhcp_client_stop (priv->dhcp6_client, release);
 
 		g_clear_object (&priv->dhcp6_client);
 	}
 
+	nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE);
+
 	if (priv->dhcp6_config) {
 		g_clear_object (&priv->dhcp6_config);
 		g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG);
 	}
 }
 
-static void
-dhcp6_add_option_cb (gpointer key, gpointer value, gpointer user_data)
-{
-	nm_dhcp6_config_add_option (NM_DHCP6_CONFIG (user_data),
-	                            (const char *) key,
-	                            (const char *) value);
-}
-
 static gboolean
 ip6_config_merge_and_apply (NMDevice *self,
                             gboolean commit,
@@ -3080,109 +3460,216 @@ ip6_config_merge_and_apply (NMDevice *self,
 	if (priv->ext_ip6_config)
 		nm_ip6_config_merge (composite, priv->ext_ip6_config);
 
-	/* Merge user overrides into the composite config */
+	/* Merge WWAN config *last* to ensure modem-given settings overwrite
+	 * any external stuff set by pppd or other scripts.
+	 */
+	if (priv->wwan_ip6_config)
+		nm_ip6_config_merge (composite, priv->wwan_ip6_config);
+
+	/* Merge user overrides into the composite config.  Generated+assumed
+	 * connections come from the system not the user and merging them would
+	 * be redundant, so don't bother.
+	 */
 	connection = nm_device_get_connection (self);
+	priv->default_route.v6_has = FALSE;
+	priv->default_route.v6_is_assumed = TRUE;
 	if (connection) {
-		nm_ip6_config_merge_setting (composite,
-		                             nm_connection_get_setting_ip6_config (connection),
-		                             nm_device_get_priority (self));
+		gboolean assumed = nm_device_uses_assumed_connection (self);
+		NMPlatformIP6Route *route = &priv->default_route.v6;
+
+		if (!nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection))) {
+			nm_ip6_config_merge_setting (composite,
+			                             nm_connection_get_setting_ip6_config (connection),
+			                             nm_device_get_ip6_route_metric (self));
+		}
+
+		/* Add the default route.
+		 *
+		 * We keep track of the default route of a device in a private field.
+		 * NMDevice needs to know the default route at this point, because the gateway
+		 * might require a direct route (see below).
+		 *
+		 * But also, we don't want to add the default route to priv->ip4_config,
+		 * because the default route from the setting might not be the same that
+		 * NMDefaultRouteManager eventually configures (because the it might
+		 * tweak the effective metric).
+		 */
+		if (   !assumed
+		    && nm_default_route_manager_ip6_connection_has_default_route (nm_default_route_manager_get (), connection)) {
+			const struct in6_addr *gateway = NULL;
+
+			priv->default_route.v6_is_assumed = FALSE;
+			if (   (!commit && priv->ext_ip6_config_had_any_addresses)
+			    || ( commit && nm_ip6_config_get_num_addresses (composite))) {
+				/* For managed interfaces, we can only configure a gateway, if either the external config indicates
+				 * that we already have addresses, or if we are about to commit any addresses.
+				 * Otherwise adding a default route will fail, because NMDefaultRouteManager does not add any
+				 * addresses for the route. */
+				gateway = nm_ip6_config_get_gateway (composite);
+				if (gateway) {
+					memset (route, 0, sizeof (*route));
+					route->source = NM_IP_CONFIG_SOURCE_USER;
+					route->gateway = *gateway;
+					route->metric = nm_device_get_ip6_route_metric (self);
+					route->mss = nm_ip6_config_get_mss (composite);
+					priv->default_route.v6_has = TRUE;
+
+					if (   gateway
+					    && !nm_ip6_config_get_subnet_for_host (composite, gateway)
+					    && !nm_ip6_config_get_direct_route_for_host (composite, gateway)) {
+						/* add a direct route to the gateway */
+						NMPlatformIP6Route r = *route;
+
+						r.network = *gateway;
+						r.plen = 128;
+						r.gateway = in6addr_any;
+						nm_ip6_config_add_route (composite, &r);
+					}
+				}
+			}
+		} else {
+			/* For interfaces that are assumed and that have no default-route by configuration, we assume
+			 * the default connection and pick up whatever is configured. */
+			priv->default_route.v6_has = _device_get_default_route_from_platform (self, AF_INET6, (NMPlatformIPRoute *) route);
+		}
 	}
 
 	nm_ip6_config_addresses_sort (composite,
 	    priv->rdisc ? priv->rdisc_use_tempaddr : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 
+	/* Allow setting MTU etc */
+	if (commit) {
+		if (NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit)
+			NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self, composite);
+	}
+
 	success = nm_device_set_ip6_config (self, composite, commit, out_reason);
 	g_object_unref (composite);
 	return success;
 }
 
 static void
-dhcp6_lease_change (NMDevice *device)
+dhcp6_lease_change (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 
 	if (priv->dhcp6_ip6_config == NULL) {
-		nm_log_warn (LOGD_DHCP6, "(%s): failed to get DHCPv6 config for rebind",
-		             nm_device_get_ip_iface (device));
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+		_LOGW (LOGD_DHCP6, "failed to get DHCPv6 config for rebind");
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
 		return;
 	}
 
 	g_assert (priv->dhcp6_client);  /* sanity check */
 
-	connection = nm_device_get_connection (device);
+	connection = nm_device_get_connection (self);
 	g_assert (connection);
 
 	/* Apply the updated config */
-	if (ip6_config_merge_and_apply (device, TRUE, &reason) == FALSE) {
-		nm_log_warn (LOGD_DHCP6, "(%s): failed to update IPv6 config in response to DHCP event.",
-		             nm_device_get_ip_iface (device));
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
+	if (ip6_config_merge_and_apply (self, TRUE, &reason) == FALSE) {
+		_LOGW (LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event.");
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 	} else {
 		/* Notify dispatcher scripts of new DHCPv6 config */
-		nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, connection, device, NULL, NULL, NULL);
+		nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, connection, self, NULL, NULL, NULL);
 	}
 }
 
 static void
-dhcp6_fail (NMDevice *device, gboolean timeout)
+dhcp6_fail (NMDevice *self, gboolean timeout)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	dhcp6_cleanup (self, TRUE, FALSE);
+
+	if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) {
+		if (timeout || (priv->ip6_state == IP_CONF))
+			nm_device_activate_schedule_ip6_config_timeout (self);
+		else if (priv->ip6_state == IP_DONE)
+			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+		else
+			g_warn_if_reached ();
+	} else {
+		/* not a hard failure; just live with the RA info */
+		if (priv->ip6_state == IP_CONF)
+			nm_device_activate_schedule_ip6_config_result (self);
+	}
+}
 
-	nm_dhcp6_config_reset (priv->dhcp6_config);
+static void
+dhcp6_timeout (NMDevice *self, NMDhcpClient *client)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (timeout || (priv->ip6_state == IP_CONF))
-		nm_device_activate_schedule_ip6_config_timeout (device);
-	else if (priv->ip6_state == IP_FAIL)
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+	if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED)
+		dhcp6_fail (self, TRUE);
+	else {
+		/* not a hard failure; just live with the RA info */
+		dhcp6_cleanup (self, TRUE, FALSE);
+		if (priv->ip6_state == IP_CONF)
+			nm_device_activate_schedule_ip6_config_result (self);
+	}
 }
 
 static void
-dhcp6_state_changed (NMDHCPClient *client,
-                     NMDHCPState state,
+dhcp6_update_config (NMDevice *self, NMDhcp6Config *config, GHashTable *options)
+{
+	GHashTableIter iter;
+	const char *key, *value;
+
+	/* Update the DHCP6 config object with new DHCP options */
+	nm_dhcp6_config_reset (config);
+
+	g_hash_table_iter_init (&iter, options);
+	while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value))
+		nm_dhcp6_config_add_option (config, key, value);
+
+	g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG);
+}
+
+static void
+dhcp6_state_changed (NMDhcpClient *client,
+                     NMDhcpState state,
+                     NMIP6Config *ip6_config,
+                     GHashTable *options,
                      gpointer user_data)
 {
-	NMDevice *device = NM_DEVICE (user_data);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE);
+	g_return_if_fail (!ip6_config || NM_IS_IP6_CONFIG (ip6_config));
 
-	nm_log_dbg (LOGD_DHCP6, "(%s): new DHCPv6 client state %d",
-	            nm_device_get_iface (device), state);
+	_LOGD (LOGD_DHCP6, "new DHCPv6 client state %d", state);
 
 	switch (state) {
-	case DHC_BOUND6:
-	case DHC_RENEW6:     /* lease renewed */
-	case DHC_REBOOT:     /* have valid lease, but now obtained a different one */
-	case DHC_REBIND6:    /* new, different lease */
+	case NM_DHCP_STATE_BOUND:
 		g_clear_object (&priv->dhcp6_ip6_config);
-		priv->dhcp6_ip6_config = nm_dhcp_client_get_ip6_config (priv->dhcp6_client, FALSE);
-
-		/* Update the DHCP6 config object with new DHCP options */
-		nm_dhcp6_config_reset (priv->dhcp6_config);
-		if (priv->dhcp6_ip6_config) {
-			nm_dhcp_client_foreach_option (priv->dhcp6_client,
-			                               dhcp6_add_option_cb,
-			                               priv->dhcp6_config);
+		if (ip6_config) {
+			priv->dhcp6_ip6_config = g_object_ref (ip6_config);
+			dhcp6_update_config (self, priv->dhcp6_config, options);
 		}
-		g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG);
 
 		if (priv->ip6_state == IP_CONF) {
 			if (priv->dhcp6_ip6_config == NULL) {
 				/* FIXME: Initial DHCP failed; should we fail IPv6 entirely then? */
-				nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DHCP_FAILED);
+				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DHCP_FAILED);
 				break;
 			}
-			nm_device_activate_schedule_ip6_config_result (device);
+			nm_device_activate_schedule_ip6_config_result (self);
 		} else if (priv->ip6_state == IP_DONE)
-			dhcp6_lease_change (device);
+			dhcp6_lease_change (self);
 		break;
-	case DHC_TIMEOUT: /* timed out contacting DHCP server */
-		dhcp6_fail (device, TRUE);
+	case NM_DHCP_STATE_TIMEOUT:
+		dhcp6_timeout (self, client);
 		break;
-	case DHC_END: /* dhclient exited normally */
+	case NM_DHCP_STATE_EXPIRE:
+		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
+		if (priv->ip6_state != IP_CONF)
+			dhcp6_fail (self, FALSE);
+		break;
+	case NM_DHCP_STATE_DONE:
 		/* In IPv6 info-only mode, the client doesn't handle leases so it
 		 * may exit right after getting a response from the server.  That's
 		 * normal.  In that case we just ignore the exit.
@@ -3190,83 +3677,45 @@ dhcp6_state_changed (NMDHCPClient *client,
 		if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_OTHERCONF)
 			break;
 		/* Otherwise, fall through */
-	case DHC_FAIL: /* all attempts to contact server timed out, sleeping */
-	case DHC_ABEND: /* dhclient exited abnormally */
-		/* dhclient quit and can't get/renew a lease; so kill the connection */
-		dhcp6_fail (device, FALSE);
+	case NM_DHCP_STATE_FAIL:
+		dhcp6_fail (self, FALSE);
 		break;
 	default:
 		break;
 	}
 }
 
-static void
-dhcp6_timeout (NMDHCPClient *client, gpointer user_data)
-{
-	NMDevice *device = NM_DEVICE (user_data);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
-
-	g_return_if_fail (nm_device_get_act_request (device) != NULL);
-	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE);
-
-	nm_dhcp_client_stop (client, FALSE);
-	if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED)
-		dhcp6_fail (device, TRUE);
-	else {
-		/* not a hard failure; just live with the RA info */
-		nm_dhcp6_config_reset (priv->dhcp6_config);
-		if (priv->dhcp6_ip6_config)
-			g_object_unref (priv->dhcp6_ip6_config);
-		priv->dhcp6_ip6_config = NULL;
-
-		if (priv->ip6_state == IP_CONF)
-			nm_device_activate_schedule_ip6_config_result (device);
-	}
-}
-
-static NMActStageReturn
-dhcp6_start (NMDevice *self,
-             NMConnection *connection,
-             guint32 dhcp_opt,
-             NMDeviceStateReason *reason)
+static gboolean
+dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 {
-	NMSettingIP6Config *s_ip6;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMSettingIPConfig *s_ip6;
 	GByteArray *tmp = NULL;
+	const guint8 *hw_addr;
+	size_t hw_addr_len = 0;
 
-	if (!connection) {
-		connection = nm_device_get_connection (self);
-		g_assert (connection);
-	}
-
-	/* Begin a DHCP transaction on the interface */
-
-	/* Clear old exported DHCP options */
-	if (priv->dhcp6_config)
-		g_object_unref (priv->dhcp6_config);
-	priv->dhcp6_config = nm_dhcp6_config_new ();
-
-	g_warn_if_fail (priv->dhcp6_ip6_config == NULL);
-	if (priv->dhcp6_ip6_config) {
-		g_object_unref (priv->dhcp6_ip6_config);
-		priv->dhcp6_ip6_config = NULL;
-	}
+	g_assert (connection);
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	g_assert (s_ip6);
 
-	if (priv->hw_addr_len) {
-		tmp = g_byte_array_sized_new (priv->hw_addr_len);
-		g_byte_array_append (tmp, priv->hw_addr, priv->hw_addr_len);
+	hw_addr = nm_platform_link_get_address (nm_device_get_ip_ifindex (self), &hw_addr_len);
+	if (hw_addr_len) {
+		tmp = g_byte_array_sized_new (hw_addr_len);
+		g_byte_array_append (tmp, hw_addr, hw_addr_len);
 	}
 
 	priv->dhcp6_client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
 	                                                nm_device_get_ip_iface (self),
+	                                                nm_device_get_ip_ifindex (self),
 	                                                tmp,
 	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_priority (self),
-	                                                nm_connection_get_setting_ip6_config (connection),
+	                                                nm_device_get_ip6_route_metric (self),
+	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
+	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip6),
 	                                                priv->dhcp_timeout,
 	                                                priv->dhcp_anycast_address,
-	                                                (dhcp_opt == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE);
+	                                                (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
+	                                                nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6)));
 	if (tmp)
 		g_byte_array_free (tmp, TRUE);
 
@@ -3275,65 +3724,85 @@ dhcp6_start (NMDevice *self,
 		                                            NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
 		                                            G_CALLBACK (dhcp6_state_changed),
 		                                            self);
-		priv->dhcp6_timeout_sigid = g_signal_connect (priv->dhcp6_client,
-		                                              NM_DHCP_CLIENT_SIGNAL_TIMEOUT,
-		                                              G_CALLBACK (dhcp6_timeout),
-		                                              self);
+	}
 
-		s_ip6 = nm_connection_get_setting_ip6_config (connection);
-		if (!nm_setting_ip6_config_get_may_fail (s_ip6) ||
-		    !strcmp (nm_setting_ip6_config_get_method (s_ip6), NM_SETTING_IP6_CONFIG_METHOD_DHCP))
-			nm_device_add_pending_action (self, PENDING_ACTION_DHCP6, TRUE);
+	return !!priv->dhcp6_client;
+}
 
-		/* DHCP devices will be notified by the DHCP manager when stuff happens */
-		ret = NM_ACT_STAGE_RETURN_POSTPONE;
-	} else {
+static gboolean
+dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip6;
+
+	g_clear_object (&priv->dhcp6_config);
+	priv->dhcp6_config = nm_dhcp6_config_new ();
+
+	g_warn_if_fail (priv->dhcp6_ip6_config == NULL);
+	g_clear_object (&priv->dhcp6_ip6_config);
+
+	connection = nm_device_get_connection (self);
+	g_assert (connection);
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	if (!nm_setting_ip_config_get_may_fail (s_ip6) ||
+	    !strcmp (nm_setting_ip_config_get_method (s_ip6), NM_SETTING_IP6_CONFIG_METHOD_DHCP))
+		nm_device_add_pending_action (self, PENDING_ACTION_DHCP6, TRUE);
+
+	if (wait_for_ll) {
+		NMActStageReturn ret;
+
+		/* ensure link local is ready... */
+		ret = linklocal6_start (self);
+		if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
+			/* success; wait for the LL address to show up */
+			return TRUE;
+		}
+
+		/* success; already have the LL address; kick off DHCP */
+		g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
+	}
+
+	if (!dhcp6_start_with_link_ready (self, connection)) {
 		*reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED;
-		ret = NM_ACT_STAGE_RETURN_FAILURE;
+		return FALSE;
 	}
 
-	return ret;
+	return TRUE;
 }
 
 gboolean
 nm_device_dhcp6_renew (NMDevice *self, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActStageReturn ret;
-	NMDeviceStateReason reason;
-	NMConnection *connection;
 
 	g_return_val_if_fail (priv->dhcp6_client != NULL, FALSE);
 
-	nm_log_info (LOGD_DHCP6, "(%s): DHCPv6 lease renewal requested",
-	             nm_device_get_iface (self));
+	_LOGI (LOGD_DHCP6, "DHCPv6 lease renewal requested");
 
 	/* Terminate old DHCP instance and release the old lease */
 	dhcp6_cleanup (self, TRUE, release);
 
-	connection = nm_device_get_connection (self);
-	g_assert (connection);
-
 	/* Start DHCP again on the interface */
-	ret = dhcp6_start (self, connection, priv->dhcp6_mode, &reason);
-
-	return (ret != NM_ACT_STAGE_RETURN_FAILURE);
+	return dhcp6_start (self, FALSE, NULL);
 }
 
 /******************************************/
 
 static gboolean
-linklocal6_config_is_ready (const NMIP6Config *ip6_config)
+have_ip6_address (const NMIP6Config *ip6_config, gboolean linklocal)
 {
-	int i;
+	guint i;
 
 	if (!ip6_config)
 		return FALSE;
 
+	linklocal = !!linklocal;
+
 	for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) {
 		const NMPlatformIP6Address *addr = nm_ip6_config_get_address (ip6_config, i);
 
-		if (IN6_IS_ADDR_LINKLOCAL (&addr->address) &&
+		if ((IN6_IS_ADDR_LINKLOCAL (&addr->address) == linklocal) &&
 		    !(addr->flags & IFA_F_TENTATIVE))
 			return TRUE;
 	}
@@ -3359,8 +3828,7 @@ linklocal6_timeout_cb (gpointer user_data)
 
 	linklocal6_cleanup (self);
 
-	nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: waiting for link-local addresses failed due to timeout",
-	             nm_device_get_iface (self));
+	_LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses failed due to timeout");
 
 	nm_device_activate_schedule_ip6_config_timeout (self);
 	return G_SOURCE_REMOVE;
@@ -3374,7 +3842,7 @@ linklocal6_complete (NMDevice *self)
 	const char *method;
 
 	g_assert (priv->linklocal6_timeout_id);
-	g_assert (linklocal6_config_is_ready (priv->ip6_config));
+	g_assert (have_ip6_address (priv->ip6_config, TRUE));
 
 	linklocal6_cleanup (self);
 
@@ -3383,17 +3851,70 @@ linklocal6_complete (NMDevice *self)
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 
-	nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: waiting for link-local addresses successful, continue with method %s",
-	             nm_device_get_iface (self), method);
+	_LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses successful, continue with method %s", method);
 
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0)
-		addrconf6_start_with_link_ready (self);
-	else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0)
+	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
+		if (!addrconf6_start_with_link_ready (self)) {
+			/* Time out IPv6 instead of failing the entire activation */
+			nm_device_activate_schedule_ip6_config_timeout (self);
+		}
+	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
+		if (!dhcp6_start_with_link_ready (self, connection)) {
+			/* Time out IPv6 instead of failing the entire activation */
+			nm_device_activate_schedule_ip6_config_timeout (self);
+		}
+	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0)
 		nm_device_activate_schedule_ip6_config_result (self);
 	else
 		g_return_if_fail (FALSE);
 }
 
+static void
+check_and_add_ipv6ll_addr (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	int ip_ifindex = nm_device_get_ip_ifindex (self);
+	NMUtilsIPv6IfaceId iid;
+	struct in6_addr lladdr;
+	guint i, n;
+
+	if (priv->nm_ipv6ll == FALSE)
+		return;
+
+	if (priv->ip6_config) {
+		n = nm_ip6_config_get_num_addresses (priv->ip6_config);
+		for (i = 0; i < n; i++) {
+			const NMPlatformIP6Address *addr;
+
+			addr = nm_ip6_config_get_address (priv->ip6_config, i);
+			if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) {
+				/* Already have an LL address, nothing to do */
+				return;
+			}
+		}
+	}
+
+	if (!nm_device_get_ip_iface_identifier (self, &iid)) {
+		_LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 may be broken");
+		return;
+	}
+
+	memset (&lladdr, 0, sizeof (lladdr));
+	lladdr.s6_addr16[0] = htons (0xfe80);
+	nm_utils_ipv6_addr_set_interface_identfier (&lladdr, iid);
+	_LOGD (LOGD_IP6, "adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL));
+	if (!nm_platform_ip6_address_add (ip_ifindex,
+	                                  lladdr,
+	                                  in6addr_any,
+	                                  64,
+	                                  NM_PLATFORM_LIFETIME_PERMANENT,
+	                                  NM_PLATFORM_LIFETIME_PERMANENT,
+	                                  0)) {
+		_LOGW (LOGD_IP6, "failed to add IPv6 link-local address %s",
+		       nm_utils_inet6_ntop (&lladdr, NULL));
+	}
+}
+
 static NMActStageReturn
 linklocal6_start (NMDevice *self)
 {
@@ -3403,15 +3924,16 @@ linklocal6_start (NMDevice *self)
 
 	linklocal6_cleanup (self);
 
-	if (linklocal6_config_is_ready (priv->ip6_config))
+	if (have_ip6_address (priv->ip6_config, TRUE))
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
 	connection = nm_device_get_connection (self);
 	g_assert (connection);
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.",
-	            nm_device_get_iface (self), method);
+	_LOGD (LOGD_DEVICE, "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.", method);
+
+	check_and_add_ipv6ll_addr (self);
 
 	priv->linklocal6_timeout_id = g_timeout_add_seconds (5, linklocal6_timeout_cb, self);
 
@@ -3468,12 +3990,10 @@ print_support_extended_ifa_flags (NMSettingIP6ConfigPrivacy use_tempaddr)
 }
 
 static void
-rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *device)
+rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
-	NMConnection *connection;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int i;
-	NMDeviceStateReason reason;
 	static int system_support = -1;
 	guint ifa_flags = 0x00;
 
@@ -3499,8 +4019,6 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *device
 	}
 
 	g_return_if_fail (priv->act_request);
-	connection = nm_device_get_connection (device);
-	g_assert (connection);
 
 	if (!priv->ac_ip6_config)
 		priv->ac_ip6_config = nm_ip6_config_new ();
@@ -3536,7 +4054,7 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *device
 			address.preferred = discovered_address->preferred;
 			if (address.preferred > address.lifetime)
 				address.preferred = address.lifetime;
-			address.source = NM_PLATFORM_SOURCE_RDISC;
+			address.source = NM_IP_CONFIG_SOURCE_RDISC;
 			address.flags = ifa_flags;
 
 			nm_ip6_config_add_address (priv->ac_ip6_config, &address);
@@ -3560,8 +4078,8 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *device
 				route.network = discovered_route->network;
 				route.plen = discovered_route->plen;
 				route.gateway = discovered_route->gateway;
-				route.source = NM_PLATFORM_SOURCE_RDISC;
-				route.metric = nm_device_get_priority (device);
+				route.source = NM_IP_CONFIG_SOURCE_RDISC;
+				route.metric = nm_device_get_ip6_route_metric (self);
 
 				nm_ip6_config_add_route (priv->ac_ip6_config, &route);
 			}
@@ -3591,39 +4109,99 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *device
 	}
 
 	if (changed & NM_RDISC_CONFIG_DHCP_LEVEL) {
-		dhcp6_cleanup (device, TRUE, TRUE);
+		dhcp6_cleanup (self, TRUE, TRUE);
 
 		priv->dhcp6_mode = rdisc->dhcp_level;
-
-		switch (priv->dhcp6_mode) {
-		case NM_RDISC_DHCP_LEVEL_NONE:
-			break;
-		default:
-			nm_log_info (LOGD_DEVICE | LOGD_DHCP6,
-			             "Activation (%s) Stage 3 of 5 (IP Configure Start) starting DHCPv6"
-			             " as requested by IPv6 router...",
-			             priv->iface);
-			switch (dhcp6_start (device, connection, priv->dhcp6_mode, &reason)) {
-			case NM_ACT_STAGE_RETURN_SUCCESS:
-				g_warn_if_reached ();
-				break;
-			case NM_ACT_STAGE_RETURN_POSTPONE:
-				return;
-			default:
-				nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
-				return;
+		if (priv->dhcp6_mode != NM_RDISC_DHCP_LEVEL_NONE) {
+			NMDeviceStateReason reason;
+
+			_LOGI (LOGD_DEVICE | LOGD_DHCP6,
+			       "Activation: Stage 3 of 5 (IP Configure Start) starting DHCPv6"
+			       " as requested by IPv6 router...");
+			if (!dhcp6_start (self, FALSE, &reason)) {
+				if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED)
+					nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 			}
+			return;
 		}
 	}
 
-	if (changed & NM_RDISC_CONFIG_HOP_LIMIT) {
+	/* hop_limit == 0 is a special value "unspecified", so do not touch
+	 * in this case */
+	if (changed & NM_RDISC_CONFIG_HOP_LIMIT && rdisc->hop_limit > 0) {
 		char val[16];
 
 		g_snprintf (val, sizeof (val), "%d", rdisc->hop_limit);
-		nm_device_ipv6_sysctl_set (device, "hop_limit", val);
+		nm_device_ipv6_sysctl_set (self, "hop_limit", val);
 	}
 
-	nm_device_activate_schedule_ip6_config_result (device);
+	if (changed & NM_RDISC_CONFIG_MTU) {
+		char val[16];
+
+		g_snprintf (val, sizeof (val), "%d", rdisc->mtu);
+		nm_device_ipv6_sysctl_set (self, "mtu", val);
+	}
+
+	nm_device_activate_schedule_ip6_config_result (self);
+}
+
+static void
+rdisc_ra_timeout (NMRDisc *rdisc, NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	/* We don't want to stop listening for router advertisements completely,
+	 * but instead let device activation continue activating.  If an RA
+	 * shows up later, we'll use it as long as the device is not disconnected.
+	 */
+
+	_LOGD (LOGD_IP6, "timed out waiting for IPv6 router advertisement");
+	if (priv->ip6_state == IP_CONF) {
+		/* If RA is our only source of addressing information and we don't
+		 * ever receive one, then time out IPv6.  But if there is other
+		 * IPv6 configuration, like manual IPv6 addresses or external IPv6
+		 * config, consider that sufficient for IPv6 success.
+		 */
+		if (have_ip6_address (priv->ip6_config, FALSE))
+			nm_device_activate_schedule_ip6_config_result (self);
+		else
+			nm_device_activate_schedule_ip6_config_timeout (self);
+	}
+}
+
+static gboolean
+addrconf6_start_with_link_ready (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMUtilsIPv6IfaceId iid;
+
+	g_assert (priv->rdisc);
+
+	if (!nm_device_get_ip_iface_identifier (self, &iid)) {
+		_LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 cannot continue");
+		return FALSE;
+	}
+	nm_rdisc_set_iid (priv->rdisc, iid);
+
+	/* Apply any manual configuration before starting RA */
+	if (!ip6_config_merge_and_apply (self, TRUE, NULL))
+		_LOGW (LOGD_IP6, "failed to apply manual IPv6 configuration");
+
+	nm_device_ipv6_sysctl_set (self, "accept_ra", "1");
+	nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
+	nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0");
+	nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0");
+
+	priv->rdisc_changed_id = g_signal_connect (priv->rdisc,
+	                                           NM_RDISC_CONFIG_CHANGED,
+	                                           G_CALLBACK (rdisc_config_changed),
+	                                           self);
+	priv->rdisc_timeout_id = g_signal_connect (priv->rdisc,
+	                                           NM_RDISC_RA_TIMEOUT,
+	                                           G_CALLBACK (rdisc_ra_timeout),
+	                                           self);
+	nm_rdisc_start (priv->rdisc);
+	return TRUE;
 }
 
 static gboolean
@@ -3645,46 +4223,26 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 
 	priv->rdisc = nm_lndp_rdisc_new (nm_device_get_ip_ifindex (self), ip_iface);
 	if (!priv->rdisc) {
-		nm_log_err (LOGD_IP6, "(%s): failed to start router discovery.", ip_iface);
+		_LOGE (LOGD_IP6, "failed to start router discovery (%s)", ip_iface);
 		return FALSE;
 	}
 
 	priv->rdisc_use_tempaddr = use_tempaddr;
 	print_support_extended_ifa_flags (use_tempaddr);
 
-	if (!nm_setting_ip6_config_get_may_fail (nm_connection_get_setting_ip6_config (connection)))
+	if (!nm_setting_ip_config_get_may_fail (nm_connection_get_setting_ip6_config (connection)))
 		nm_device_add_pending_action (self, PENDING_ACTION_AUTOCONF6, TRUE);
 
 	/* ensure link local is ready... */
 	ret = linklocal6_start (self);
-	if (ret == NM_ACT_STAGE_RETURN_SUCCESS)
-		addrconf6_start_with_link_ready (self);
-	else
-		g_return_val_if_fail (ret == NM_ACT_STAGE_RETURN_POSTPONE, TRUE);
-
-	return TRUE;
-}
-
-static void
-addrconf6_start_with_link_ready (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	g_assert (priv->rdisc);
-
-	/* FIXME: what if interface has no lladdr, like PPP? */
-	if (priv->hw_addr_len)
-		nm_rdisc_set_lladdr (priv->rdisc, (const char *) priv->hw_addr, priv->hw_addr_len);
-
-	nm_device_ipv6_sysctl_set (self, "accept_ra", "1");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0");
-
-	priv->rdisc_config_changed_sigid = g_signal_connect (priv->rdisc, NM_RDISC_CONFIG_CHANGED,
-	                                                     G_CALLBACK (rdisc_config_changed), self);
+	if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
+		/* success; wait for the LL address to show up */
+		return TRUE;
+	}
 
-	nm_rdisc_start (priv->rdisc);
+	/* success; already have the LL address; kick off router discovery */
+	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
+	return addrconf6_start_with_link_ready (self);
 }
 
 static void
@@ -3692,10 +4250,14 @@ addrconf6_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->rdisc_config_changed_sigid) {
-		g_signal_handler_disconnect (priv->rdisc,
-		                             priv->rdisc_config_changed_sigid);
-		priv->rdisc_config_changed_sigid = 0;
+	if (priv->rdisc_changed_id) {
+		g_signal_handler_disconnect (priv->rdisc, priv->rdisc_changed_id);
+		priv->rdisc_changed_id = 0;
+	}
+
+	if (priv->rdisc_timeout_id) {
+		g_signal_handler_disconnect (priv->rdisc, priv->rdisc_timeout_id);
+		priv->rdisc_timeout_id = 0;
 	}
 
 	nm_device_remove_pending_action (self, PENDING_ACTION_AUTOCONF6, FALSE);
@@ -3744,8 +4306,53 @@ restore_ip6_properties (NMDevice *self)
 	gpointer key, value;
 
 	g_hash_table_iter_init (&iter, priv->ip6_saved_properties);
-	while (g_hash_table_iter_next (&iter, &key, &value))
+	while (g_hash_table_iter_next (&iter, &key, &value)) {
+		/* Don't touch "disable_ipv6" if we're doing userland IPv6LL */
+		if (priv->nm_ipv6ll && strcmp (key, "disable_ipv6") == 0)
+			continue;
 		nm_device_ipv6_sysctl_set (self, key, value);
+	}
+}
+
+static inline void
+set_disable_ipv6 (NMDevice *self, const char *value)
+{
+	/* We only touch disable_ipv6 when NM is not managing the IPv6LL address */
+	if (NM_DEVICE_GET_PRIVATE (self)->nm_ipv6ll == FALSE)
+		nm_device_ipv6_sysctl_set (self, "disable_ipv6", value);
+}
+
+static inline void
+set_nm_ipv6ll (NMDevice *self, gboolean enable)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	int ifindex = nm_device_get_ip_ifindex (self);
+	const char *iface = nm_device_get_ip_iface (self);
+	char *value;
+
+	if (!nm_platform_check_support_user_ipv6ll ())
+		return;
+
+	priv->nm_ipv6ll = enable;
+	if (ifindex > 0) {
+		const char *detail = enable ? "enable" : "disable";
+
+		_LOGD (LOGD_IP6, "will %s userland IPv6LL", detail);
+		if (  !nm_platform_link_set_user_ipv6ll_enabled (ifindex, enable)
+		   && nm_platform_get_error () != NM_PLATFORM_ERROR_NOT_FOUND)
+			_LOGW (LOGD_IP6, "failed to %s userspace IPv6LL address handling", detail);
+
+		if (enable) {
+			/* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */
+			value = nm_platform_sysctl_get (nm_utils_ip6_property_path (iface, "disable_ipv6"));
+			if (g_strcmp0 (value, "0") == 0) {
+				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
+				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "0");
+			}
+			g_free (value);
+		}
+
+	}
 }
 
 static NMSettingIP6ConfigPrivacy
@@ -3838,15 +4445,11 @@ act_stage3_ip6_config_start (NMDevice *self,
 	connection = nm_device_get_connection (self);
 	g_assert (connection);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	if (priv->master)
-		g_assert_cmpstr (method, ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
-
-	if (   strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) != 0
+	if (   connection_ip4_method_requires_carrier (connection, NULL)
 	    && priv->is_master
 	    && !priv->carrier) {
-		nm_log_info (LOGD_IP6 | LOGD_DEVICE,
-		             "(%s): IPv6 config waiting until carrier is on", ip_iface);
+		_LOGI (LOGD_IP6 | LOGD_DEVICE,
+		       "IPv6 config waiting until carrier is on");
 		return NM_ACT_STAGE_RETURN_WAIT;
 	}
 
@@ -3859,23 +4462,41 @@ act_stage3_ip6_config_start (NMDevice *self,
 		g_slist_free (slaves);
 
 		if (ready_slaves == FALSE) {
-			nm_log_info (LOGD_DEVICE | LOGD_IP6,
-			             "(%s): IPv6 config waiting until slaves are ready",
-			             ip_iface);
+			_LOGI (LOGD_DEVICE | LOGD_IP6,
+			       "IPv6 config waiting until slaves are ready");
 			return NM_ACT_STAGE_RETURN_WAIT;
 		}
 	}
 
 	priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE;
 
+	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
-		if (!priv->master)
+		if (!priv->master) {
+			gboolean old_nm_ipv6ll = priv->nm_ipv6ll;
+
+			/* When activating an IPv6 'ignore' connection we need to revert back
+			 * to kernel IPv6LL, but the kernel won't actually assign an address
+			 * to the interface until disable_ipv6 is bounced.
+			 */
+			set_nm_ipv6ll (self, FALSE);
+			if (old_nm_ipv6ll == TRUE)
+				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
 			restore_ip6_properties (self);
+		}
 		return NM_ACT_STAGE_RETURN_STOP;
 	}
 
+	/* Any method past this point requires an IPv6LL address. Use NM-controlled
+	 * IPv6LL if this is not an assumed connection, since assumed connections
+	 * will already have IPv6 set up.
+	 */
+	if (!nm_device_uses_assumed_connection (self))
+		set_nm_ipv6ll (self, TRUE);
+
 	/* Re-enable IPv6 on the interface */
-	nm_device_ipv6_sysctl_set (self, "disable_ipv6", "0");
+	set_disable_ipv6 (self, "0");
 
 	/* Enable/disable IPv6 Privacy Extensions.
 	 * If a global value is configured by sysadmin (e.g. /etc/sysctl.conf),
@@ -3883,10 +4504,10 @@ act_stage3_ip6_config_start (NMDevice *self,
 	 */
 	ip6_privacy = ip6_use_tempaddr ();
 	if (ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) {
-		NMSettingIP6Config *s_ip6 = nm_connection_get_setting_ip6_config (connection);
+		NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection);
 
 		if (s_ip6)
-			ip6_privacy = nm_setting_ip6_config_get_ip6_privacy (s_ip6);
+			ip6_privacy = nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6));
 	}
 	ip6_privacy = use_tempaddr_clamp (ip6_privacy);
 
@@ -3905,17 +4526,19 @@ act_stage3_ip6_config_start (NMDevice *self,
 		}
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
 		priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_MANAGED;
-		ret = dhcp6_start (self, connection, priv->dhcp6_mode, reason);
+		if (!dhcp6_start (self, TRUE, reason)) {
+			/* IPv6 might be disabled; allow IPv4 to proceed */
+			ret = NM_ACT_STAGE_RETURN_STOP;
+		} else
+			ret = NM_ACT_STAGE_RETURN_POSTPONE;
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) == 0) {
 		/* New blank config */
 		*out_config = nm_ip6_config_new ();
 		g_assert (*out_config);
 
 		ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	} else {
-		nm_log_warn (LOGD_IP6, "(%s): unhandled IPv6 config method '%s'; will fail",
-		             nm_device_get_ip_iface (self), method);
-	}
+	} else
+		_LOGW (LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
 
 	/* Other methods (shared) aren't implemented yet */
 
@@ -4015,6 +4638,42 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 }
 
 /*
+ * nm_device_check_ip_failed
+ *
+ * Progress the device to appropriate state if both IPv4 and IPv6 failed
+ */
+static void
+nm_device_check_ip_failed (NMDevice *self, gboolean may_fail)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceState state;
+
+	if (   priv->ip4_state != IP_FAIL
+	    || priv->ip6_state != IP_FAIL)
+		return;
+
+	if (nm_device_uses_assumed_connection (self)) {
+		/* We have assumed configuration, but couldn't
+		 * redo it. No problem, move to check state. */
+		priv->ip4_state = priv->ip6_state = IP_DONE;
+		state = NM_DEVICE_STATE_IP_CHECK;
+	} else if (   may_fail
+	           && get_ip_config_may_fail (self, AF_INET)
+	           && get_ip_config_may_fail (self, AF_INET6)) {
+		/* Couldn't start either IPv6 and IPv4 autoconfiguration,
+		 * but both are allowed to fail. */
+		state = NM_DEVICE_STATE_SECONDARIES;
+	} else {
+		/* Autoconfiguration attempted without success. */
+		state = NM_DEVICE_STATE_FAILED;
+	}
+
+	nm_device_state_changed (self,
+	                         state,
+	                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+}
+
+/*
  * nm_device_activate_stage3_ip_config_start
  *
  * Begin automatic/manual IP configuration
@@ -4025,7 +4684,6 @@ 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;
 	NMActiveConnection *master;
 	NMDevice *master_device;
 
@@ -4034,15 +4692,12 @@ nm_device_activate_stage3_ip_config_start (gpointer user_data)
 
 	priv->ip4_state = priv->ip6_state = IP_WAIT;
 
-	iface = nm_device_get_iface (self);
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) started...", iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) started...");
 	nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
 	/* Device should be up before we can do anything with it */
-	if (!nm_platform_link_is_up (nm_device_get_ip_ifindex (self))) {
-		nm_log_warn (LOGD_DEVICE, "(%s): interface %s not up for IP configuration",
-		             iface, nm_device_get_ip_iface (self));
-	}
+	if (!nm_platform_link_is_up (nm_device_get_ip_ifindex (self)))
+		_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 
 	/* 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
@@ -4057,10 +4712,9 @@ nm_device_activate_stage3_ip_config_start (gpointer user_data)
 	if (master) {
 		master_device = nm_active_connection_get_device (master);
 		if (priv->ip4_state == IP_WAIT && priv->ip6_state == IP_WAIT) {
-			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)),
-			             master_device ? nm_device_get_iface (master_device) : "(unknown)");
+			_LOGI (LOGD_DEVICE, "Activation: connection '%s' waiting on master '%s'",
+			       nm_connection_get_id (nm_device_get_connection (self)),
+			       master_device ? nm_device_get_iface (master_device) : "(unknown)");
 		}
 		goto out;
 	}
@@ -4073,13 +4727,10 @@ nm_device_activate_stage3_ip_config_start (gpointer user_data)
 	if (!nm_device_activate_stage3_ip6_start (self))
 		goto out;
 
-	if (priv->ip4_state == IP_FAIL && priv->ip6_state == IP_FAIL) {
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED,
-		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-	}
+	nm_device_check_ip_failed (self, TRUE);
 
 out:
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) complete.", iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) complete.");
 	return FALSE;
 }
 
@@ -4087,8 +4738,14 @@ out:
 static void
 fw_change_zone_cb (GError *error, gpointer user_data)
 {
-	NMDevice *self = NM_DEVICE (user_data);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevice *self;
+	NMDevicePrivate *priv;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_DEVICE (user_data);
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	priv->fw_call = NULL;
 
@@ -4097,9 +4754,7 @@ fw_change_zone_cb (GError *error, gpointer user_data)
 	}
 
 	activation_source_schedule (self, nm_device_activate_stage3_ip_config_start, 0);
-
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) scheduled.",
-	             nm_device_get_iface (self));
+	_LOGI (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) scheduled.");
 }
 
 /*
@@ -4120,15 +4775,24 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	g_return_if_fail (priv->act_request);
 
+	g_return_if_fail (!priv->fw_call);
+
 	/* Add the interface to the specified firewall zone */
 	connection = nm_device_get_connection (self);
 	g_assert (connection);
 	s_con = nm_connection_get_setting_connection (connection);
 
 	zone = nm_setting_connection_get_zone (s_con);
-	nm_log_dbg (LOGD_DEVICE, "Activation (%s) setting firewall zone '%s'",
-	            nm_device_get_iface (self), zone ? zone : "default");
-	priv->fw_call = nm_firewall_manager_add_or_change_zone (priv->fw_manager,
+
+	if (nm_device_uses_assumed_connection (self)) {
+		_LOGD (LOGD_DEVICE, "Activation: skip setting firewall zone '%s' for assumed device", zone ? zone : "default");
+		activation_source_schedule (self, nm_device_activate_stage3_ip_config_start, 0);
+		_LOGI (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) scheduled.");
+		return;
+	}
+
+	_LOGD (LOGD_DEVICE, "Activation: setting firewall zone '%s'", zone ? zone : "default");
+	priv->fw_call = nm_firewall_manager_add_or_change_zone (nm_firewall_manager_get (),
 	                                                        nm_device_get_ip_iface (self),
 	                                                        zone,
 	                                                        FALSE,
@@ -4139,7 +4803,7 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 static NMActStageReturn
 act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
 {
-	if (nm_device_ip_config_should_fail (self, FALSE)) {
+	if (!get_ip_config_may_fail (self, AF_INET)) {
 		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
@@ -4158,17 +4822,14 @@ nm_device_activate_ip4_config_timeout (gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *iface;
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 
 	/* Clear the activation source ID now that this stage has run */
 	activation_source_clear (self, FALSE, AF_INET);
 
-	iface = nm_device_get_iface (self);
-	nm_log_info (LOGD_DEVICE | LOGD_IP4,
-	             "Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) started...",
-	             iface);
+	_LOGI (LOGD_DEVICE | LOGD_IP4,
+	       "Activation: Stage 4 of 5 (IPv4 Configure Timeout) started...");
 
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip4_config_timeout (self, &reason);
 	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
@@ -4181,16 +4842,11 @@ nm_device_activate_ip4_config_timeout (gpointer user_data)
 
 	priv->ip4_state = IP_FAIL;
 
-	/* If IPv4 failed and IPv6 failed, the activation fails */
-	if (priv->ip6_state == IP_FAIL)
-		nm_device_state_changed (self,
-		                         NM_DEVICE_STATE_FAILED,
-		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+	nm_device_check_ip_failed (self, FALSE);
 
 out:
-	nm_log_info (LOGD_DEVICE | LOGD_IP4,
-	             "Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) complete.",
-	             iface);
+	_LOGI (LOGD_DEVICE | LOGD_IP4,
+	       "Activation: Stage 4 of 5 (IPv4 Configure Timeout) complete.");
 	return FALSE;
 }
 
@@ -4213,16 +4869,15 @@ nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
 
 	activation_source_schedule (self, nm_device_activate_ip4_config_timeout, AF_INET);
 
-	nm_log_info (LOGD_DEVICE | LOGD_IP4,
-	             "Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) scheduled...",
-	             nm_device_get_iface (self));
+	_LOGI (LOGD_DEVICE | LOGD_IP4,
+	       "Activation: Stage 4 of 5 (IPv4 Configure Timeout) scheduled...");
 }
 
 
 static NMActStageReturn
 act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
 {
-	if (nm_device_ip_config_should_fail (self, TRUE)) {
+	if (!get_ip_config_may_fail (self, AF_INET6)) {
 		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
@@ -4242,17 +4897,14 @@ nm_device_activate_ip6_config_timeout (gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *iface;
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 
 	/* Clear the activation source ID now that this stage has run */
 	activation_source_clear (self, FALSE, AF_INET6);
 
-	iface = nm_device_get_iface (self);
-	nm_log_info (LOGD_DEVICE | LOGD_IP6,
-	             "Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) started...",
-	             iface);
+	_LOGI (LOGD_DEVICE | LOGD_IP6,
+	       "Activation: Stage 4 of 5 (IPv6 Configure Timeout) started...");
 
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip6_config_timeout (self, &reason);
 	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
@@ -4265,16 +4917,11 @@ nm_device_activate_ip6_config_timeout (gpointer user_data)
 
 	priv->ip6_state = IP_FAIL;
 
-	/* If IPv6 failed and IPv4 failed, the activation fails */
-	if (priv->ip4_state == IP_FAIL)
-		nm_device_state_changed (self,
-		                         NM_DEVICE_STATE_FAILED,
-		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+	nm_device_check_ip_failed (self, FALSE);
 
 out:
-	nm_log_info (LOGD_DEVICE | LOGD_IP6,
-	             "Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) complete.",
-	             iface);
+	_LOGI (LOGD_DEVICE | LOGD_IP6,
+	       "Activation: Stage 4 of 5 (IPv6 Configure Timeout) complete.");
 	return FALSE;
 }
 
@@ -4297,9 +4944,8 @@ nm_device_activate_schedule_ip6_config_timeout (NMDevice *self)
 
 	activation_source_schedule (self, nm_device_activate_ip6_config_timeout, AF_INET6);
 
-	nm_log_info (LOGD_DEVICE | LOGD_IP6,
-	             "Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) scheduled...",
-	             nm_device_get_iface (self));
+	_LOGI (LOGD_DEVICE | LOGD_IP6,
+	       "Activation: Stage 4 of 5 (IPv6 Configure Timeout) scheduled...");
 }
 
 static void
@@ -4320,16 +4966,19 @@ share_init (void)
 	                    "nf_nat_sip", "nf_nat_tftp", "nf_nat_pptp", "nf_nat_h323",
 	                    NULL };
 	char **iter;
+	int errsv;
 
 	if (!nm_platform_sysctl_set ("/proc/sys/net/ipv4/ip_forward", "1")) {
-		nm_log_err (LOGD_SHARING, "Error starting IP forwarding: (%d) %s",
-					errno, strerror (errno));
+		errsv = errno;
+		nm_log_err (LOGD_SHARING, "share: error starting IP forwarding: (%d) %s",
+		            errsv, strerror (errsv));
 		return FALSE;
 	}
 
 	if (!nm_platform_sysctl_set ("/proc/sys/net/ipv4/ip_dynaddr", "1")) {
-		nm_log_err (LOGD_SHARING, "error starting IP forwarding: (%d) %s",
-					errno, strerror (errno));
+		errsv = errno;
+		nm_log_err (LOGD_SHARING, "share: error starting IP forwarding: (%d) %s",
+		            errsv, strerror (errsv));
 	}
 
 	for (iter = modules; *iter; iter++) {
@@ -4339,7 +4988,7 @@ share_init (void)
 
 		if (!g_spawn_sync ("/", argv, envp, G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
 		                   share_child_setup, NULL, NULL, NULL, &status, &error)) {
-			nm_log_err (LOGD_SHARING, "error loading NAT module %s: (%d) %s",
+			nm_log_err (LOGD_SHARING, "share: error loading NAT module %s: (%d) %s",
 			            *iter, error ? error->code : 0,
 			            (error && error->message) ? error->message : "unknown");
 			if (error)
@@ -4412,9 +5061,8 @@ start_sharing (NMDevice *self, NMIP4Config *config)
 	nm_act_request_set_shared (req, TRUE);
 
 	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 && error->message) ? error->message : "(unknown)");
+		_LOGE (LOGD_SHARING, "share: (%s) failed to start dnsmasq: %s",
+		       ip_iface, (error && error->message) ? error->message : "(unknown)");
 		g_error_free (error);
 		nm_act_request_set_shared (req, FALSE);
 		return FALSE;
@@ -4429,13 +5077,12 @@ start_sharing (NMDevice *self, NMIP4Config *config)
 static void
 send_arps (NMDevice *self, const char *mode_arg)
 {
-	const char *argv[] = { "/sbin/arping", mode_arg, "-q", "-I", nm_device_get_ip_iface (self), "-c", "1", NULL, NULL };
+	const char *argv[] = { NULL, mode_arg, "-q", "-I", nm_device_get_ip_iface (self), "-c", "1", NULL, NULL };
 	int ip_arg = G_N_ELEMENTS (argv) - 2;
 	NMConnection *connection;
-	NMSettingIP4Config *s_ip4;
+	NMSettingIPConfig *s_ip4;
 	int i, num;
-	NMIP4Address *addr;
-	guint32 ipaddr;
+	NMIPAddress *addr;
 	GError *error = NULL;
 
 	connection = nm_device_get_connection (self);
@@ -4444,24 +5091,33 @@ send_arps (NMDevice *self, const char *mode_arg)
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	if (!s_ip4)
 		return;
-	num = nm_setting_ip4_config_get_num_addresses (s_ip4);
+	num = nm_setting_ip_config_get_num_addresses (s_ip4);
+	if (num == 0)
+		return;
+
+	argv[0] = nm_utils_find_helper ("arping", NULL, NULL);
+	if (!argv[0]) {
+		_LOGW (LOGD_DEVICE | LOGD_IP4, "arping could not be found; no ARPs will be sent");
+		return;
+	}
 
 	for (i = 0; i < num; i++) {
-		addr = nm_setting_ip4_config_get_address (s_ip4, i);
-		ipaddr = nm_ip4_address_get_address (addr);
-		argv[ip_arg] = (char *) nm_utils_inet4_ntop (ipaddr, NULL);
-
-		nm_log_dbg (LOGD_DEVICE | LOGD_IP4,
-		            "Running arping %s -I %s %s",
-		            mode_arg, nm_device_get_iface (self), argv[ip_arg]);
-		g_spawn_async (NULL, (char **) argv, NULL,
-		               G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
-		               nm_unblock_posix_signals,
-		               NULL, NULL, &error);
-		if (error) {
-			nm_log_warn (LOGD_DEVICE | LOGD_IP4,
-			             "Could not send ARP for local address %s: %s",
-			             argv[ip_arg], error->message);
+		gs_free char *tmp_str = NULL;
+		gboolean success;
+
+		addr = nm_setting_ip_config_get_address (s_ip4, i);
+		argv[ip_arg] = nm_ip_address_get_address (addr);
+
+		_LOGD (LOGD_DEVICE | LOGD_IP4,
+		       "arping: run %s", (tmp_str = g_strjoinv (" ", (char **) argv)));
+		success = g_spawn_async (NULL, (char **) argv, NULL,
+		                         G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+		                         nm_unblock_posix_signals,
+		                         NULL, NULL, &error);
+		if (!success) {
+			_LOGW (LOGD_DEVICE | LOGD_IP4,
+			       "arping: could not send ARP for local address %s: %s",
+			       argv[ip_arg], error->message);
 			g_clear_error (&error);
 		}
 	}
@@ -4497,7 +5153,7 @@ arp_announce (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
-	NMSettingIP4Config *s_ip4;
+	NMSettingIPConfig *s_ip4;
 	int num;
 
 	arp_cleanup (self);
@@ -4511,7 +5167,7 @@ arp_announce (NMDevice *self)
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	if (!s_ip4)
 		return;
-	num = nm_setting_ip4_config_get_num_addresses (s_ip4);
+	num = nm_setting_ip_config_get_num_addresses (s_ip4);
 	if (num == 0)
 		return;
 
@@ -4525,33 +5181,33 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
-	const char *iface, *method;
+	const char *method;
 	NMConnection *connection;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	int ip_ifindex;
 
 	/* Clear the activation source ID now that this stage has run */
 	activation_source_clear (self, FALSE, AF_INET);
 
-	iface = nm_device_get_iface (self);
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv4 Commit) started...",
-	             iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv4 Commit) started...");
 
 	req = nm_device_get_act_request (self);
 	g_assert (req);
 	connection = nm_act_request_get_connection (req);
 	g_assert (connection);
 
-	/* Device should be up before we can do anything with it */
-	if (!nm_platform_link_is_up (nm_device_get_ip_ifindex (self))) {
-		nm_log_warn (LOGD_DEVICE, "(%s): interface %s not up for IP configuration",
-		             iface, nm_device_get_ip_iface (self));
+	/* Interface must be IFF_UP before IP config can be applied */
+	ip_ifindex = nm_device_get_ip_ifindex (self);
+	if (!nm_platform_link_is_up (ip_ifindex) && !nm_device_uses_assumed_connection (self)) {
+		nm_platform_link_set_up (ip_ifindex);
+		if (!nm_platform_link_is_up (ip_ifindex))
+			_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 	}
 
 	/* NULL to use the existing priv->dev_ip4_config */
 	if (!ip4_config_merge_and_apply (self, NULL, TRUE, &reason)) {
-		nm_log_info (LOGD_DEVICE | LOGD_IP4,
-			         "Activation (%s) Stage 5 of 5 (IPv4 Commit) failed",
-					 iface);
+		_LOGI (LOGD_DEVICE | LOGD_IP4,
+		       "Activation: Stage 5 of 5 (IPv4 Commit) failed");
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 		goto out;
 	}
@@ -4561,7 +5217,7 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
 		if (!start_sharing (self, priv->ip4_config)) {
-			nm_log_warn (LOGD_SHARING, "Activation (%s) Stage 5 of 5 (IPv4 Commit) start sharing failed.", iface);
+			_LOGW (LOGD_SHARING, "Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed.");
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
 			goto out;
 		}
@@ -4593,8 +5249,7 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 
 out:
-	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv4 Commit) complete.",
-	             iface);
+	_LOGI (LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv4 Commit) complete.");
 
 	return FALSE;
 }
@@ -4605,17 +5260,16 @@ nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *conf
 	NMDevicePrivate *priv;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
-	g_return_if_fail (NM_IS_IP4_CONFIG (config));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	g_clear_object (&priv->dev_ip4_config);
-	priv->dev_ip4_config = g_object_ref (config);
+	if (config)
+		priv->dev_ip4_config = g_object_ref (config);
 
 	activation_source_schedule (self, nm_device_activate_ip4_config_commit, AF_INET);
 
-	nm_log_info (LOGD_DEVICE | LOGD_IP4,
-		         "Activation (%s) Stage 5 of 5 (IPv4 Configure Commit) scheduled...",
-		         nm_device_get_iface (self));
+	_LOGI (LOGD_DEVICE | LOGD_IP4,
+	       "Activation: Stage 5 of 5 (IPv4 Configure Commit) scheduled...");
 }
 
 gboolean
@@ -4639,27 +5293,27 @@ nm_device_activate_ip6_config_commit (gpointer user_data)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	guint level = (priv->ip6_state == IP_DONE) ? LOGL_DEBUG : LOGL_INFO;
 	NMActRequest *req;
-	const char *iface;
 	NMConnection *connection;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	int ip_ifindex;
 
 	/* Clear the activation source ID now that this stage has run */
 	activation_source_clear (self, FALSE, AF_INET6);
 
-	iface = nm_device_get_iface (self);
-	nm_log (LOGD_DEVICE, level, "Activation (%s) Stage 5 of 5 (IPv6 Commit) started...", iface);
+	_LOG (level, LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv6 Commit) started...");
 
 	req = nm_device_get_act_request (self);
 	g_assert (req);
 	connection = nm_act_request_get_connection (req);
 	g_assert (connection);
 
-	/* Device should be up before we can do anything with it */
-	g_warn_if_fail (nm_platform_link_is_up (nm_device_get_ip_ifindex (self)));
-
-	/* Allow setting MTU etc */
-	if (NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit)
-		NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self);
+	/* Interface must be IFF_UP before IP config can be applied */
+	ip_ifindex = nm_device_get_ip_ifindex (self);
+	if (!nm_platform_link_is_up (ip_ifindex) && !nm_device_uses_assumed_connection (self)) {
+		nm_platform_link_set_up (ip_ifindex);
+		if (!nm_platform_link_is_up (ip_ifindex))
+			_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
+	}
 
 	if (ip6_config_merge_and_apply (self, TRUE, &reason)) {
 		/* If IPv6 wasn't the first IP to complete, and DHCP was used,
@@ -4686,13 +5340,12 @@ nm_device_activate_ip6_config_commit (gpointer user_data)
 		if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG)
 			nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 	} else {
-		nm_log_warn (LOGD_DEVICE | LOGD_IP6,
-			         "Activation (%s) Stage 5 of 5 (IPv6 Commit) failed",
-					 iface);
+		_LOGW (LOGD_DEVICE | LOGD_IP6,
+		       "Activation: Stage 5 of 5 (IPv6 Commit) failed");
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 	}
 
-	nm_log (LOGD_DEVICE, level, "Activation (%s) Stage 5 of 5 (IPv6 Commit) complete.", iface);
+	_LOG (level, LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv6 Commit) complete.");
 
 	return FALSE;
 }
@@ -4705,11 +5358,16 @@ nm_device_activate_schedule_ip6_config_result (NMDevice *self)
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
+	/* If IP had previously failed, move it back to IP_CONF since we
+	 * clearly now have configuration.
+	 */
+	if (priv->ip6_state == IP_FAIL)
+		priv->ip6_state = IP_CONF;
+
 	activation_source_schedule (self, nm_device_activate_ip6_config_commit, AF_INET6);
 
-	nm_log (LOGD_DEVICE | LOGD_IP6, level,
-		    "Activation (%s) Stage 5 of 5 (IPv6 Commit) scheduled...",
-		    nm_device_get_iface (self));
+	_LOG (level, LOGD_DEVICE | LOGD_IP6,
+	      "Activation: Stage 5 of 5 (IPv6 Commit) scheduled...");
 }
 
 gboolean
@@ -4775,7 +5433,7 @@ _update_ip4_address (NMDevice *self)
 
 	fd = socket (PF_INET, SOCK_DGRAM, 0);
 	if (fd < 0) {
-		nm_log_err (LOGD_IP4, "couldn't open control socket.");
+		_LOGE (LOGD_IP4, "couldn't open control socket.");
 		return;
 	}
 
@@ -4790,17 +5448,17 @@ _update_ip4_address (NMDevice *self)
 }
 
 gboolean
-nm_device_get_is_nm_owned (NMDevice *device)
+nm_device_get_is_nm_owned (NMDevice *self)
 {
-	return NM_DEVICE_GET_PRIVATE (device)->is_nm_owned;
+	return NM_DEVICE_GET_PRIVATE (self)->is_nm_owned;
 }
 
 void
-nm_device_set_nm_owned (NMDevice *device)
+nm_device_set_nm_owned (NMDevice *self)
 {
-	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (NM_IS_DEVICE (self));
 
-	NM_DEVICE_GET_PRIVATE (device)->is_nm_owned = TRUE;
+	NM_DEVICE_GET_PRIVATE (self)->is_nm_owned = TRUE;
 }
 
 /*
@@ -4814,6 +5472,7 @@ static gboolean
 delete_on_deactivate_link_delete (gpointer user_data)
 {
 	DeleteOnDeactivateData *data = user_data;
+	NMDevice *self = data->device;
 
 	if (data->device) {
 		NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (data->device);
@@ -4822,8 +5481,8 @@ delete_on_deactivate_link_delete (gpointer user_data)
 		priv->delete_on_deactivate_data = NULL;
 	}
 
-	nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)",
-	                         data->ifindex, data->idle_add_id);
+	_LOGD (LOGD_DEVICE, "delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)",
+	       data->ifindex, data->idle_add_id);
 	nm_platform_link_delete (data->ifindex);
 	g_free (data);
 	return FALSE;
@@ -4841,8 +5500,8 @@ delete_on_deactivate_unschedule (NMDevice *self)
 
 		g_source_remove (data->idle_add_id);
 		g_object_remove_weak_pointer (G_OBJECT (self), (void **) &data->device);
-		nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: cancel cleanup and delete virtual link #%d (id=%u)",
-		                         data->ifindex, data->idle_add_id);
+		_LOGD (LOGD_DEVICE, "delete_on_deactivate: cancel cleanup and delete virtual link #%d (id=%u)",
+		       data->ifindex, data->idle_add_id);
 		g_free (data);
 	}
 }
@@ -4857,6 +5516,8 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
 		return;
 	if (!priv->is_nm_owned)
 		return;
+	if (priv->queued_act_request)
+		return;
 	if (!nm_device_is_software (self))
 		return;
 	if (nm_device_get_state (self) == NM_DEVICE_STATE_UNMANAGED)
@@ -4872,17 +5533,17 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
 	data->idle_add_id = g_idle_add (delete_on_deactivate_link_delete, data);
 	priv->delete_on_deactivate_data = data;
 
-	nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: schedule cleanup and delete virtual link #%d for [%s] (id=%u)",
-	                         ifindex, nm_device_get_iface (self), data->idle_add_id);
+	_LOGD (LOGD_DEVICE, "delete_on_deactivate: schedule cleanup and delete virtual link #%d (id=%u)",
+	       ifindex, data->idle_add_id);
 }
 
 static void
-disconnect_cb (NMDevice *device,
+disconnect_cb (NMDevice *self,
                DBusGMethodInvocation *context,
                GError *error,
                gpointer user_data)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	GError *local = NULL;
 
 	if (error) {
@@ -4898,9 +5559,9 @@ disconnect_cb (NMDevice *device,
 		dbus_g_method_return_error (context, local);
 		g_error_free (local);
 	} else {
-		priv->autoconnect = FALSE;
+		nm_device_set_autoconnect (self, FALSE);
 
-		nm_device_state_changed (device,
+		nm_device_state_changed (self,
 		                         NM_DEVICE_STATE_DEACTIVATING,
 		                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
 		dbus_g_method_return (context);
@@ -4908,12 +5569,12 @@ disconnect_cb (NMDevice *device,
 }
 
 static void
-impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context)
+impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context)
 {
 	NMConnection *connection;
 	GError *error = NULL;
 
-	if (NM_DEVICE_GET_PRIVATE (device)->act_request == NULL) {
+	if (NM_DEVICE_GET_PRIVATE (self)->act_request == NULL) {
 		error = g_error_new_literal (NM_DEVICE_ERROR,
 		                             NM_DEVICE_ERROR_NOT_ACTIVE,
 		                             "This device is not active");
@@ -4922,11 +5583,11 @@ impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context)
 		return;
 	}
 
-	connection = nm_device_get_connection (device);
+	connection = nm_device_get_connection (self);
 	g_assert (connection);
 
 	/* Ask the manager to authenticate this request for us */
-	g_signal_emit (device, signals[AUTH_REQUEST], 0,
+	g_signal_emit (self, signals[AUTH_REQUEST], 0,
 	               context,
 	               connection,
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
@@ -4936,6 +5597,46 @@ impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context)
 }
 
 static void
+delete_cb (NMDevice *self,
+           DBusGMethodInvocation *context,
+           GError *error,
+           gpointer user_data)
+{
+	if (error) {
+		dbus_g_method_return_error (context, error);
+		return;
+	}
+
+	/* Authorized */
+	nm_platform_link_delete (nm_device_get_ifindex (self));
+	dbus_g_method_return (context);
+}
+
+static void
+impl_device_delete (NMDevice *self, DBusGMethodInvocation *context)
+{
+	GError *error = NULL;
+
+	if (!nm_device_is_software (self)) {
+		error = g_error_new_literal (NM_DEVICE_ERROR,
+		                             NM_DEVICE_ERROR_NOT_SOFTWARE,
+		                             "This device is not a software device");
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Ask the manager to authenticate this request for us */
+	g_signal_emit (self, signals[AUTH_REQUEST], 0,
+	               context,
+	               NULL,
+	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	               TRUE,
+	               delete_cb,
+	               NULL);
+}
+
+static void
 _device_activate (NMDevice *self, NMActRequest *req)
 {
 	NMDevicePrivate *priv;
@@ -4949,9 +5650,8 @@ _device_activate (NMDevice *self, NMActRequest *req)
 	connection = nm_act_request_get_connection (req);
 	g_assert (connection);
 
-	nm_log_info (LOGD_DEVICE, "Activation (%s) starting connection '%s'",
-	             nm_device_get_iface (self),
-	             nm_connection_get_id (connection));
+	_LOGI (LOGD_DEVICE, "Activation: starting connection '%s'",
+	       nm_connection_get_id (connection));
 
 	delete_on_deactivate_unschedule (self);
 
@@ -4986,8 +5686,7 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 	priv->queued_act_request = g_object_ref (req);
 
 	/* Deactivate existing activation request first */
-	nm_log_info (LOGD_DEVICE, "(%s): disconnecting for new activation request.",
-	             nm_device_get_iface (self));
+	_LOGI (LOGD_DEVICE, "disconnecting for new activation request.");
 	nm_device_state_changed (self,
 	                         NM_DEVICE_STATE_DEACTIVATING,
 	                         NM_DEVICE_STATE_REASON_NONE);
@@ -5000,14 +5699,14 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
  *
  */
 gboolean
-nm_device_is_activating (NMDevice *device)
+nm_device_is_activating (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceState state;
 
-	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	state = nm_device_get_state (device);
+	state = nm_device_get_state (self);
 	if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_SECONDARIES)
 		return TRUE;
 
@@ -5021,7 +5720,7 @@ nm_device_is_activating (NMDevice *device)
 
 /* IP Configuration stuff */
 
-NMDHCP4Config *
+NMDhcp4Config *
 nm_device_get_dhcp4_config (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
@@ -5041,6 +5740,7 @@ nm_device_get_ip4_config (NMDevice *self)
 static gboolean
 nm_device_set_ip4_config (NMDevice *self,
                           NMIP4Config *new_config,
+                          guint32 default_route_metric,
                           gboolean commit,
                           NMDeviceStateReason *reason)
 {
@@ -5062,7 +5762,12 @@ nm_device_set_ip4_config (NMDevice *self,
 
 	/* Always commit to nm-platform to update lifetimes */
 	if (commit && new_config) {
-		success = nm_ip4_config_commit (new_config, ip_ifindex);
+		gboolean assumed = nm_device_uses_assumed_connection (self);
+
+		/* for assumed devices we set the device_route_metric to the default which will
+		 * stop nm_platform_ip4_address_sync() to replace the device routes. */
+		success = nm_ip4_config_commit (new_config, ip_ifindex,
+		                                assumed ? NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE : default_route_metric);
 		if (!success)
 			reason_local = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
 	}
@@ -5073,8 +5778,8 @@ nm_device_set_ip4_config (NMDevice *self,
 			 * this causes a re-read and reset. This should only happen for relevant changes */
 			nm_ip4_config_replace (old_config, new_config, &has_changes);
 			if (has_changes) {
-				nm_log_dbg (LOGD_IP4, "(%s): update IP4Config instance (%s)",
-				            ip_iface, nm_ip4_config_get_dbus_path (old_config));
+				_LOGD (LOGD_IP4, "update IP4Config instance (%s)",
+				       nm_ip4_config_get_dbus_path (old_config));
 			}
 		} else {
 			has_changes = TRUE;
@@ -5085,18 +5790,20 @@ nm_device_set_ip4_config (NMDevice *self,
 				nm_ip4_config_export (new_config);
 			}
 
-			nm_log_dbg (LOGD_IP4, "(%s): set IP4Config instance (%s)",
-			            ip_iface, nm_ip4_config_get_dbus_path (new_config));
+			_LOGD (LOGD_IP4, "set IP4Config instance (%s)",
+			       nm_ip4_config_get_dbus_path (new_config));
 		}
 	} else if (old_config) {
 		has_changes = TRUE;
 		priv->ip4_config = NULL;
-		nm_log_dbg (LOGD_IP4, "(%s): clear IP4Config instance (%s)",
-		            ip_iface, nm_ip4_config_get_dbus_path (old_config));
+		_LOGD (LOGD_IP4, "clear IP4Config instance (%s)",
+		       nm_ip4_config_get_dbus_path (old_config));
 		/* Device config is invalid if combined config is invalid */
 		g_clear_object (&priv->dev_ip4_config);
 	}
 
+	nm_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), self);
+
 	if (has_changes) {
 		_update_ip4_address (self);
 
@@ -5107,7 +5814,7 @@ nm_device_set_ip4_config (NMDevice *self,
 		if (old_config != priv->ip4_config && old_config)
 			g_object_unref (old_config);
 
-		if (nm_device_uses_generated_connection (self)) {
+		if (nm_device_uses_generated_assumed_connection (self)) {
 			NMConnection *connection = nm_device_get_connection (self);
 			NMSetting *s_ip4;
 
@@ -5128,9 +5835,9 @@ nm_device_set_ip4_config (NMDevice *self,
 }
 
 void
-nm_device_set_vpn4_config (NMDevice *device, NMIP4Config *config)
+nm_device_set_vpn4_config (NMDevice *self, NMIP4Config *config)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->vpn4_config == config)
 		return;
@@ -5140,10 +5847,25 @@ nm_device_set_vpn4_config (NMDevice *device, NMIP4Config *config)
 		priv->vpn4_config = g_object_ref (config);
 
 	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (device, NULL, TRUE, NULL)) {
-		nm_log_warn (LOGD_IP4, "(%s): failed to set VPN routes for device",
-			         nm_device_get_ip_iface (device));
-	}
+	if (!ip4_config_merge_and_apply (self, NULL, TRUE, NULL))
+		_LOGW (LOGD_IP4, "failed to set VPN routes for device");
+}
+
+void
+nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->wwan_ip4_config == config)
+		return;
+
+	g_clear_object (&priv->wwan_ip4_config);
+	if (config)
+		priv->wwan_ip4_config = g_object_ref (config);
+
+	/* NULL to use existing configs */
+	if (!ip4_config_merge_and_apply (self, NULL, TRUE, NULL))
+		_LOGW (LOGD_IP4, "failed to set WWAN IPv4 configuration");
 }
 
 static gboolean
@@ -5181,8 +5903,8 @@ nm_device_set_ip6_config (NMDevice *self,
 			 * this causes a re-read and reset. This should only happen for relevant changes */
 			nm_ip6_config_replace (old_config, new_config, &has_changes);
 			if (has_changes) {
-				nm_log_dbg (LOGD_IP6, "(%s): update IP6Config instance (%s)",
-				            ip_iface, nm_ip6_config_get_dbus_path (old_config));
+				_LOGD (LOGD_IP6, "update IP6Config instance (%s)",
+				       nm_ip6_config_get_dbus_path (old_config));
 			}
 		} else {
 			has_changes = TRUE;
@@ -5193,16 +5915,18 @@ nm_device_set_ip6_config (NMDevice *self,
 				nm_ip6_config_export (new_config);
 			}
 
-			nm_log_dbg (LOGD_IP4, "(%s): set IP6Config instance (%s)",
-			            ip_iface, nm_ip6_config_get_dbus_path (new_config));
+			_LOGD (LOGD_IP4, "set IP6Config instance (%s)",
+			       nm_ip6_config_get_dbus_path (new_config));
 		}
 	} else if (old_config) {
 		has_changes = TRUE;
 		priv->ip6_config = NULL;
-		nm_log_dbg (LOGD_IP6, "(%s): clear IP6Config instance (%s)",
-		            ip_iface, nm_ip6_config_get_dbus_path (old_config));
+		_LOGD (LOGD_IP6, "clear IP6Config instance (%s)",
+		       nm_ip6_config_get_dbus_path (old_config));
 	}
 
+	nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), self);
+
 	if (has_changes) {
 		if (old_config != priv->ip6_config)
 			g_object_notify (G_OBJECT (self), NM_DEVICE_IP6_CONFIG);
@@ -5211,7 +5935,7 @@ nm_device_set_ip6_config (NMDevice *self,
 		if (old_config != priv->ip6_config && old_config)
 			g_object_unref (old_config);
 
-		if (nm_device_uses_generated_connection (self)) {
+		if (nm_device_uses_generated_assumed_connection (self)) {
 			NMConnection *connection = nm_device_get_connection (self);
 			NMSetting *s_ip6;
 
@@ -5232,9 +5956,9 @@ nm_device_set_ip6_config (NMDevice *self,
 }
 
 void
-nm_device_set_vpn6_config (NMDevice *device, NMIP6Config *config)
+nm_device_set_vpn6_config (NMDevice *self, NMIP6Config *config)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->vpn6_config == config)
 		return;
@@ -5244,13 +5968,28 @@ nm_device_set_vpn6_config (NMDevice *device, NMIP6Config *config)
 		priv->vpn6_config = g_object_ref (config);
 
 	/* NULL to use existing configs */
-	if (!ip6_config_merge_and_apply (device, TRUE, NULL)) {
-		nm_log_warn (LOGD_IP6, "(%s): failed to set VPN routes for device",
-			         nm_device_get_ip_iface (device));
-	}
+	if (!ip6_config_merge_and_apply (self, TRUE, NULL))
+		_LOGW (LOGD_IP6, "failed to set VPN routes for device");
+}
+
+void
+nm_device_set_wwan_ip6_config (NMDevice *self, NMIP6Config *config)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->wwan_ip6_config == config)
+		return;
+
+	g_clear_object (&priv->wwan_ip6_config);
+	if (config)
+		priv->wwan_ip6_config = g_object_ref (config);
+
+	/* NULL to use existing configs */
+	if (!ip6_config_merge_and_apply (self, TRUE, NULL))
+		_LOGW (LOGD_IP6, "failed to set WWAN IPv6 configuration");
 }
 
-NMDHCP6Config *
+NMDhcp6Config *
 nm_device_get_dhcp6_config (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
@@ -5336,16 +6075,7 @@ ip_check_gw_ping_cleanup (NMDevice *self)
 	}
 
 	if (priv->gw_ping.pid) {
-		guint count = 20;
-		int status;
-
-		kill (priv->gw_ping.pid, SIGKILL);
-		do {
-			if (waitpid (priv->gw_ping.pid, &status, WNOHANG) != 0)
-				break;
-			g_usleep (G_USEC_PER_SEC / 20);
-		} while (count--);
-
+		nm_utils_kill_child_async (priv->gw_ping.pid, SIGTERM, priv->gw_ping.log_domain, "ping", 1000, NULL, NULL);
 		priv->gw_ping.pid = 0;
 	}
 }
@@ -5355,7 +6085,6 @@ ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *iface;
 	guint log_domain = priv->gw_ping.log_domain;
 
 	if (!priv->gw_ping.watch)
@@ -5363,17 +6092,15 @@ ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data)
 	priv->gw_ping.watch = 0;
 	priv->gw_ping.pid = 0;
 
-	iface = nm_device_get_iface (self);
-
 	if (WIFEXITED (status)) {
 		if (WEXITSTATUS (status) == 0)
-			nm_log_dbg (log_domain, "(%s): gateway ping succeeded", iface);
+			_LOGD (log_domain, "ping: gateway ping succeeded");
 		else {
-			nm_log_warn (log_domain, "(%s): gateway ping failed with error code %d",
-				         iface, WEXITSTATUS (status));
+			_LOGW (log_domain, "ping: gateway ping failed with error code %d",
+			       WEXITSTATUS (status));
 		}
 	} else
-		nm_log_warn (log_domain, "(%s): ping stopped unexpectedly with status %d", iface, status);
+		_LOGW (log_domain, "ping: stopped unexpectedly with status %d", status);
 
 	/* We've got connectivity, proceed to pre_up */
 	ip_check_gw_ping_cleanup (self);
@@ -5388,8 +6115,7 @@ ip_check_ping_timeout_cb (gpointer user_data)
 
 	priv->gw_ping.timeout = 0;
 
-	nm_log_warn (priv->gw_ping.log_domain, "(%s): gateway ping timed out",
-	             nm_device_get_iface (self));
+	_LOGW (priv->gw_ping.log_domain, "ping: gateway ping timed out");
 
 	ip_check_gw_ping_cleanup (self);
 	ip_check_pre_up (self);
@@ -5406,7 +6132,8 @@ spawn_ping (NMDevice *self,
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const char *args[] = { binary, "-I", nm_device_get_ip_iface (self), "-c", "1", "-w", NULL, address, NULL };
 	GError *error = NULL;
-	char *str_timeout, *cmd;
+	char *str_timeout;
+	gs_free char *tmp_str = NULL;
 	gboolean success;
 
 	g_return_val_if_fail (priv->gw_ping.watch == 0, FALSE);
@@ -5414,13 +6141,8 @@ spawn_ping (NMDevice *self,
 
 	args[6] = str_timeout = g_strdup_printf ("%u", timeout);
 
-	if (nm_logging_enabled (LOGL_DEBUG, log_domain)) {
-		cmd = g_strjoinv (" ", (gchar **) args);
-		nm_log_dbg (log_domain, "(%s): running '%s'",
-		            nm_device_get_iface (self),
-		            cmd);
-		g_free (cmd);
-	}
+	_LOGD (log_domain, "ping: running '%s'",
+	       (tmp_str = g_strjoinv (" ", (gchar **) args)));
 
 	success = g_spawn_async ("/",
 	                         (gchar **) args,
@@ -5435,7 +6157,7 @@ spawn_ping (NMDevice *self,
 		priv->gw_ping.watch = g_child_watch_add (priv->gw_ping.pid, ip_check_ping_watch_cb, self);
 		priv->gw_ping.timeout = g_timeout_add_seconds (timeout + 1, ip_check_ping_timeout_cb, self);
 	} else {
-		nm_log_warn (log_domain, "could not spawn %s: %s", binary, error->message);
+		_LOGW (log_domain, "ping: could not spawn %s: %s", binary, error->message);
 		g_clear_error (&error);
 	}
 
@@ -5471,7 +6193,7 @@ nm_device_start_ip_check (NMDevice *self)
 	timeout = nm_setting_connection_get_gateway_ping_timeout (s_con);
 
 	if (timeout) {
-		if (priv->ip4_state == IP_DONE) {
+		if (priv->ip4_config && priv->ip4_state == IP_DONE) {
 			guint gw = 0;
 
 			ping_binary = "/usr/bin/ping";
@@ -5524,9 +6246,9 @@ nm_device_is_up (NMDevice *self)
 }
 
 static gboolean
-is_up (NMDevice *device)
+is_up (NMDevice *self)
 {
-	int ifindex = nm_device_get_ip_ifindex (device);
+	int ifindex = nm_device_get_ip_ifindex (self);
 
 	return ifindex > 0 ? nm_platform_link_is_up (ifindex) : TRUE;
 }
@@ -5539,7 +6261,7 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	nm_log_dbg (LOGD_HW, "(%s): bringing up device.", nm_device_get_iface (self));
+	_LOGD (LOGD_HW, "bringing up device.");
 
 	if (NM_DEVICE_GET_CLASS (self)->bring_up) {
 		if (!NM_DEVICE_GET_CLASS (self)->bring_up (self, no_firmware))
@@ -5561,9 +6283,9 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 
 	if (!device_is_up) {
 		if (block)
-			nm_log_warn (LOGD_HW, "(%s): device not up after timeout!", nm_device_get_iface (self));
+			_LOGW (LOGD_HW, "device not up after timeout!");
 		else
-			nm_log_dbg (LOGD_HW, "(%s): device not up immediately", nm_device_get_iface (self));
+			_LOGD (LOGD_HW, "device not up immediately");
 		return FALSE;
 	}
 
@@ -5589,18 +6311,18 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 }
 
 static void
-check_carrier (NMDevice *device)
+check_carrier (NMDevice *self)
 {
-	int ifindex = nm_device_get_ip_ifindex (device);
+	int ifindex = nm_device_get_ip_ifindex (self);
 
-	if (!device_has_capability (device, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
-		nm_device_set_carrier (device, nm_platform_link_is_connected (ifindex));
+	if (!device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
+		nm_device_set_carrier (self, nm_platform_link_is_connected (ifindex));
 }
 
 static gboolean
-bring_up (NMDevice *device, gboolean *no_firmware)
+bring_up (NMDevice *self, gboolean *no_firmware)
 {
-	int ifindex = nm_device_get_ip_ifindex (device);
+	int ifindex = nm_device_get_ip_ifindex (self);
 	gboolean result;
 
 	if (ifindex <= 0) {
@@ -5614,8 +6336,8 @@ bring_up (NMDevice *device, gboolean *no_firmware)
 		*no_firmware = nm_platform_get_error () == NM_PLATFORM_ERROR_NO_FIRMWARE;
 
 	/* Store carrier immediately. */
-	if (result && device_has_capability (device, NM_DEVICE_CAP_CARRIER_DETECT))
-		check_carrier (device);
+	if (result && device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT))
+		check_carrier (self);
 
 	return result;
 }
@@ -5627,7 +6349,7 @@ nm_device_take_down (NMDevice *self, gboolean block)
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	nm_log_dbg (LOGD_HW, "(%s): taking down device.", nm_device_get_iface (self));
+	_LOGD (LOGD_HW, "taking down device.");
 
 	if (NM_DEVICE_GET_CLASS (self)->take_down) {
 		if (!NM_DEVICE_GET_CLASS (self)->take_down (self))
@@ -5649,22 +6371,22 @@ nm_device_take_down (NMDevice *self, gboolean block)
 
 	if (device_is_up) {
 		if (block)
-			nm_log_warn (LOGD_HW, "(%s): device not down after timeout!", nm_device_get_iface (self));
+			_LOGW (LOGD_HW, "device not down after timeout!");
 		else
-			nm_log_dbg (LOGD_HW, "(%s): device not down immediately", nm_device_get_iface (self));
+			_LOGD (LOGD_HW, "device not down immediately");
 	}
 }
 
 static gboolean
-take_down (NMDevice *device)
+take_down (NMDevice *self)
 {
-	int ifindex = nm_device_get_ip_ifindex (device);
+	int ifindex = nm_device_get_ip_ifindex (self);
 
 	if (ifindex > 0)
 		return nm_platform_link_set_down (ifindex);
 
 	/* devices without ifindex are always up. */
-	nm_log_dbg (LOGD_HW, "(%s): cannot take down device without ifindex", nm_device_get_iface (device));
+	_LOGD (LOGD_HW, "cannot take down device without ifindex");
 	return FALSE;
 }
 
@@ -5689,11 +6411,11 @@ nm_device_get_firmware_missing (NMDevice *self)
 }
 
 static NMIP4Config *
-find_ip4_lease_config (NMDevice *device,
+find_ip4_lease_config (NMDevice *self,
                        NMConnection *connection,
                        NMIP4Config *ext_ip4_config)
 {
-	const char *ip_iface = nm_device_get_ip_iface (device);
+	const char *ip_iface = nm_device_get_ip_iface (self);
 	GSList *leases, *liter;
 	NMIP4Config *found = NULL;
 
@@ -5703,7 +6425,8 @@ find_ip4_lease_config (NMDevice *device,
 	leases = nm_dhcp_manager_get_lease_ip_configs (nm_dhcp_manager_get (),
 	                                               ip_iface,
 	                                               nm_connection_get_uuid (connection),
-	                                               FALSE);
+	                                               FALSE,
+	                                               nm_device_get_ip4_route_metric (self));
 	for (liter = leases; liter && !found; liter = liter->next) {
 		NMIP4Config *lease_config = liter->data;
 		const NMPlatformIP4Address *address = nm_ip4_config_get_address (lease_config, 0);
@@ -5722,13 +6445,13 @@ find_ip4_lease_config (NMDevice *device,
 }
 
 static void
-capture_lease_config (NMDevice *device,
+capture_lease_config (NMDevice *self,
                       NMIP4Config *ext_ip4_config,
                       NMIP4Config **out_ip4_config,
                       NMIP6Config *ext_ip6_config,
                       NMIP6Config **out_ip6_config)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const GSList *connections, *citer;
 	guint i;
 	gboolean dhcp_used = FALSE;
@@ -5767,13 +6490,13 @@ capture_lease_config (NMDevice *device,
 		NMConnection *candidate = citer->data;
 		const char *method;
 
-		if (!nm_device_check_connection_compatible (device, candidate))
+		if (!nm_device_check_connection_compatible (self, candidate))
 			continue;
 
 		/* IPv4 leases */
 		method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP4_CONFIG);
 		if (out_ip4_config && strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
-			*out_ip4_config = find_ip4_lease_config (device, candidate, ext_ip4_config);
+			*out_ip4_config = find_ip4_lease_config (self, candidate, ext_ip4_config);
 			if (*out_ip4_config)
 				return;
 		}
@@ -5805,7 +6528,8 @@ update_ip_config (NMDevice *self, gboolean initial)
 	/* IPv4 */
 	g_clear_object (&priv->ext_ip4_config);
 	priv->ext_ip4_config = nm_ip4_config_capture (ifindex, capture_resolv_conf);
-
+	priv->ext_ip4_config_had_any_addresses = (   priv->ext_ip4_config
+	                                          && nm_ip4_config_get_num_addresses (priv->ext_ip4_config) > 0);
 	if (priv->ext_ip4_config) {
 		if (initial) {
 			g_clear_object (&priv->dev_ip4_config);
@@ -5815,6 +6539,8 @@ update_ip_config (NMDevice *self, gboolean initial)
 			nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config);
 		if (priv->vpn4_config)
 			nm_ip4_config_subtract (priv->ext_ip4_config, priv->vpn4_config);
+		if (priv->wwan_ip4_config)
+			nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_config);
 
 		ip4_config_merge_and_apply (self, NULL, FALSE, NULL);
 	}
@@ -5822,16 +6548,20 @@ update_ip_config (NMDevice *self, gboolean initial)
 	/* IPv6 */
 	g_clear_object (&priv->ext_ip6_config);
 	priv->ext_ip6_config = nm_ip6_config_capture (ifindex, capture_resolv_conf, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+	priv->ext_ip6_config_had_any_addresses = (   priv->ext_ip6_config
+	                                          && nm_ip6_config_get_num_addresses (priv->ext_ip6_config) > 0);
 	if (priv->ext_ip6_config) {
 
 		/* Check this before modifying ext_ip6_config */
 		linklocal6_just_completed = priv->linklocal6_timeout_id &&
-		                            linklocal6_config_is_ready (priv->ext_ip6_config);
+		                            have_ip6_address (priv->ext_ip6_config, TRUE);
 
 		if (priv->ac_ip6_config)
 			nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config);
 		if (priv->dhcp6_ip6_config)
 			nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6_ip6_config);
+		if (priv->wwan_ip6_config)
+			nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_ip6_config);
 		if (priv->vpn6_config)
 			nm_ip6_config_subtract (priv->ext_ip6_config, priv->vpn6_config);
 
@@ -5847,9 +6577,9 @@ update_ip_config (NMDevice *self, gboolean initial)
 }
 
 void
-nm_device_capture_initial_config (NMDevice *dev)
+nm_device_capture_initial_config (NMDevice *self)
 {
-	update_ip_config (dev, TRUE);
+	update_ip_config (self, TRUE);
 }
 
 static gboolean
@@ -5864,22 +6594,33 @@ queued_ip_config_change (gpointer user_data)
 
 	priv->queued_ip_config_id = 0;
 	update_ip_config (self, FALSE);
+
+	/* If no IPv6 link-local address exists but other addresses do then we
+	 * must add the LL address to remain conformant with RFC 3513 chapter 2.1
+	 * ("Addressing Model"): "All interfaces are required to have at least
+	 * one link-local unicast address".
+	 */
+	if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config))
+		check_and_add_ipv6ll_addr (self);
+
 	return FALSE;
 }
 
 static void
-device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, NMPlatformSignalChangeType change_type, NMPlatformReason reason, gpointer user_data)
+device_ip_changed (NMPlatform *platform,
+                   int ifindex,
+                   gpointer platform_object,
+                   NMPlatformSignalChangeType change_type,
+                   NMPlatformReason reason,
+                   NMDevice *self)
 {
-	NMDevice *self = user_data;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (nm_device_get_ip_ifindex (self) == ifindex) {
-		NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
 		if (!priv->queued_ip_config_id)
 			priv->queued_ip_config_id = g_idle_add (queued_ip_config_change, self);
 
-		nm_log_dbg (LOGD_DEVICE, "(%s): queued IP config change",
-		            nm_device_get_iface (self));
+		_LOGD (LOGD_DEVICE, "queued IP config change");
 	}
 }
 
@@ -5889,8 +6630,7 @@ nm_device_queued_ip_config_change_clear (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->queued_ip_config_id) {
-		nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued IP config change",
-		            nm_device_get_iface (self));
+		_LOGD (LOGD_DEVICE, "clearing queued IP config change");
 		g_source_remove (priv->queued_ip_config_id);
 		priv->queued_ip_config_id = 0;
 	}
@@ -5898,19 +6638,19 @@ nm_device_queued_ip_config_change_clear (NMDevice *self)
 
 /**
  * nm_device_get_managed():
- * @device: the #NMDevice
+ * @self: the #NMDevice
  *
  * Returns: %TRUE if the device is managed
  */
 gboolean
-nm_device_get_managed (NMDevice *device)
+nm_device_get_managed (NMDevice *self)
 {
 	NMDevicePrivate *priv;
 	gboolean managed;
 
-	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	priv = NM_DEVICE_GET_PRIVATE (device);
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* Return the composite of all managed flags.  However, if the device
 	 * is a default-unmanaged device, and would be managed except for the
@@ -5926,30 +6666,30 @@ nm_device_get_managed (NMDevice *device)
 
 /**
  * nm_device_get_unmanaged_flag():
- * @device: the #NMDevice
+ * @self: the #NMDevice
  *
  * Returns: %TRUE if the device is unmanaged for @flag.
  */
 gboolean
-nm_device_get_unmanaged_flag (NMDevice *device, NMUnmanagedFlags flag)
+nm_device_get_unmanaged_flag (NMDevice *self, NMUnmanagedFlags flag)
 {
-	return NM_DEVICE_GET_PRIVATE (device)->unmanaged_flags & flag;
+	return NM_FLAGS_ANY (NM_DEVICE_GET_PRIVATE (self)->unmanaged_flags, flag);
 }
 
 /**
  * nm_device_get_default_unmanaged():
- * @device: the #NMDevice
+ * @self: the #NMDevice
  *
  * Returns: %TRUE if the device is by default unmanaged
  */
 static gboolean
-nm_device_get_default_unmanaged (NMDevice *device)
+nm_device_get_default_unmanaged (NMDevice *self)
 {
-	return nm_device_get_unmanaged_flag (device, NM_UNMANAGED_DEFAULT);
+	return nm_device_get_unmanaged_flag (self, NM_UNMANAGED_DEFAULT);
 }
 
 void
-nm_device_set_unmanaged (NMDevice *device,
+nm_device_set_unmanaged (NMDevice *self,
                          NMUnmanagedFlags flag,
                          gboolean unmanaged,
                          NMDeviceStateReason reason)
@@ -5957,42 +6697,40 @@ nm_device_set_unmanaged (NMDevice *device,
 	NMDevicePrivate *priv;
 	gboolean was_managed, now_managed;
 
-	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (flag <= NM_UNMANAGED_LAST);
 
-	priv = NM_DEVICE_GET_PRIVATE (device);
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	was_managed = nm_device_get_managed (device);
+	was_managed = nm_device_get_managed (self);
 	if (unmanaged)
 		priv->unmanaged_flags |= flag;
 	else
 		priv->unmanaged_flags &= ~flag;
-	now_managed = nm_device_get_managed (device);
+	now_managed = nm_device_get_managed (self);
 
 	if (was_managed != now_managed) {
-		nm_log_dbg (LOGD_DEVICE, "(%s): now %s",
-			        nm_device_get_iface (device),
-			        unmanaged ? "unmanaged" : "managed");
+		_LOGD (LOGD_DEVICE, "now %s", unmanaged ? "unmanaged" : "managed");
 
-		g_object_notify (G_OBJECT (device), NM_DEVICE_MANAGED);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_MANAGED);
 
 		if (unmanaged)
-			nm_device_state_changed (device, NM_DEVICE_STATE_UNMANAGED, reason);
+			nm_device_state_changed (self, NM_DEVICE_STATE_UNMANAGED, reason);
 		else
-			nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, reason);
+			nm_device_state_changed (self, NM_DEVICE_STATE_UNAVAILABLE, reason);
 	}
 }
 
 void
-nm_device_set_unmanaged_quitting (NMDevice *device)
+nm_device_set_unmanaged_quitting (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* It's OK to block here because we're quitting */
-	if (nm_device_is_activating (device) || priv->state == NM_DEVICE_STATE_ACTIVATED)
-		_set_state_full (device, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_REMOVED, TRUE);
+	if (nm_device_is_activating (self) || priv->state == NM_DEVICE_STATE_ACTIVATED)
+		_set_state_full (self, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_REMOVED, TRUE);
 
-	nm_device_set_unmanaged (device,
+	nm_device_set_unmanaged (self,
 	                         NM_UNMANAGED_INTERNAL,
 	                         TRUE,
 	                         NM_DEVICE_STATE_REASON_REMOVED);
@@ -6000,26 +6738,26 @@ nm_device_set_unmanaged_quitting (NMDevice *device)
 
 /**
  * nm_device_set_initial_unmanaged_flag():
- * @device: the #NMDevice
+ * @self: the #NMDevice
  * @flag: an #NMUnmanagedFlag
  * @unmanaged: %TRUE or %FALSE to set or clear @flag
  *
- * Like nm_device_set_unmanaged() but must be set before the device is exported
- * and does not trigger state changes.  Should only be used when initializing
- * a device.
+ * Like nm_device_set_unmanaged(), but must be set before the device is
+ * initialized by nm_device_finish_init(), and does not trigger state changes.
+ * Should only be used when initializing a device.
  */
 void
-nm_device_set_initial_unmanaged_flag (NMDevice *device,
+nm_device_set_initial_unmanaged_flag (NMDevice *self,
                                       NMUnmanagedFlags flag,
                                       gboolean unmanaged)
 {
 	NMDevicePrivate *priv;
 
-	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (flag <= NM_UNMANAGED_LAST);
 
-	priv = NM_DEVICE_GET_PRIVATE (device);
-	g_return_if_fail (priv->path == NULL);
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->initialized == FALSE);
 
 	if (unmanaged)
 		priv->unmanaged_flags |= flag;
@@ -6028,60 +6766,54 @@ nm_device_set_initial_unmanaged_flag (NMDevice *device,
 }
 
 void
-nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout)
+nm_device_set_dhcp_timeout (NMDevice *self, guint32 timeout)
 {
-	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (NM_IS_DEVICE (self));
 
-	NM_DEVICE_GET_PRIVATE (device)->dhcp_timeout = timeout;
+	NM_DEVICE_GET_PRIVATE (self)->dhcp_timeout = timeout;
 }
 
 void
-nm_device_set_dhcp_anycast_address (NMDevice *device, guint8 *addr)
+nm_device_set_dhcp_anycast_address (NMDevice *self, const char *addr)
 {
 	NMDevicePrivate *priv;
 
-	g_return_if_fail (NM_IS_DEVICE (device));
-
-	priv = NM_DEVICE_GET_PRIVATE (device);
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (!addr || nm_utils_hwaddr_valid (addr, ETH_ALEN));
 
-	if (priv->dhcp_anycast_address) {
-		g_byte_array_free (priv->dhcp_anycast_address, TRUE);
-		priv->dhcp_anycast_address = NULL;
-	}
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (addr) {
-		priv->dhcp_anycast_address = g_byte_array_sized_new (ETH_ALEN);
-		g_byte_array_append (priv->dhcp_anycast_address, addr, ETH_ALEN);
-	}
+	g_free (priv->dhcp_anycast_address);
+	priv->dhcp_anycast_address = g_strdup (addr);
 }
 
 /**
  * nm_device_connection_is_available():
- * @device: the #NMDevice
+ * @self: the #NMDevice
  * @connection: the #NMConnection to check for availability
  * @allow_device_override: set to %TRUE to let the device do specific checks
  *
- * Check if @connection is available to be activated on @device.  Normally this
- * only checks if the connection is in @device's AvailableConnections property.
+ * Check if @connection is available to be activated on @self.  Normally this
+ * only checks if the connection is in @self's AvailableConnections property.
  * If @allow_device_override is %TRUE then the device is asked to do specific
  * checks that may bypass the AvailableConnections property.
  *
- * Returns: %TRUE if @connection can be activated on @device
+ * Returns: %TRUE if @connection can be activated on @self
  */
 gboolean
-nm_device_connection_is_available (NMDevice *device,
+nm_device_connection_is_available (NMDevice *self,
                                    NMConnection *connection,
                                    gboolean allow_device_override)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean available = FALSE;
 
-	if (nm_device_get_default_unmanaged (device) && (priv->state == NM_DEVICE_STATE_UNMANAGED)) {
+	if (nm_device_get_default_unmanaged (self) && (priv->state == NM_DEVICE_STATE_UNMANAGED)) {
 		/* default-unmanaged  devices in UNMANAGED state have no available connections
 		 * so we must manually check whether the connection is available here.
 		 */
-		if (   nm_device_check_connection_compatible (device, connection)
-		    && NM_DEVICE_GET_CLASS (device)->check_connection_available (device, connection, NULL))
+		if (   nm_device_check_connection_compatible (self, connection)
+		    && NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, NULL))
 			return TRUE;
 	}
 
@@ -6092,32 +6824,33 @@ nm_device_connection_is_available (NMDevice *device,
 		 * activating but the network isn't available let the device recheck
 		 * availability.
 		 */
-		if (   nm_device_check_connection_compatible (device, connection)
-		    && NM_DEVICE_GET_CLASS (device)->check_connection_available_wifi_hidden)
-			available = NM_DEVICE_GET_CLASS (device)->check_connection_available_wifi_hidden (device, connection);
+		if (   nm_device_check_connection_compatible (self, connection)
+		    && NM_DEVICE_GET_CLASS (self)->check_connection_available_wifi_hidden)
+			available = NM_DEVICE_GET_CLASS (self)->check_connection_available_wifi_hidden (self, connection);
 	}
 
 	return available;
 }
 
 static void
-_signal_available_connections_changed (NMDevice *device)
+_signal_available_connections_changed (NMDevice *self)
 {
-	g_object_notify (G_OBJECT (device), NM_DEVICE_AVAILABLE_CONNECTIONS);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_AVAILABLE_CONNECTIONS);
 }
 
 static void
-_clear_available_connections (NMDevice *device, gboolean do_signal)
+_clear_available_connections (NMDevice *self, gboolean do_signal)
 {
-	g_hash_table_remove_all (NM_DEVICE_GET_PRIVATE (device)->available_connections);
+	g_hash_table_remove_all (NM_DEVICE_GET_PRIVATE (self)->available_connections);
 	if (do_signal == TRUE)
-		_signal_available_connections_changed (device);
+		_signal_available_connections_changed (self);
 }
 
 static gboolean
 _try_add_available_connection (NMDevice *self, NMConnection *connection)
 {
-	if (nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED)
+	if (   nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED
+	    && !nm_device_get_default_unmanaged (self))
 		return FALSE;
 
 	if (nm_device_check_connection_compatible (self, connection)) {
@@ -6132,98 +6865,49 @@ _try_add_available_connection (NMDevice *self, NMConnection *connection)
 }
 
 static gboolean
-_del_available_connection (NMDevice *device, NMConnection *connection)
+_del_available_connection (NMDevice *self, NMConnection *connection)
 {
-	return g_hash_table_remove (NM_DEVICE_GET_PRIVATE (device)->available_connections, connection);
+	return g_hash_table_remove (NM_DEVICE_GET_PRIVATE (self)->available_connections, connection);
 }
 
 static gboolean
-connection_requires_carrier (NMConnection *connection)
-{
-	NMSettingIP4Config *s_ip4;
-	NMSettingIP6Config *s_ip6;
-	const char *method;
-	gboolean ip4_carrier_wanted = FALSE, ip6_carrier_wanted = FALSE;
-	gboolean ip4_used = FALSE, ip6_used = FALSE;
-
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (   strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) != 0
-	    && strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) != 0) {
-		ip4_carrier_wanted = TRUE;
-
-		/* If IPv4 wants a carrier and cannot fail, the whole connection
-		 * requires a carrier regardless of the IPv6 method.
-		 */
-		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		if (s_ip4 && !nm_setting_ip4_config_get_may_fail (s_ip4))
-			return TRUE;
-	}
-	ip4_used = (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) != 0);
-
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	if (   strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) != 0
-	    && strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) != 0) {
-		ip6_carrier_wanted = TRUE;
-
-		/* If IPv6 wants a carrier and cannot fail, the whole connection
-		 * requires a carrier regardless of the IPv4 method.
-		 */
-		s_ip6 = nm_connection_get_setting_ip6_config (connection);
-		if (s_ip6 && !nm_setting_ip6_config_get_may_fail (s_ip6))
-			return TRUE;
-	}
-	ip6_used = (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) != 0);
-
-	/* If an IP version wants a carrier and and the other IP version isn't
-	 * used, the connection requires carrier since it will just fail without one.
-	 */
-	if (ip4_carrier_wanted && !ip6_used)
-		return TRUE;
-	if (ip6_carrier_wanted && !ip4_used)
-		return TRUE;
-
-	/* If both want a carrier, the whole connection wants a carrier */
-	return ip4_carrier_wanted && ip6_carrier_wanted;
-}
-
-static gboolean
-check_connection_available (NMDevice *device,
+check_connection_available (NMDevice *self,
                             NMConnection *connection,
                             const char *specific_object)
 {
 	/* Connections which require a network connection are not available when
 	 * the device has no carrier, even with ignore-carrer=TRUE.
 	 */
-	if (NM_DEVICE_GET_PRIVATE (device)->carrier == FALSE)
+	if (NM_DEVICE_GET_PRIVATE (self)->carrier == FALSE)
 		return connection_requires_carrier (connection) ? FALSE : TRUE;
 
 	return TRUE;
 }
 
 void
-nm_device_recheck_available_connections (NMDevice *device)
+nm_device_recheck_available_connections (NMDevice *self)
 {
 	NMDevicePrivate *priv;
 	const GSList *connections, *iter;
 
-	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (NM_IS_DEVICE (self));
 
-	priv = NM_DEVICE_GET_PRIVATE(device);
+	priv = NM_DEVICE_GET_PRIVATE(self);
 
 	if (priv->con_provider) {
-		_clear_available_connections (device, FALSE);
+		_clear_available_connections (self, 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));
+			_try_add_available_connection (self, NM_CONNECTION (iter->data));
 
-		_signal_available_connections_changed (device);
+		_signal_available_connections_changed (self);
 	}
 }
 
 /**
  * nm_device_get_available_connections:
- * @device: the #NMDevice
+ * @self: the #NMDevice
  * @specific_object: a specific object path if any
  *
  * Returns a list of connections available to activate on the device, taking
@@ -6233,9 +6917,9 @@ nm_device_recheck_available_connections (NMDevice *device)
  * Returns: caller-owned #GPtrArray of #NMConnections
  */
 GPtrArray *
-nm_device_get_available_connections (NMDevice *device, const char *specific_object)
+nm_device_get_available_connections (NMDevice *self, const char *specific_object)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	GHashTableIter iter;
 	guint num_available;
 	NMConnection *connection = NULL;
@@ -6250,7 +6934,7 @@ nm_device_get_available_connections (NMDevice *device, const char *specific_obje
 			 * compatible with it.
 			 */
 			if (   !specific_object
-			    || NM_DEVICE_GET_CLASS (device)->check_connection_available (device, connection, specific_object))
+			    || NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, specific_object))
 				g_ptr_array_add (array, connection);
 		}
 	}
@@ -6286,14 +6970,14 @@ cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpoin
 }
 
 gboolean
-nm_device_supports_vlans (NMDevice *device)
+nm_device_supports_vlans (NMDevice *self)
 {
-	return nm_platform_link_supports_vlans (nm_device_get_ifindex (device));
+	return nm_platform_link_supports_vlans (nm_device_get_ifindex (self));
 }
 
 /**
  * nm_device_add_pending_action():
- * @device: the #NMDevice to add the pending action to
+ * @self: the #NMDevice to add the pending action to
  * @action: a static string that identifies the action
  * @assert_not_yet_pending: if %TRUE, assert that the @action is currently not yet pending.
  * Otherwise, ignore duplicate scheduling of the same action silently.
@@ -6305,9 +6989,9 @@ nm_device_supports_vlans (NMDevice *device)
  * a second time.
  */
 gboolean
-nm_device_add_pending_action (NMDevice *device, const char *action, gboolean assert_not_yet_pending)
+nm_device_add_pending_action (NMDevice *self, const char *action, gboolean assert_not_yet_pending)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	GSList *iter;
 	guint count = 0;
 
@@ -6317,16 +7001,12 @@ nm_device_add_pending_action (NMDevice *device, const char *action, gboolean ass
 	for (iter = priv->pending_actions; iter; iter = iter->next) {
 		if (!strcmp (action, iter->data)) {
 			if (assert_not_yet_pending) {
-				nm_log_warn (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending",
-				             nm_device_get_iface (device),
-				             count + g_slist_length (iter),
-				             action);
+				_LOGW (LOGD_DEVICE, "add_pending_action (%d): '%s' already pending",
+				       count + g_slist_length (iter), action);
 				g_return_val_if_reached (FALSE);
 			} else {
-				nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending (expected)",
-				            nm_device_get_iface (device),
-				            count + g_slist_length (iter),
-				            action);
+				_LOGD (LOGD_DEVICE, "add_pending_action (%d): '%s' already pending (expected)",
+				       count + g_slist_length (iter), action);
 			}
 			return FALSE;
 		}
@@ -6336,20 +7016,17 @@ nm_device_add_pending_action (NMDevice *device, const char *action, gboolean ass
 	priv->pending_actions = g_slist_append (priv->pending_actions, g_strdup (action));
 	count++;
 
-	nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s'",
-	            nm_device_get_iface (device),
-	            count,
-	            action);
+	_LOGD (LOGD_DEVICE, "add_pending_action (%d): '%s'", count, action);
 
 	if (count == 1)
-		g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_HAS_PENDING_ACTION);
 
 	return TRUE;
 }
 
 /**
  * nm_device_remove_pending_action():
- * @device: the #NMDevice to remove the pending action from
+ * @self: the #NMDevice to remove the pending action from
  * @action: a static string that identifies the action
  * @assert_is_pending: if %TRUE, assert that the @action is pending.
  * If %FALSE, don't do anything if the current action is not pending and
@@ -6360,9 +7037,9 @@ nm_device_add_pending_action (NMDevice *device, const char *action, gboolean ass
  * Returns: whether the @action was pending and is now removed.
  */
 gboolean
-nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending)
+nm_device_remove_pending_action (NMDevice *self, const char *action, gboolean assert_is_pending)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	GSList *iter;
 	guint count = 0;
 
@@ -6370,38 +7047,31 @@ nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean
 
 	for (iter = priv->pending_actions; iter; iter = iter->next) {
 		if (!strcmp (action, iter->data)) {
-			nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s'",
-			            nm_device_get_iface (device),
-			            count + g_slist_length (iter->next), /* length excluding 'iter' */
-			            action);
+			_LOGD (LOGD_DEVICE, "remove_pending_action (%d): '%s'",
+			       count + g_slist_length (iter->next), /* length excluding 'iter' */
+			       action);
 			g_free (iter->data);
 			priv->pending_actions = g_slist_delete_link (priv->pending_actions, iter);
 			if (priv->pending_actions == NULL)
-				g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION);
+				g_object_notify (G_OBJECT (self), NM_DEVICE_HAS_PENDING_ACTION);
 			return TRUE;
 		}
 		count++;
 	}
 
 	if (assert_is_pending) {
-		nm_log_warn (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending",
-		             nm_device_get_iface (device),
-		             count,
-		             action);
+		_LOGW (LOGD_DEVICE, "remove_pending_action (%d): '%s' not pending", count, action);
 		g_return_val_if_reached (FALSE);
-	} else {
-		nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending (expected)",
-		             nm_device_get_iface (device),
-		             count,
-		             action);
-	}
+	} else
+		_LOGD (LOGD_DEVICE, "remove_pending_action (%d): '%s' not pending (expected)", count, action);
+
 	return FALSE;
 }
 
 gboolean
-nm_device_has_pending_action (NMDevice *device)
+nm_device_has_pending_action (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	return !!priv->pending_actions;
 }
@@ -6409,25 +7079,41 @@ nm_device_has_pending_action (NMDevice *device)
 /***********************************************************/
 
 static void
+_cleanup_ip_pre (NMDevice *self, gboolean deconfigure)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	priv->ip4_state = priv->ip6_state = IP_NONE;
+	nm_device_queued_ip_config_change_clear (self);
+
+	dhcp4_cleanup (self, deconfigure, FALSE);
+	arp_cleanup (self);
+	dhcp6_cleanup (self, deconfigure, FALSE);
+	linklocal6_cleanup (self);
+	addrconf6_cleanup (self);
+	dnsmasq_cleanup (self);
+	aipd_cleanup (self);
+}
+
+static void
 _cleanup_generic_pre (NMDevice *self, gboolean deconfigure)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMConnection *connection;
 
 	/* Clean up when device was deactivated during call to firewall */
-	if (priv->fw_manager) {
-		NMConnection *connection;
-
-		if (priv->fw_call) {
-			nm_firewall_manager_cancel_call (priv->fw_manager, priv->fw_call);
-			priv->fw_call = NULL;
-		}
+	if (priv->fw_call) {
+		nm_firewall_manager_cancel_call (nm_firewall_manager_get (), priv->fw_call);
+		priv->fw_call = NULL;
+	}
 
-		connection = nm_device_get_connection (self);
-		if (deconfigure && connection) {
-			nm_firewall_manager_remove_from_zone (priv->fw_manager,
-			                                      nm_device_get_ip_iface (self),
-			                                      NULL);
-		}
+	connection = nm_device_get_connection (self);
+	if (   deconfigure
+	    && connection
+	    && !nm_device_uses_assumed_connection (self)) {
+		nm_firewall_manager_remove_from_zone (nm_firewall_manager_get (),
+		                                      nm_device_get_ip_iface (self),
+		                                      NULL);
 	}
 
 	ip_check_gw_ping_cleanup (self);
@@ -6438,17 +7124,8 @@ _cleanup_generic_pre (NMDevice *self, gboolean deconfigure)
 
 	/* Clear any queued transitions */
 	nm_device_queued_state_clear (self);
-	nm_device_queued_ip_config_change_clear (self);
 
-	priv->ip4_state = priv->ip6_state = IP_NONE;
-
-	dhcp4_cleanup (self, deconfigure, FALSE);
-	arp_cleanup (self);
-	dhcp6_cleanup (self, deconfigure, FALSE);
-	linklocal6_cleanup (self);
-	addrconf6_cleanup (self);
-	dnsmasq_cleanup (self);
-	aipd_cleanup (self);
+	_cleanup_ip_pre (self, deconfigure);
 }
 
 static void
@@ -6457,20 +7134,33 @@ _cleanup_generic_post (NMDevice *self, gboolean deconfigure)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE;
 
+	priv->default_route.v4_has = FALSE;
+	priv->default_route.v4_is_assumed = TRUE;
+	priv->default_route.v6_has = FALSE;
+	priv->default_route.v6_is_assumed = TRUE;
+
+	nm_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), self);
+	nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), self);
+
 	/* Clean up IP configs; this does not actually deconfigure the
 	 * interface; the caller must flush routes and addresses explicitly.
 	 */
-	nm_device_set_ip4_config (self, NULL, TRUE, &ignored);
+	nm_device_set_ip4_config (self, NULL, 0, TRUE, &ignored);
 	nm_device_set_ip6_config (self, NULL, TRUE, &ignored);
 	g_clear_object (&priv->dev_ip4_config);
 	g_clear_object (&priv->ext_ip4_config);
+	g_clear_object (&priv->wwan_ip4_config);
 	g_clear_object (&priv->vpn4_config);
 	g_clear_object (&priv->ip4_config);
 	g_clear_object (&priv->ac_ip6_config);
 	g_clear_object (&priv->ext_ip6_config);
 	g_clear_object (&priv->vpn6_config);
+	g_clear_object (&priv->wwan_ip6_config);
 	g_clear_object (&priv->ip6_config);
 
+	priv->ext_ip4_config_had_any_addresses = FALSE;
+	priv->ext_ip6_config_had_any_addresses = FALSE;
+
 	clear_act_request (self);
 
 	/* Clear legacy IPv4 address property */
@@ -6507,13 +7197,10 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason)
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED) {
-		nm_log_info (LOGD_DEVICE, "(%s): preparing device",
-		             nm_device_get_iface (self));
-	} else {
-		nm_log_info (LOGD_DEVICE, "(%s): deactivating device (reason '%s') [%d]",
-		             nm_device_get_iface (self), reason_to_string (reason), reason);
-	}
+	if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED)
+		_LOGI (LOGD_DEVICE, "preparing device");
+	else
+		_LOGI (LOGD_DEVICE, "deactivating device (reason '%s') [%d]", reason_to_string (reason), reason);
 
 	/* Save whether or not we tried IPv6 for later */
 	priv = NM_DEVICE_GET_PRIVATE (self);
@@ -6521,7 +7208,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason)
 	_cleanup_generic_pre (self, TRUE);
 
 	/* Turn off kernel IPv6 */
-	nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
+	set_disable_ipv6 (self, "1");
 	nm_device_ipv6_sysctl_set (self, "accept_ra", "0");
 	nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0");
 
@@ -6547,6 +7234,170 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason)
 	_cleanup_generic_post (self, TRUE);
 }
 
+static char *
+bin2hexstr (const char *bytes, gsize len)
+{
+	GString *str;
+	int i;
+
+	g_return_val_if_fail (bytes != NULL, NULL);
+	g_return_val_if_fail (len > 0, NULL);
+
+	str = g_string_sized_new (len * 2 + 1);
+	for (i = 0; i < len; i++) {
+		if (str->len)
+			g_string_append_c (str, ':');
+		g_string_append_printf (str, "%02x", (guint8) bytes[i]);
+	}
+	return g_string_free (str, FALSE);
+}
+
+static char *
+find_dhcp4_address (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	guint i, n;
+
+	if (!priv->ip4_config)
+		return NULL;
+
+	n = nm_ip4_config_get_num_addresses (priv->ip4_config);
+	for (i = 0; i < n; i++) {
+		const NMPlatformIP4Address *a = nm_ip4_config_get_address (priv->ip4_config, i);
+
+		if (a->source == NM_IP_CONFIG_SOURCE_DHCP)
+			return g_strdup (nm_utils_inet4_ntop (a->address, NULL));
+	}
+	return NULL;
+}
+
+void
+nm_device_spawn_iface_helper (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean configured = FALSE;
+	NMConnection *connection;
+	GError *error = NULL;
+	const char *method;
+	GPtrArray *argv;
+	gs_free char *dhcp4_address = NULL;
+
+	if (priv->state != NM_DEVICE_STATE_ACTIVATED)
+		return;
+	if (!nm_device_can_assume_connections (self))
+		return;
+
+	connection = nm_device_get_connection (self);
+	g_assert (connection);
+
+	argv = g_ptr_array_sized_new (10);
+	g_ptr_array_set_free_func (argv, g_free);
+
+	g_ptr_array_add (argv, g_strdup (LIBEXECDIR "/nm-iface-helper"));
+	g_ptr_array_add (argv, g_strdup ("--ifname"));
+	g_ptr_array_add (argv, g_strdup (nm_device_get_ip_iface (self)));
+	g_ptr_array_add (argv, g_strdup ("--uuid"));
+	g_ptr_array_add (argv, g_strdup (nm_connection_get_uuid (connection)));
+
+	dhcp4_address = find_dhcp4_address (self);
+
+	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	if (   priv->ip4_config
+	    && priv->ip4_state == IP_DONE
+	    && g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0
+	    && priv->dhcp4_client
+	    && dhcp4_address) {
+		NMSettingIPConfig *s_ip4;
+		GBytes *client_id;
+		char *hex_client_id;
+		const char *hostname;
+
+		s_ip4 = nm_connection_get_setting_ip4_config (connection);
+		g_assert (s_ip4);
+
+		g_ptr_array_add (argv, g_strdup ("--priority4"));
+		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_ip4_route_metric (self)));
+
+		g_ptr_array_add (argv, g_strdup ("--dhcp4"));
+		g_ptr_array_add (argv, g_strdup (dhcp4_address));
+		if (nm_setting_ip_config_get_may_fail (s_ip4) == FALSE)
+			g_ptr_array_add (argv, g_strdup ("--dhcp4-required"));
+
+		client_id = nm_dhcp_client_get_client_id (priv->dhcp4_client);
+		if (client_id) {
+			g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid"));
+			hex_client_id = bin2hexstr (g_bytes_get_data (client_id, NULL),
+			                            g_bytes_get_size (client_id));
+			g_ptr_array_add (argv, hex_client_id);
+		}
+
+		hostname = nm_dhcp_client_get_hostname (priv->dhcp4_client);
+		if (client_id) {
+			g_ptr_array_add (argv, g_strdup ("--dhcp4-hostname"));
+			g_ptr_array_add (argv, g_strdup (hostname));
+		}
+
+		configured = TRUE;
+	}
+
+	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	if (   priv->ip6_config
+	    && priv->ip6_state == IP_DONE
+	    && g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
+	    && priv->rdisc
+	    && priv->ac_ip6_config) {
+		NMSettingIPConfig *s_ip6;
+		char *hex_iid;
+		NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
+
+		s_ip6 = nm_connection_get_setting_ip6_config (connection);
+		g_assert (s_ip6);
+
+		g_ptr_array_add (argv, g_strdup ("--priority6"));
+		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_ip6_route_metric (self)));
+
+		g_ptr_array_add (argv, g_strdup ("--slaac"));
+
+		if (nm_setting_ip_config_get_may_fail (s_ip6) == FALSE)
+			g_ptr_array_add (argv, g_strdup ("--slaac-required"));
+
+		g_ptr_array_add (argv, g_strdup ("--slaac-tempaddr"));
+		g_ptr_array_add (argv, g_strdup_printf ("%d", priv->rdisc_use_tempaddr));
+
+		if (nm_device_get_ip_iface_identifier (self, &iid)) {
+			g_ptr_array_add (argv, g_strdup ("--iid"));
+			hex_iid = bin2hexstr ((const char *) iid.id_u8, sizeof (NMUtilsIPv6IfaceId));
+			g_ptr_array_add (argv, hex_iid);
+		}
+
+		configured = TRUE;
+	}
+
+	if (configured) {
+		GPid pid;
+
+		g_ptr_array_add (argv, NULL);
+
+		if (nm_logging_enabled (LOGL_DEBUG, LOGD_DEVICE)) {
+			char *tmp;
+
+			tmp = g_strjoinv (" ", (char **) argv->pdata);
+			_LOGD (LOGD_DEVICE, "running '%s'", tmp);
+			g_free (tmp);
+		}
+
+		if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
+		                   G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, &error)) {
+			_LOGI (LOGD_DEVICE, "spawned helper PID %u", (guint) pid);
+		} else {
+			_LOGW (LOGD_DEVICE, "failed to spawn helper: %s", error->message);
+			g_error_free (error);
+		}
+	}
+
+	g_ptr_array_unref (argv);
+}
+
 /***********************************************************/
 
 static gboolean
@@ -6558,22 +7409,33 @@ ip_config_valid (NMDeviceState state)
 }
 
 static void
-notify_ip_properties (NMDevice *device)
+notify_ip_properties (NMDevice *self)
+{
+	g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_CONFIG);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_IP6_CONFIG);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG);
+}
+
+static void
+ip6_managed_setup (NMDevice *self)
 {
-	g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE);
-	g_object_notify (G_OBJECT (device), NM_DEVICE_IP4_CONFIG);
-	g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG);
-	g_object_notify (G_OBJECT (device), NM_DEVICE_IP6_CONFIG);
-	g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG);
+	set_nm_ipv6ll (self, TRUE);
+	set_disable_ipv6 (self, "1");
+	nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
+	nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0");
+	nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0");
+	nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0");
 }
 
 static void
-_set_state_full (NMDevice *device,
+_set_state_full (NMDevice *self,
                  NMDeviceState state,
                  NMDeviceStateReason reason,
                  gboolean quitting)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceState old_state;
 	NMActRequest *req;
 	gboolean no_firmware = FALSE;
@@ -6583,7 +7445,7 @@ _set_state_full (NMDevice *device,
 	g_warn_if_fail (priv->in_state_changed == FALSE);
 	priv->in_state_changed = TRUE;
 
-	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (NM_IS_DEVICE (self));
 
 	/* Do nothing if state isn't changing, but as a special case allow
 	 * re-setting UNAVAILABLE if the device is missing firmware so that we
@@ -6599,78 +7461,96 @@ _set_state_full (NMDevice *device,
 	priv->state = state;
 	priv->state_reason = reason;
 
-	nm_log_info (LOGD_DEVICE, "(%s): device state change: %s -> %s (reason '%s') [%d %d %d]",
-	             nm_device_get_iface (device),
-	             state_to_string (old_state),
-	             state_to_string (state),
-	             reason_to_string (reason),
-	             old_state,
-	             state,
-	             reason);
+	_LOGI (LOGD_DEVICE, "device state change: %s -> %s (reason '%s') [%d %d %d]",
+	       state_to_string (old_state),
+	       state_to_string (state),
+	       reason_to_string (reason),
+	       old_state,
+	       state,
+	       reason);
 
 	/* Clear any queued transitions */
-	nm_device_queued_state_clear (device);
+	nm_device_queued_state_clear (self);
 
-	dispatcher_cleanup (device);
+	dispatcher_cleanup (self);
 
 	/* Cache the activation request for the dispatcher */
 	req = priv->act_request ? g_object_ref (priv->act_request) : NULL;
 
 	if (state <= NM_DEVICE_STATE_UNAVAILABLE) {
-		_clear_available_connections (device, TRUE);
+		_clear_available_connections (self, TRUE);
 		g_clear_object (&priv->queued_act_request);
 	}
 
 	/* 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);
+	if (   (state >= NM_DEVICE_STATE_DISCONNECTED && old_state < NM_DEVICE_STATE_DISCONNECTED)
+	    || nm_device_get_default_unmanaged (self))
+		nm_device_recheck_available_connections (self);
 
 	/* Handle the new state here; but anything that could trigger
 	 * another state change should be done below.
 	 */
 	switch (state) {
 	case NM_DEVICE_STATE_UNMANAGED:
-		nm_device_set_firmware_missing (device, FALSE);
+		nm_device_set_firmware_missing (self, FALSE);
 		if (old_state > NM_DEVICE_STATE_UNMANAGED) {
 			/* Clean up if the device is now unmanaged but was activated */
-			if (nm_device_get_act_request (device))
-				nm_device_cleanup (device, reason);
-			nm_device_take_down (device, TRUE);
-			restore_ip6_properties (device);
+			if (nm_device_get_act_request (self))
+				nm_device_cleanup (self, reason);
+			nm_device_take_down (self, TRUE);
+			set_nm_ipv6ll (self, FALSE);
+			restore_ip6_properties (self);
 		}
 		break;
 	case NM_DEVICE_STATE_UNAVAILABLE:
 		if (old_state == NM_DEVICE_STATE_UNMANAGED) {
-			save_ip6_properties (device);
-			if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) {
-				nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1");
-				nm_device_ipv6_sysctl_set (device, "accept_ra_defrtr", "0");
-				nm_device_ipv6_sysctl_set (device, "accept_ra_pinfo", "0");
-				nm_device_ipv6_sysctl_set (device, "accept_ra_rtr_pref", "0");
-				nm_device_ipv6_sysctl_set (device, "use_tempaddr", "0");
-			}
+			save_ip6_properties (self);
+			if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED)
+				ip6_managed_setup (self);
 		}
 
-		if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) {
-			if (!nm_device_bring_up (device, TRUE, &no_firmware) && no_firmware)
-				nm_log_warn (LOGD_HW, "(%s): firmware may be missing.", nm_device_get_iface (device));
-			nm_device_set_firmware_missing (device, no_firmware ? TRUE : FALSE);
+		if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) {
+			if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) {
+				if (!nm_device_bring_up (self, TRUE, &no_firmware) && no_firmware)
+					_LOGW (LOGD_HW, "firmware may be missing.");
+				nm_device_set_firmware_missing (self, no_firmware ? TRUE : FALSE);
+			}
+
+			/* Ensure the device gets deactivated in response to stuff like
+			 * carrier changes or rfkill.  But don't deactivate devices that are
+			 * about to assume a connection since that defeats the purpose of
+			 * assuming the device's existing connection.
+			 *
+			 * Note that we "deactivate" the device even when coming from
+			 * UNMANAGED, to ensure that it's in a clean state.
+			 */
+			nm_device_cleanup (self, reason);
 		}
-		/* Ensure the device gets deactivated in response to stuff like
-		 * carrier changes or rfkill.  But don't deactivate devices that are
-		 * about to assume a connection since that defeats the purpose of
-		 * assuming the device's existing connection.
-		 *
-		 * Note that we "deactivate" the device even when coming from
-		 * UNMANAGED, to ensure that it's in a clean state.
-		 */
-		if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED)
-			nm_device_cleanup (device, reason);
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
-		if (old_state > NM_DEVICE_STATE_UNAVAILABLE)
-			nm_device_cleanup (device, reason);
+		if (old_state > NM_DEVICE_STATE_DISCONNECTED) {
+			/* Ensure devices that previously assumed a connection now have
+			 * userspace IPv6LL enabled.
+			 */
+			set_nm_ipv6ll (self, TRUE);
+
+			nm_device_cleanup (self, reason);
+		} else if (old_state < NM_DEVICE_STATE_DISCONNECTED) {
+			if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) {
+				/* Ensure IPv6 is set up as it may not have been done when
+				 * entering the UNAVAILABLE state depending on the reason.
+				 */
+				ip6_managed_setup (self);
+			}
+		}
+		break;
+	case NM_DEVICE_STATE_NEED_AUTH:
+		if (old_state > NM_DEVICE_STATE_NEED_AUTH) {
+			/* Clean up any half-done IP operations if the device's layer2
+			 * finds out it needs authentication during IP config.
+			 */
+			_cleanup_ip_pre (self, TRUE);
+		}
 		break;
 	default:
 		break;
@@ -6679,11 +7559,11 @@ _set_state_full (NMDevice *device,
 	/* Reset autoconnect flag when the device is activating or connected. */
 	if (   state >= NM_DEVICE_STATE_PREPARE
 	    && state <= NM_DEVICE_STATE_ACTIVATED)
-		priv->autoconnect = TRUE;
+		nm_device_set_autoconnect (self, TRUE);
 
-	g_object_notify (G_OBJECT (device), NM_DEVICE_STATE);
-	g_object_notify (G_OBJECT (device), NM_DEVICE_STATE_REASON);
-	g_signal_emit_by_name (device, "state-changed", state, old_state, reason);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_STATE);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_STATE_REASON);
+	g_signal_emit_by_name (self, "state-changed", state, old_state, reason);
 
 	/* Post-process the event after internal notification */
 
@@ -6695,35 +7575,33 @@ _set_state_full (NMDevice *device,
 		 * we can't change states again from the state handler for a variety of
 		 * reasons.
 		 */
-		if (nm_device_is_available (device)) {
-			nm_log_dbg (LOGD_DEVICE, "(%s): device is available, will transition to DISCONNECTED",
-			            nm_device_get_iface (device));
-			nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
+		if (nm_device_is_available (self)) {
+			_LOGD (LOGD_DEVICE, "device is available, will transition to DISCONNECTED");
+			nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
 		} else {
-			if (old_state == NM_DEVICE_STATE_UNMANAGED) {
-				nm_log_dbg (LOGD_DEVICE, "(%s): device not yet available for transition to DISCONNECTED",
-				            nm_device_get_iface (device));
-			} else if (   old_state > NM_DEVICE_STATE_UNAVAILABLE
-			           && nm_device_get_default_unmanaged (device))
-				nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE);
+			if (old_state == NM_DEVICE_STATE_UNMANAGED)
+				_LOGD (LOGD_DEVICE, "device not yet available for transition to DISCONNECTED");
+			else if (   old_state > NM_DEVICE_STATE_UNAVAILABLE
+			         && nm_device_get_default_unmanaged (self))
+				nm_device_queue_state (self, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE);
 		}
 		break;
 	case NM_DEVICE_STATE_DEACTIVATING:
 		if (quitting) {
 			nm_dispatcher_call_sync (DISPATCHER_ACTION_PRE_DOWN,
 			                         nm_act_request_get_connection (req),
-			                         device);
+			                         self);
 		} else {
 			priv->dispatcher.post_state = NM_DEVICE_STATE_DISCONNECTED;
 			priv->dispatcher.post_state_reason = reason;
 			if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_DOWN,
 			                         nm_act_request_get_connection (req),
-			                         device,
+			                         self,
 			                         dispatcher_complete_proceed_state,
-			                         device,
+			                         self,
 			                         &priv->dispatcher.call_id)) {
 				/* Just proceed on errors */
-				dispatcher_complete_proceed_state (0, device);
+				dispatcher_complete_proceed_state (0, self);
 			}
 		}
 		break;
@@ -6733,26 +7611,32 @@ _set_state_full (NMDevice *device,
 
 			queued_req = priv->queued_act_request;
 			priv->queued_act_request = NULL;
-			_device_activate (device, queued_req);
+			_device_activate (self, queued_req);
 			g_object_unref (queued_req);
 		} else if (   old_state > NM_DEVICE_STATE_DISCONNECTED
-		           && nm_device_get_default_unmanaged (device))
-			nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE);
+		           && nm_device_get_default_unmanaged (self))
+			nm_device_queue_state (self, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE);
 		break;
 	case NM_DEVICE_STATE_ACTIVATED:
-		nm_log_info (LOGD_DEVICE, "Activation (%s) successful, device activated.",
-		             nm_device_get_iface (device));
-		nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), device, NULL, NULL, NULL);
+		_LOGI (LOGD_DEVICE, "Activation: successful, device activated.");
+		nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), self, NULL, NULL, NULL);
 		break;
 	case NM_DEVICE_STATE_FAILED:
-		connection = nm_device_get_connection (device);
-		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
-		             "Activation (%s) failed for connection '%s'",
-		             nm_device_get_iface (device),
-		             connection ? nm_connection_get_id (connection) : "<unknown>");
+		if (nm_device_uses_assumed_connection (self)) {
+			/* Avoid tearing down assumed connection, assume it's connected */
+			nm_device_queue_state (self,
+			                       NM_DEVICE_STATE_ACTIVATED,
+			                       NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
+			break;
+		}
+
+		connection = nm_device_get_connection (self);
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: failed for connection '%s'",
+		       connection ? nm_connection_get_id (connection) : "<unknown>");
 
 		/* Notify any slaves of the unexpected failure */
-		nm_device_master_release_slaves (device);
+		nm_device_master_release_slaves (self);
 
 		/* 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
@@ -6769,41 +7653,40 @@ _set_state_full (NMDevice *device,
 		 * 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);
+		nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
 		break;
 	case NM_DEVICE_STATE_IP_CHECK:
-		nm_device_start_ip_check (device);
+		nm_device_start_ip_check (self);
 
 		/* IP-related properties are only valid when the device has IP configuration;
 		 * now that it does, ensure their change notifications are emitted.
 		 */
-		notify_ip_properties (device);
+		notify_ip_properties (self);
 		break;
 	case NM_DEVICE_STATE_SECONDARIES:
-		ip_check_gw_ping_cleanup (device);
-		nm_log_dbg (LOGD_DEVICE, "(%s): device entered SECONDARIES state",
-		            nm_device_get_iface (device));
+		ip_check_gw_ping_cleanup (self);
+		_LOGD (LOGD_DEVICE, "device entered SECONDARIES state");
 		break;
 	default:
 		break;
 	}
 
 	if (state > NM_DEVICE_STATE_DISCONNECTED)
-		delete_on_deactivate_unschedule (device);
+		delete_on_deactivate_unschedule (self);
 
 	if (   (old_state == NM_DEVICE_STATE_ACTIVATED || old_state == NM_DEVICE_STATE_DEACTIVATING)
 	    && (state != NM_DEVICE_STATE_DEACTIVATING)) {
 		if (quitting)
-			nm_dispatcher_call_sync (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device);
+			nm_dispatcher_call_sync (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), self);
 		else
-			nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device, NULL, NULL, NULL);
+			nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), self, NULL, NULL, NULL);
 	}
 
 	/* IP-related properties are only valid when the device has IP configuration.
 	 * If it no longer does, ensure their change notifications are emitted.
 	 */
 	if (ip_config_valid (old_state) && !ip_config_valid (state))
-	    notify_ip_properties (device);
+	    notify_ip_properties (self);
 
 	/* Dispose of the cached activation request */
 	if (req)
@@ -6813,11 +7696,11 @@ _set_state_full (NMDevice *device,
 }
 
 void
-nm_device_state_changed (NMDevice *device,
+nm_device_state_changed (NMDevice *self,
                          NMDeviceState state,
                          NMDeviceStateReason reason)
 {
-	_set_state_full (device, state, reason, FALSE);
+	_set_state_full (self, state, reason, FALSE);
 }
 
 static gboolean
@@ -6829,10 +7712,9 @@ queued_set_state (gpointer user_data)
 	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);
+		_LOGD (LOGD_DEVICE, "running queued state change to %s (id %d)",
+		       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.
@@ -6871,10 +7753,9 @@ nm_device_queue_state (NMDevice *self,
 
 	/* We should only ever have one delayed state transition at a time */
 	if (priv->queued_state.id) {
-		nm_log_warn (LOGD_DEVICE, "(%s): overwriting previously queued state change to %s (%s)",
-		             nm_device_get_iface (self),
-		             state_to_string (priv->queued_state.state),
-		             reason_to_string (priv->queued_state.reason));
+		_LOGW (LOGD_DEVICE, "overwriting previously queued state change to %s (%s)",
+		       state_to_string (priv->queued_state.state),
+		       reason_to_string (priv->queued_state.reason));
 		nm_device_queued_state_clear (self);
 	}
 
@@ -6882,9 +7763,9 @@ nm_device_queue_state (NMDevice *self,
 	priv->queued_state.reason = reason;
 	priv->queued_state.id = g_idle_add (queued_set_state, self);
 
-	nm_log_dbg (LOGD_DEVICE, "(%s): queued state change to %s due to %s (id %d)",
-	            nm_device_get_iface (self), state_to_string (state), reason_to_string (reason),
-	            priv->queued_state.id);
+	_LOGD (LOGD_DEVICE, "queued state change to %s due to %s (id %d)",
+	       state_to_string (state), reason_to_string (reason),
+	       priv->queued_state.id);
 }
 
 NMDeviceState
@@ -6905,8 +7786,8 @@ nm_device_queued_state_clear (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->queued_state.id) {
-		nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued state transition (id %d)",
-		            nm_device_get_iface (self), priv->queued_state.id);
+		_LOGD (LOGD_DEVICE, "clearing queued state transition (id %d)",
+		       priv->queued_state.id);
 		g_source_remove (priv->queued_state.id);
 		nm_device_remove_pending_action (self, queued_state_to_string (priv->queued_state.state), TRUE);
 	}
@@ -6914,152 +7795,112 @@ nm_device_queued_state_clear (NMDevice *self)
 }
 
 NMDeviceState
-nm_device_get_state (NMDevice *device)
+nm_device_get_state (NMDevice *self)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (device), NM_DEVICE_STATE_UNKNOWN);
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_STATE_UNKNOWN);
 
-	return NM_DEVICE_GET_PRIVATE (device)->state;
+	return NM_DEVICE_GET_PRIVATE (self)->state;
 }
 
 /***********************************************************/
 /* NMConfigDevice interface related stuff */
 
-static guint
-nm_device_get_hw_address_length (NMDevice *dev, gboolean *out_permanent)
-{
-	return NM_DEVICE_GET_CLASS (dev)->get_hw_address_length (dev, out_permanent);
-}
-
-const guint8 *
-nm_device_get_hw_address (NMDevice *dev, guint *out_len)
+const char *
+nm_device_get_hw_address (NMDevice *self)
 {
 	NMDevicePrivate *priv;
 
-	g_return_val_if_fail (NM_IS_DEVICE (dev), NULL);
-	priv = NM_DEVICE_GET_PRIVATE (dev);
-
-	if (out_len)
-		*out_len = priv->hw_addr_len;
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->hw_addr_len == 0)
-		return NULL;
-	else
-		return priv->hw_addr;
+	return priv->hw_addr_len ? priv->hw_addr : NULL;
 }
 
-gboolean
-nm_device_update_hw_address (NMDevice *dev)
+static void
+nm_device_update_hw_address (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
-	gboolean changed = FALSE, permanent = FALSE;
-
-	priv->hw_addr_len = nm_device_get_hw_address_length (dev, &permanent);
-
-	/* If the address can't be changed, don't bother trying */
-	if (permanent)
-		return FALSE;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	int ifindex = nm_device_get_ifindex (self);
+	const guint8 *hwaddr;
+	gsize hwaddrlen = 0;
 
-	if (priv->hw_addr_len) {
-		int ifindex = nm_device_get_ip_ifindex (dev);
-		gsize addrlen;
-		const guint8 *binaddr;
+	if (ifindex <= 0)
+		return;
 
-		g_return_val_if_fail (ifindex > 0, FALSE);
+	hwaddr = nm_platform_link_get_address (ifindex, &hwaddrlen);
 
-		binaddr = nm_platform_link_get_address (ifindex, &addrlen);
+	if (hwaddrlen) {
+		if (!priv->hw_addr || !nm_utils_hwaddr_matches (priv->hw_addr, -1, hwaddr, hwaddrlen)) {
+			g_free (priv->hw_addr);
+			priv->hw_addr = nm_utils_hwaddr_ntoa (hwaddr, hwaddrlen);
 
-		if (addrlen != priv->hw_addr_len) {
-			nm_log_err (LOGD_HW | LOGD_DEVICE,
-			            "(%s): hardware address is wrong length (got %zd, expected %d)",
-			            nm_device_get_iface (dev), addrlen, priv->hw_addr_len);
-		} else {
-			changed = !!memcmp (priv->hw_addr, binaddr, addrlen);
-			if (changed) {
-				char *addrstr = nm_utils_hwaddr_ntoa_len (binaddr, priv->hw_addr_len);
-
-				memcpy (priv->hw_addr, binaddr, addrlen);
-				nm_log_dbg (LOGD_HW | LOGD_DEVICE,
-				            "(%s): hardware address is %s",
-				            nm_device_get_iface (dev), addrstr);
-				g_free (addrstr);
-				g_object_notify (G_OBJECT (dev), NM_DEVICE_HW_ADDRESS);
-			}
+			_LOGD (LOGD_HW | LOGD_DEVICE, "hardware address now %s", priv->hw_addr);
+			g_object_notify (G_OBJECT (self), NM_DEVICE_HW_ADDRESS);
 		}
 	} else {
-		int i;
-
-		/* hw_addr_len is now 0; see if hw_addr was already empty */
-		for (i = 0; i < sizeof (priv->hw_addr) && !changed; i++) {
-			if (priv->hw_addr[i])
-				changed = TRUE;
-		}
-		if (changed) {
-			memset (priv->hw_addr, 0, sizeof (priv->hw_addr));
-			nm_log_dbg (LOGD_HW | LOGD_DEVICE,
-			            "(%s): previous hardware address is no longer valid",
-			            nm_device_get_iface (dev));
-			g_object_notify (G_OBJECT (dev), NM_DEVICE_HW_ADDRESS);
+		/* Invalid or no hardware address */
+		if (priv->hw_addr_len != 0) {
+			g_clear_pointer (&priv->hw_addr, g_free);
+			_LOGD (LOGD_HW | LOGD_DEVICE,
+			       "previous hardware address is no longer valid");
+			g_object_notify (G_OBJECT (self), NM_DEVICE_HW_ADDRESS);
 		}
 	}
-
-	return changed;
+	priv->hw_addr_len = hwaddrlen;
 }
 
 gboolean
-nm_device_set_hw_addr (NMDevice *device, const guint8 *addr,
+nm_device_set_hw_addr (NMDevice *self, const char *addr,
                        const char *detail, guint64 hw_log_domain)
 {
-	const char *iface;
-	char *mac_str = NULL;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean success = FALSE;
-	guint len;
-	const guint8 *cur_addr = nm_device_get_hw_address (device, &len);
+	const char *cur_addr = nm_device_get_hw_address (self);
+	guint8 addr_bytes[NM_UTILS_HWADDR_LEN_MAX];
 
 	g_return_val_if_fail (addr != NULL, FALSE);
 
-	iface = nm_device_get_iface (device);
-
 	/* Do nothing if current MAC is same */
-	if (cur_addr && !memcmp (cur_addr, addr, len)) {
-		nm_log_dbg (LOGD_DEVICE | hw_log_domain, "(%s): no MAC address change needed", iface);
+	if (cur_addr && nm_utils_hwaddr_matches (cur_addr, -1, addr, -1)) {
+		_LOGD (LOGD_DEVICE | hw_log_domain, "no MAC address change needed");
 		return TRUE;
 	}
-
-	mac_str = nm_utils_hwaddr_ntoa_len (addr, len);
+	if (!nm_utils_hwaddr_aton (addr, addr_bytes, priv->hw_addr_len)) {
+		_LOGW (LOGD_DEVICE | hw_log_domain, "invalid MAC address %s", addr);
+		return FALSE;
+	}
 
 	/* Can't change MAC address while device is up */
-	nm_device_take_down (device, FALSE);
+	nm_device_take_down (self, FALSE);
 
-	success = nm_platform_link_set_address (nm_device_get_ip_ifindex (device), addr, len);
+	success = nm_platform_link_set_address (nm_device_get_ip_ifindex (self), addr_bytes, priv->hw_addr_len);
 	if (success) {
 		/* MAC address succesfully changed; update the current MAC to match */
-		nm_device_update_hw_address (device);
-		cur_addr = nm_device_get_hw_address (device, NULL);
-		if (memcmp (cur_addr, addr, len) == 0) {
-			nm_log_info (LOGD_DEVICE | hw_log_domain, "(%s): %s MAC address to %s",
-			             iface, detail, mac_str);
+		nm_device_update_hw_address (self);
+		cur_addr = nm_device_get_hw_address (self);
+		if (cur_addr && nm_utils_hwaddr_matches (cur_addr, -1, addr, -1)) {
+			_LOGI (LOGD_DEVICE | hw_log_domain, "%s MAC address to %s",
+			       detail, addr);
 		} else {
-			nm_log_warn (LOGD_DEVICE | hw_log_domain, "(%s): new MAC address %s "
-			             "not successfully set",
-			             iface, mac_str);
+			_LOGW (LOGD_DEVICE | hw_log_domain,
+			       "new MAC address %s not successfully set", addr);
 			success = FALSE;
 		}
 	} else {
-		nm_log_warn (LOGD_DEVICE | hw_log_domain, "(%s): failed to %s MAC address to %s",
-		             iface, detail, mac_str);
+		_LOGW (LOGD_DEVICE | hw_log_domain, "failed to %s MAC address to %s",
+		       detail, addr);
 	}
-	nm_device_bring_up (device, TRUE, NULL);
-	g_free (mac_str);
+	nm_device_bring_up (self, TRUE, NULL);
 
 	return success;
 }
 
 /**
  * nm_device_spec_match_list:
- * @device: an #NMDevice
+ * @self: an #NMDevice
  * @specs: (element-type utf8): a list of device specs
  *
- * Checks if @device matches any of the specifications in @specs. The
+ * Checks if @self matches any of the specifications in @specs. The
  * currently-supported spec types are:
  *
  *     "mac:00:11:22:33:44:55" - matches a device with the given
@@ -7073,52 +7914,37 @@ nm_device_set_hw_addr (NMDevice *device, const guint8 *addr,
  *
  *     "*" - matches any device
  *
- * Returns: #TRUE if @device matches one of the specs in @specs
+ * Returns: #TRUE if @self matches one of the specs in @specs
  */
 gboolean
-nm_device_spec_match_list (NMDevice *device, const GSList *specs)
+nm_device_spec_match_list (NMDevice *self, const GSList *specs)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	if (!specs)
 		return FALSE;
 
-	return NM_DEVICE_GET_CLASS (device)->spec_match_list (device, specs);
+	return NM_DEVICE_GET_CLASS (self)->spec_match_list (self, specs);
 }
 
 static gboolean
-spec_match_list (NMDevice *device, const GSList *specs)
+spec_match_list (NMDevice *self, const GSList *specs)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
-	char *hwaddr_str;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean matched = FALSE;
 
 	if (nm_match_spec_string (specs, "*"))
 		return TRUE;
 
-	if (priv->hw_addr_len) {
-		hwaddr_str = nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len);
-		matched = nm_match_spec_hwaddr (specs, hwaddr_str);
-		g_free (hwaddr_str);
-	}
+	if (priv->hw_addr_len)
+		matched = nm_match_spec_hwaddr (specs, priv->hw_addr);
 
 	if (!matched)
-		matched = nm_match_spec_interface_name (specs, nm_device_get_iface (device));
+		matched = nm_match_spec_interface_name (specs, nm_device_get_iface (self));
 
 	return matched;
 }
 
-static guint
-get_hw_address_length (NMDevice *dev, gboolean *out_permanent)
-{
-	size_t len;
-
-	if (nm_platform_link_get_address (nm_device_get_ip_ifindex (dev), &len))
-		return len;
-	else
-		return 0;
-}
-
 /***********************************************************/
 
 #define DEFAULT_AUTOCONNECT TRUE
@@ -7138,13 +7964,9 @@ nm_device_init (NMDevice *self)
 	priv->unmanaged_flags = NM_UNMANAGED_INTERNAL;
 	priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
 	priv->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
-}
 
-static void
-nm_device_config_device_interface_init (NMConfigDeviceInterface *iface)
-{
-	iface->spec_match_list = (gboolean (*) (NMConfigDevice *, const GSList *)) nm_device_spec_match_list;
-	iface->get_hw_address = (const guint8 * (*) (NMConfigDevice *, guint *)) nm_device_get_hw_address;
+	priv->default_route.v4_is_assumed = TRUE;
+	priv->default_route.v6_is_assumed = TRUE;
 }
 
 /*
@@ -7152,7 +7974,7 @@ nm_device_config_device_interface_init (NMConfigDeviceInterface *iface)
  * Returns driver and firmware versions to 'driver_version and' 'firmware_version'
  */
 static gboolean
-device_get_driver_info (const char *iface, char **driver_version, char **firmware_version)
+device_get_driver_info (NMDevice *self, const char *iface, char **driver_version, char **firmware_version)
 {
 	struct ethtool_drvinfo drvinfo;
 	struct ifreq req;
@@ -7160,7 +7982,7 @@ device_get_driver_info (const char *iface, char **driver_version, char **firmwar
 
 	fd = socket (PF_INET, SOCK_DGRAM, 0);
 	if (fd < 0) {
-		nm_log_warn (LOGD_HW, "couldn't open control socket.");
+		_LOGW (LOGD_HW, "couldn't open control socket.");
 		return FALSE;
 	}
 
@@ -7173,8 +7995,8 @@ device_get_driver_info (const char *iface, char **driver_version, char **firmwar
 
 	errno = 0;
 	if (ioctl (fd, SIOCETHTOOL, &req) < 0) {
-		nm_log_dbg (LOGD_HW, "SIOCETHTOOL ioctl() failed: cmd=ETHTOOL_GDRVINFO, iface=%s, errno=%d",
-		            iface, errno);
+		_LOGD (LOGD_HW, "SIOCETHTOOL ioctl() failed: cmd=ETHTOOL_GDRVINFO, iface=%s, errno=%d",
+		       iface, errno);
 		close (fd);
 		return FALSE;
 	}
@@ -7193,7 +8015,7 @@ constructor (GType type,
              GObjectConstructParam *construct_params)
 {
 	GObject *object;
-	NMDevice *dev;
+	NMDevice *self;
 	NMDevicePrivate *priv;
 	NMPlatform *platform;
 	static guint32 id = 0;
@@ -7204,11 +8026,13 @@ constructor (GType type,
 	if (!object)
 		return NULL;
 
-	dev = NM_DEVICE (object);
-	priv = NM_DEVICE_GET_PRIVATE (dev);
+	self = NM_DEVICE (object);
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	_LOGD (LOGD_DEVICE, "constructor(): %s, kernel ifindex %d", G_OBJECT_TYPE_NAME (self), priv->ifindex);
 
 	if (!priv->iface) {
-		nm_log_err (LOGD_DEVICE, "No device interface provided, ignoring");
+		_LOGE (LOGD_DEVICE, "No device interface provided, ignoring");
 		goto error;
 	}
 
@@ -7217,52 +8041,60 @@ constructor (GType type,
 		priv->udi = g_strdup_printf ("/virtual/device/placeholder/%d", id++);
 	}
 
-	if (NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities)
-		priv->capabilities |= NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities (dev);
+	if (NM_DEVICE_GET_CLASS (self)->get_generic_capabilities)
+		priv->capabilities |= NM_DEVICE_GET_CLASS (self)->get_generic_capabilities (self);
 
-	priv->fw_manager = nm_firewall_manager_get ();
+	if (priv->ifindex <= 0 && !device_has_capability (self, NM_DEVICE_CAP_IS_NON_KERNEL))
+		_LOGW (LOGD_HW, "failed to look up interface index");
 
-	device_get_driver_info (priv->iface, &priv->driver_version, &priv->firmware_version);
+	device_get_driver_info (self, priv->iface, &priv->driver_version, &priv->firmware_version);
 
 	/* Watch for external IP config changes */
 	platform = nm_platform_get ();
-	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev);
-	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev);
-	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev);
-	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev);
-	g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), dev);
+	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), self);
+	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), self);
+	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), self);
+	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), self);
+	g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self);
+
+	if (nm_platform_check_support_user_ipv6ll ()) {
+		int ip_ifindex = nm_device_get_ip_ifindex (self);
+
+		if (ip_ifindex > 0)
+			priv->nm_ipv6ll = nm_platform_link_get_user_ipv6ll_enabled (ip_ifindex);
+	}
 
 	return object;
 
 error:
-	g_object_unref (dev);
+	g_object_unref (self);
 	return NULL;
 }
 
 static void
 constructed (GObject *object)
 {
-	NMDevice *dev = NM_DEVICE (object);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
+	NMDevice *self = NM_DEVICE (object);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	int master;
 
-	nm_device_update_hw_address (dev);
+	nm_device_update_hw_address (self);
 
-	if (NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address)
-		NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address (dev);
+	if (NM_DEVICE_GET_CLASS (self)->update_permanent_hw_address)
+		NM_DEVICE_GET_CLASS (self)->update_permanent_hw_address (self);
 
-	if (NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address)
-		NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address (dev);
+	if (NM_DEVICE_GET_CLASS (self)->update_initial_hw_address)
+		NM_DEVICE_GET_CLASS (self)->update_initial_hw_address (self);
 
 	/* Have to call update_initial_hw_address() before calling get_ignore_carrier() */
-	if (device_has_capability (dev, NM_DEVICE_CAP_CARRIER_DETECT)) {
-		priv->ignore_carrier = nm_config_get_ignore_carrier (nm_config_get (), NM_CONFIG_DEVICE (dev));
-
-		check_carrier (dev);
-		nm_log_info (LOGD_HW,
-		             "(%s): carrier is %s%s",
-		             nm_device_get_iface (NM_DEVICE (dev)),
-		             priv->carrier ? "ON" : "OFF",
-		             priv->ignore_carrier ? " (but ignored)" : "");
+	if (device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
+		priv->ignore_carrier = nm_config_get_ignore_carrier (nm_config_get (), self);
+
+		check_carrier (self);
+		_LOGI (LOGD_HW,
+		       "carrier is %s%s",
+		       priv->carrier ? "ON" : "OFF",
+		       priv->ignore_carrier ? " (but ignored)" : "");
 	} else {
 		/* Fake online link when carrier detection is not available. */
 		priv->carrier = TRUE;
@@ -7271,27 +8103,42 @@ constructed (GObject *object)
 	if (priv->ifindex > 0) {
 		priv->is_software = nm_platform_link_is_software (priv->ifindex);
 		priv->physical_port_id = nm_platform_link_get_physical_port_id (priv->ifindex);
+		priv->mtu = nm_platform_link_get_mtu (priv->ifindex);
 	}
+	/* Indicate software device in capabilities. */
+	if (priv->is_software)
+		priv->capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
 
-	if (priv->ifindex > 0)
-		priv->mtu = nm_platform_link_get_mtu (priv->ifindex);
+	/* Enslave ourselves */
+	master = nm_platform_link_get_master (priv->ifindex);
+	if (master)
+		device_set_master (self, master);
 
 	priv->con_provider = nm_connection_provider_get ();
 	g_assert (priv->con_provider);
 	g_signal_connect (priv->con_provider,
 	                  NM_CP_SIGNAL_CONNECTION_ADDED,
 	                  G_CALLBACK (cp_connection_added),
-	                  dev);
+	                  self);
 
 	g_signal_connect (priv->con_provider,
 	                  NM_CP_SIGNAL_CONNECTION_REMOVED,
 	                  G_CALLBACK (cp_connection_removed),
-	                  dev);
+	                  self);
 
 	g_signal_connect (priv->con_provider,
 	                  NM_CP_SIGNAL_CONNECTION_UPDATED,
 	                  G_CALLBACK (cp_connection_updated),
-	                  dev);
+	                  self);
+
+	/* Update default-unmanaged device available connections immediately,
+	 * since they don't transition from UNMANAGED (and thus the state handler
+	 * doesn't run and update them) until something external happens.
+	 */
+	if (nm_device_get_default_unmanaged (self)) {
+		nm_device_set_autoconnect (self, FALSE);
+		nm_device_recheck_available_connections (self);
+	}
 
 	G_OBJECT_CLASS (nm_device_parent_class)->constructed (object);
 }
@@ -7303,6 +8150,8 @@ dispose (GObject *object)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatform *platform;
 
+	_LOGD (LOGD_DEVICE, "dispose(): %s", G_OBJECT_TYPE_NAME (self));
+
 	dispatcher_cleanup (self);
 
 	_cleanup_generic_pre (self, FALSE);
@@ -7310,6 +8159,9 @@ dispose (GObject *object)
 	g_warn_if_fail (priv->slaves == NULL);
 	g_assert (priv->master_ready_id == 0);
 
+	/* Let the kernel manage IPv6LL again */
+	set_nm_ipv6ll (self, FALSE);
+
 	_cleanup_generic_post (self, FALSE);
 
 	g_clear_pointer (&priv->ip6_saved_properties, g_hash_table_unref);
@@ -7342,8 +8194,6 @@ dispose (GObject *object)
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ip_changed), self);
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self);
 
-	g_clear_object (&priv->fw_manager);
-
 	G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
 }
 
@@ -7353,6 +8203,9 @@ finalize (GObject *object)
 	NMDevice *self = NM_DEVICE (object);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	_LOGD (LOGD_DEVICE, "finalize(): %s", G_OBJECT_TYPE_NAME (self));
+
+	g_free (priv->hw_addr);
 	g_slist_free_full (priv->pending_actions, g_free);
 	g_clear_pointer (&priv->physical_port_id, g_free);
 	g_free (priv->udi);
@@ -7363,8 +8216,7 @@ finalize (GObject *object)
 	g_free (priv->driver_version);
 	g_free (priv->firmware_version);
 	g_free (priv->type_desc);
-	if (priv->dhcp_anycast_address)
-		g_byte_array_free (priv->dhcp_anycast_address, TRUE);
+	g_free (priv->dhcp_anycast_address);
 
 	G_OBJECT_CLASS (nm_device_parent_class)->finalize (object);
 }
@@ -7373,9 +8225,11 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (object);
+	NMDevice *self = NM_DEVICE (object);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatformLink *platform_device;
-	const char *hw_addr;
+	const char *hw_addr, *p;
+	guint count;
  
 	switch (prop_id) {
 	case PROP_PLATFORM_DEVICE:
@@ -7386,6 +8240,7 @@ set_property (GObject *object, guint prop_id,
 			g_free (priv->iface);
 			priv->iface = g_strdup (platform_device->name);
 			priv->ifindex = platform_device->ifindex;
+			priv->up = platform_device->up;
 			g_free (priv->driver);
 			priv->driver = g_strdup (platform_device->driver);
 		}
@@ -7399,18 +8254,10 @@ set_property (GObject *object, guint prop_id,
 	case PROP_IFACE:
 		if (g_value_get_string (value)) {
 			g_free (priv->iface);
-			priv->ifindex = 0;
 			priv->iface = g_value_dup_string (value);
-
-			/* Only look up the ifindex if it appears to be an actual kernel
-			 * interface name.  eg Bluetooth devices won't have one until we know
-			 * the IP interface.
-			 */
-			if (priv->iface && !strchr (priv->iface, ':')) {
-				priv->ifindex = nm_platform_link_get_ifindex (priv->iface);
-				if (priv->ifindex <= 0)
-					nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", priv->iface);
-			}
+			priv->ifindex = nm_platform_link_get_ifindex (priv->iface);
+			if (priv->ifindex > 0)
+				priv->up = nm_platform_link_is_up (priv->ifindex);
 		}
 		break;
 	case PROP_DRIVER:
@@ -7434,7 +8281,7 @@ set_property (GObject *object, guint prop_id,
 		priv->ip4_address = g_value_get_uint (value);
 		break;
 	case PROP_AUTOCONNECT:
-		priv->autoconnect = g_value_get_boolean (value);
+		nm_device_set_autoconnect (self, g_value_get_boolean (value));
 		break;
 	case PROP_FIRMWARE_MISSING:
 		priv->firmware_missing = g_value_get_boolean (value);
@@ -7454,19 +8301,30 @@ set_property (GObject *object, guint prop_id,
 		priv->is_master = g_value_get_boolean (value);
 		break;
 	case PROP_HW_ADDRESS:
-		priv->hw_addr_len = nm_device_get_hw_address_length (NM_DEVICE (object), NULL);
-
-		hw_addr = g_value_get_string (value);
-		if (!hw_addr)
-			break;
-		if (priv->hw_addr_len == 0) {
-			g_warn_if_fail (*hw_addr == '\0');
+		/* construct only */
+		p = hw_addr = g_value_get_string (value);
+
+		/* Hardware address length is the number of ':' plus 1 */
+		count = 1;
+		while (p && *p) {
+			if (*p++ == ':')
+				count++;
+		}
+		if (count < ETH_ALEN || count > NM_UTILS_HWADDR_LEN_MAX) {
+			if (hw_addr && *hw_addr) {
+				_LOGW (LOGD_DEVICE, "ignoring hardware address '%s' with unexpected length %d",
+				       hw_addr, count);
+			}
 			break;
 		}
 
-		if (!nm_utils_hwaddr_aton_len (hw_addr, priv->hw_addr, priv->hw_addr_len)) {
-			g_warning ("Could not parse hw-address '%s'", hw_addr);
-			memset (priv->hw_addr, 0, sizeof (priv->hw_addr));
+		priv->hw_addr_len = count;
+		g_free (priv->hw_addr);
+		if (nm_utils_hwaddr_valid (hw_addr, priv->hw_addr_len))
+			priv->hw_addr = g_strdup (hw_addr);
+		else {
+			_LOGW (LOGD_DEVICE, "could not parse hw-address '%s'", hw_addr);
+			priv->hw_addr = NULL;
 		}
 		break;
 	default:
@@ -7596,10 +8454,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_object (value, priv->master);
 		break;
 	case PROP_HW_ADDRESS:
-		if (priv->hw_addr_len)
-			g_value_take_string (value, nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len));
-		else
-			g_value_set_string (value, NULL);
+		g_value_set_string (value, priv->hw_addr);
 		break;
 	case PROP_HAS_PENDING_ACTION:
 		g_value_set_boolean (value, nm_device_has_pending_action (self));
@@ -7644,257 +8499,226 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->bring_up = bring_up;
 	klass->take_down = take_down;
 	klass->carrier_changed = carrier_changed;
-	klass->get_hw_address_length = get_hw_address_length;
+	klass->get_ip_iface_identifier = get_ip_iface_identifier;
 
 	/* Properties */
 	g_object_class_install_property
 		(object_class, PROP_PLATFORM_DEVICE,
-		 g_param_spec_pointer (NM_DEVICE_PLATFORM_DEVICE,
-		                       "Platform Device",
-		                       "NMPlatform device object",
-		                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+		 g_param_spec_pointer (NM_DEVICE_PLATFORM_DEVICE, "", "",
+		                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+		                       G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_UDI,
-		 g_param_spec_string (NM_DEVICE_UDI,
-		                      "UDI",
-		                      "Unique Device Identifier",
+		 g_param_spec_string (NM_DEVICE_UDI, "", "",
 		                      NULL,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_IFACE,
-		 g_param_spec_string (NM_DEVICE_IFACE,
-		                      "Interface",
-		                      "Interface",
+		 g_param_spec_string (NM_DEVICE_IFACE, "", "",
 		                      NULL,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_IP_IFACE,
-		 g_param_spec_string (NM_DEVICE_IP_IFACE,
-		                      "IP Interface",
-		                      "IP Interface",
+		 g_param_spec_string (NM_DEVICE_IP_IFACE, "", "",
 		                      NULL,
-		                      G_PARAM_READABLE));
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_DRIVER,
-		 g_param_spec_string (NM_DEVICE_DRIVER,
-		                      "Driver",
-		                      "Driver",
+		 g_param_spec_string (NM_DEVICE_DRIVER, "", "",
 		                      NULL,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_DRIVER_VERSION,
-		 g_param_spec_string (NM_DEVICE_DRIVER_VERSION,
-		                      "Driver Version",
-		                      "Driver Version",
+		 g_param_spec_string (NM_DEVICE_DRIVER_VERSION, "", "",
 		                      NULL,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_FIRMWARE_VERSION,
-		 g_param_spec_string (NM_DEVICE_FIRMWARE_VERSION,
-		                      "Firmware Version",
-		                      "Firmware Version",
+		 g_param_spec_string (NM_DEVICE_FIRMWARE_VERSION, "", "",
 		                      NULL,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_CAPABILITIES,
-		 g_param_spec_uint (NM_DEVICE_CAPABILITIES,
-		                    "Capabilities",
-		                    "Capabilities",
+		 g_param_spec_uint (NM_DEVICE_CAPABILITIES, "", "",
 		                    0, G_MAXUINT32, NM_DEVICE_CAP_NONE,
-		                    G_PARAM_READABLE));
+		                    G_PARAM_READABLE |
+		                    G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_CARRIER,
-		 g_param_spec_boolean (NM_DEVICE_CARRIER,
-		                       "Carrier",
-		                       "Carrier",
+		 g_param_spec_boolean (NM_DEVICE_CARRIER, "", "",
 		                       FALSE,
-		                       G_PARAM_READABLE));
+		                       G_PARAM_READABLE |
+		                       G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_MTU,
-		 g_param_spec_uint (NM_DEVICE_MTU,
-		                    "MTU",
-		                    "MTU",
+		 g_param_spec_uint (NM_DEVICE_MTU, "", "",
 		                    0, G_MAXUINT32, 1500,
-		                    G_PARAM_READABLE));
+		                    G_PARAM_READABLE |
+		                    G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_IP4_ADDRESS,
-		 g_param_spec_uint (NM_DEVICE_IP4_ADDRESS,
-		                    "IP4 address",
-		                    "IP4 address",
+		 g_param_spec_uint (NM_DEVICE_IP4_ADDRESS, "", "",
 		                    0, G_MAXUINT32, 0, /* FIXME */
-		                    G_PARAM_READWRITE));
+		                    G_PARAM_READWRITE |
+		                    G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_IP4_CONFIG,
-		 g_param_spec_boxed (NM_DEVICE_IP4_CONFIG,
-		                     "IP4 Config",
-		                     "IP4 Config",
+		 g_param_spec_boxed (NM_DEVICE_IP4_CONFIG, "", "",
 		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READWRITE));
+		                     G_PARAM_READWRITE |
+		                     G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_DHCP4_CONFIG,
-		 g_param_spec_boxed (NM_DEVICE_DHCP4_CONFIG,
-		                     "DHCP4 Config",
-		                     "DHCP4 Config",
+		 g_param_spec_boxed (NM_DEVICE_DHCP4_CONFIG, "", "",
 		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READWRITE));
+		                     G_PARAM_READWRITE |
+		                     G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_IP6_CONFIG,
-		 g_param_spec_boxed (NM_DEVICE_IP6_CONFIG,
-		                     "IP6 Config",
-		                     "IP6 Config",
+		 g_param_spec_boxed (NM_DEVICE_IP6_CONFIG, "", "",
 		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READWRITE));
+		                     G_PARAM_READWRITE |
+		                     G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_DHCP6_CONFIG,
-		 g_param_spec_boxed (NM_DEVICE_DHCP6_CONFIG,
-		                     "DHCP6 Config",
-		                     "DHCP6 Config",
+		 g_param_spec_boxed (NM_DEVICE_DHCP6_CONFIG, "", "",
 		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READWRITE));
+		                     G_PARAM_READWRITE |
+		                     G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_STATE,
-		 g_param_spec_uint (NM_DEVICE_STATE,
-		                    "State",
-		                    "State",
+		 g_param_spec_uint (NM_DEVICE_STATE, "", "",
 		                    0, G_MAXUINT32, NM_DEVICE_STATE_UNKNOWN,
-		                    G_PARAM_READABLE));
+		                    G_PARAM_READABLE |
+		                    G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_STATE_REASON,
-		 g_param_spec_boxed (NM_DEVICE_STATE_REASON,
-		                     "StateReason",
-		                     "StateReason",
+		 g_param_spec_boxed (NM_DEVICE_STATE_REASON, "", "",
 		                     DBUS_TYPE_STATE_REASON_STRUCT,
-		                     G_PARAM_READABLE));
+		                     G_PARAM_READABLE |
+		                     G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_ACTIVE_CONNECTION,
-		 g_param_spec_boxed (NM_DEVICE_ACTIVE_CONNECTION,
-		                     "ActiveConnection",
-		                     "ActiveConnection",
+		 g_param_spec_boxed (NM_DEVICE_ACTIVE_CONNECTION, "", "",
 		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READABLE));
+		                     G_PARAM_READABLE |
+		                     G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_DEVICE_TYPE,
-		 g_param_spec_uint (NM_DEVICE_DEVICE_TYPE,
-		                    "DeviceType",
-		                    "DeviceType",
+		 g_param_spec_uint (NM_DEVICE_DEVICE_TYPE, "", "",
 		                    0, G_MAXUINT32, NM_DEVICE_TYPE_UNKNOWN,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                    G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_MANAGED,
-		 g_param_spec_boolean (NM_DEVICE_MANAGED,
-		                       "Managed",
-		                       "Managed",
+		 g_param_spec_boolean (NM_DEVICE_MANAGED, "", "",
 		                       FALSE,
-		                       G_PARAM_READABLE));
+		                       G_PARAM_READABLE |
+		                       G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_AUTOCONNECT,
-		 g_param_spec_boolean (NM_DEVICE_AUTOCONNECT,
-		                       "Autoconnect",
-		                       "Autoconnect",
+		 g_param_spec_boolean (NM_DEVICE_AUTOCONNECT, "", "",
 		                       DEFAULT_AUTOCONNECT,
-		                       G_PARAM_READWRITE));
+		                       G_PARAM_READWRITE |
+		                       G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_FIRMWARE_MISSING,
-		 g_param_spec_boolean (NM_DEVICE_FIRMWARE_MISSING,
-		                       "FirmwareMissing",
-		                       "Firmware missing",
+		 g_param_spec_boolean (NM_DEVICE_FIRMWARE_MISSING, "", "",
 		                       FALSE,
-		                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                       G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_TYPE_DESC,
-		 g_param_spec_string (NM_DEVICE_TYPE_DESC,
-		                      "Type Description",
-		                      "Device type description",
+		 g_param_spec_string (NM_DEVICE_TYPE_DESC, "", "",
 		                      NULL,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_RFKILL_TYPE,
-		 g_param_spec_uint (NM_DEVICE_RFKILL_TYPE,
-		                    "Rfkill Type",
-		                    "Type of rfkill switch (if any) supported by this device",
+		 g_param_spec_uint (NM_DEVICE_RFKILL_TYPE, "", "",
 		                    RFKILL_TYPE_WLAN,
 		                    RFKILL_TYPE_MAX,
 		                    RFKILL_TYPE_UNKNOWN,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                    G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_IFINDEX,
-		 g_param_spec_int (NM_DEVICE_IFINDEX,
-		                   "Ifindex",
-		                   "Ifindex",
+		 g_param_spec_int (NM_DEVICE_IFINDEX, "", "",
 		                   0, G_MAXINT, 0,
-		                   G_PARAM_READABLE));
+		                   G_PARAM_READABLE |
+		                   G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_AVAILABLE_CONNECTIONS,
-		 g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS,
-		                     "AvailableConnections",
-		                     "AvailableConnections",
+		 g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS, "", "",
 		                     DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH,
-		                     G_PARAM_READABLE));
+		                     G_PARAM_READABLE |
+		                     G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_PHYSICAL_PORT_ID,
-		 g_param_spec_string (NM_DEVICE_PHYSICAL_PORT_ID,
-		                      "PhysicalPortId",
-		                      "PhysicalPortId",
+		 g_param_spec_string (NM_DEVICE_PHYSICAL_PORT_ID, "", "",
 		                      NULL,
-		                      G_PARAM_READABLE));
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_IS_MASTER,
-		 g_param_spec_boolean (NM_DEVICE_IS_MASTER,
-		                       "IsMaster",
-		                       "IsMaster",
+		 g_param_spec_boolean (NM_DEVICE_IS_MASTER, "", "",
 		                       FALSE,
-		                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                       G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_MASTER,
-		 g_param_spec_object (NM_DEVICE_MASTER,
-		                      "Master",
-		                      "Master",
+		 g_param_spec_object (NM_DEVICE_MASTER, "", "",
 		                      NM_TYPE_DEVICE,
-		                      G_PARAM_READABLE));
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_HW_ADDRESS,
-		 g_param_spec_string (NM_DEVICE_HW_ADDRESS,
-		                      "Hardware Address",
-		                      "Hardware address",
+		 g_param_spec_string (NM_DEVICE_HW_ADDRESS, "", "",
 		                      NULL,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_HAS_PENDING_ACTION,
-		 g_param_spec_boolean (NM_DEVICE_HAS_PENDING_ACTION,
-		                       "Has pending action",
-		                       "Has pending action",
+		 g_param_spec_boolean (NM_DEVICE_HAS_PENDING_ACTION, "", "",
 		                       FALSE,
-		                       G_PARAM_READABLE));
+		                       G_PARAM_READABLE |
+		                       G_PARAM_STATIC_STRINGS));
 
 	/* Signals */
 	signals[STATE_CHANGED] =