about summary refs log tree commit diff
path: root/src/devices/nm-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/nm-device.c')
-rw-r--r--src/devices/nm-device.c3971
1 files changed, 2706 insertions, 1265 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index bb39ca55..93782a45 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -21,9 +21,6 @@
 
 #include "config.h"
 
-#include <glib.h>
-#include <glib/gi18n.h>
-#include <dbus/dbus.h>
 #include <netinet/in.h>
 #include <string.h>
 #include <unistd.h>
@@ -35,9 +32,9 @@
 #include <arpa/inet.h>
 #include <fcntl.h>
 #include <netlink/route/addr.h>
+#include <linux/if_addr.h>
 
-#include "gsystem-local-alloc.h"
-#include "nm-glib-compat.h"
+#include "nm-default.h"
 #include "nm-device.h"
 #include "nm-device-private.h"
 #include "NetworkManagerUtils.h"
@@ -46,8 +43,6 @@
 #include "nm-rdisc.h"
 #include "nm-lndp-rdisc.h"
 #include "nm-dhcp-manager.h"
-#include "nm-dbus-manager.h"
-#include "nm-logging.h"
 #include "nm-activation-request.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
@@ -56,35 +51,35 @@
 #include "nm-dhcp6-config.h"
 #include "nm-rfkill-manager.h"
 #include "nm-firewall-manager.h"
-#include "nm-properties-changed-signal.h"
 #include "nm-enum-types.h"
 #include "nm-settings-connection.h"
 #include "nm-connection-provider.h"
 #include "nm-auth-utils.h"
-#include "nm-dbus-glib-types.h"
 #include "nm-dispatcher.h"
 #include "nm-config.h"
 #include "nm-dns-manager.h"
 #include "nm-core-internal.h"
 #include "nm-default-route-manager.h"
 #include "nm-route-manager.h"
+#include "nm-lldp-listener.h"
+#include "sd-ipv4ll.h"
+#include "nm-audit-manager.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF (NMDevice);
 
-static void impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context);
-static void impl_device_delete     (NMDevice *self, DBusGMethodInvocation *context);
+#include "nmdbus-device.h"
+
 static void ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data);
 static gboolean ip_config_valid (NMDeviceState state);
-static void nm_device_update_metered (NMDevice *self);
 static NMActStageReturn dhcp4_start (NMDevice *self, NMConnection *connection, NMDeviceStateReason *reason);
 static gboolean dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason);
+static void nm_device_start_ip_check (NMDevice *self);
+static void realize_start_setup (NMDevice *self, const NMPlatformLink *plink);
 
-#include "nm-device-glue.h"
-
-G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, G_TYPE_OBJECT)
+G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_EXPORTED_OBJECT)
 
-#define NM_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE, NMDevicePrivate))
+#define NM_DEVICE_GET_PRIVATE(o) ((o)->priv)
 
 enum {
 	STATE_CHANGED,
@@ -95,14 +90,12 @@ enum {
 	REMOVED,
 	RECHECK_AUTO_ACTIVATE,
 	RECHECK_ASSUME,
-	LINK_INITIALIZED,
 	LAST_SIGNAL,
 };
 static guint signals[LAST_SIGNAL] = { 0 };
 
 enum {
 	PROP_0,
-	PROP_PLATFORM_DEVICE,
 	PROP_UDI,
 	PROP_IFACE,
 	PROP_IP_IFACE,
@@ -121,9 +114,11 @@ enum {
 	PROP_STATE_REASON,
 	PROP_ACTIVE_CONNECTION,
 	PROP_DEVICE_TYPE,
+	PROP_LINK_TYPE,
 	PROP_MANAGED,
 	PROP_AUTOCONNECT,
 	PROP_FIRMWARE_MISSING,
+	PROP_NM_PLUGIN_MISSING,
 	PROP_TYPE_DESC,
 	PROP_RFKILL_TYPE,
 	PROP_IFINDEX,
@@ -134,15 +129,27 @@ enum {
 	PROP_HW_ADDRESS,
 	PROP_HAS_PENDING_ACTION,
 	PROP_METERED,
+	PROP_LLDP_NEIGHBORS,
+	PROP_REAL,
+	PROP_SLAVES,
 	LAST_PROP
 };
 
+#define DEFAULT_AUTOCONNECT TRUE
+
 /***********************************************************/
 
 #define PENDING_ACTION_DHCP4 "dhcp4"
 #define PENDING_ACTION_DHCP6 "dhcp6"
 #define PENDING_ACTION_AUTOCONF6 "autoconf6"
 
+typedef void (*ActivationHandleFunc) (NMDevice *self);
+
+typedef struct {
+	ActivationHandleFunc func;
+	guint id;
+} ActivationHandleData;
+
 typedef enum {
 	CLEANUP_TYPE_DECONFIGURE,
 	CLEANUP_TYPE_KEEP,
@@ -165,13 +172,13 @@ typedef struct {
 
 typedef struct {
 	NMDevice *slave;
-	gboolean enslaved;
+	gboolean slave_is_enslaved;
 	gboolean configure;
-	guint watch_id;
+	gulong watch_id;
 } SlaveInfo;
 
 typedef struct {
-	guint log_domain;
+	NMLogDomain log_domain;
 	guint timeout;
 	guint watch;
 	GPid pid;
@@ -186,7 +193,7 @@ typedef struct {
 	int ifindex;
 } DeleteOnDeactivateData;
 
-typedef struct {
+typedef struct _NMDevicePrivate {
 	gboolean in_state_changed;
 	gboolean initialized;
 	gboolean platform_link_initialized;
@@ -200,22 +207,25 @@ typedef struct {
 	guint queued_ip4_config_id;
 	guint queued_ip6_config_id;
 	GSList *pending_actions;
+	GSList *dad6_failed_addrs;
 
 	char *        udi;
-	char *        path;
 	char *        iface;   /* may change, could be renamed by user */
 	int           ifindex;
+	gboolean      real;
 	char *        ip_iface;
 	int           ip_ifindex;
 	NMDeviceType  type;
 	char *        type_desc;
 	char *        type_description;
+	NMLinkType    link_type;
 	NMDeviceCapabilities capabilities;
 	char *        driver;
 	char *        driver_version;
 	char *        firmware_version;
 	RfKillType    rfkill_type;
 	gboolean      firmware_missing;
+	gboolean      nm_plugin_missing;
 	GHashTable *  available_connections;
 	char *        hw_addr;
 	guint         hw_addr_len;
@@ -224,6 +234,7 @@ typedef struct {
 	char *        physical_port_id;
 	guint         dev_id;
 
+	gboolean                managed_touched_by_user;
 	NMUnmanagedFlags        unmanaged_flags;
 	gboolean                is_nm_owned; /* whether the device is a device owned and created by NM */
 	DeleteOnDeactivateData *delete_on_deactivate_data; /* data for scheduled cleanup when deleting link (g_idle_add) */
@@ -235,13 +246,11 @@ typedef struct {
 	NMActRequest *  queued_act_request;
 	gboolean        queued_act_request_is_waiting_for_carrier;
 	NMActRequest *  act_request;
-	guint           act_source_id;
-	gpointer        act_source_func;
-	guint           act_source6_id;
-	gpointer        act_source6_func;
+	ActivationHandleData act_handle4; /* for layer2 and IPv4. */
+	ActivationHandleData act_handle6;
 	guint           recheck_assume_id;
 	struct {
-		guint       		call_id;
+		guint               call_id;
 		NMDeviceStateReason available_reason;
 		NMDeviceStateReason unavailable_reason;
 	}               recheck_available;
@@ -272,6 +281,7 @@ typedef struct {
 	NMIP4Config *   dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
 	NMIP4Config *   ext_ip4_config; /* Stuff added outside NM */
 	NMIP4Config *   wwan_ip4_config; /* WWAN configuration */
+	GSList *        vpn4_configs;   /* VPNs which use this device */
 	struct {
 		gboolean v4_has;
 		gboolean v4_is_assumed;
@@ -289,7 +299,6 @@ typedef struct {
 	gulong          dhcp4_state_sigid;
 	NMDhcp4Config * dhcp4_config;
 	guint           dhcp4_restart_id;
-	NMIP4Config *   vpn4_config;  /* routes added by a VPN which uses this device */
 
 	guint           arp_round2_id;
 	PingInfo        gw_ping;
@@ -299,20 +308,20 @@ typedef struct {
 	gulong            dnsmasq_state_id;
 
 	/* Firewall */
-	NMFirewallPendingCall fw_call;
+	gboolean       fw_ready;
+	NMFirewallManagerCallId fw_call;
 
-	/* avahi-autoipd stuff */
-	GPid    aipd_pid;
-	guint   aipd_watch;
-	guint   aipd_timeout;
+	/* IPv4LL stuff */
+	sd_ipv4ll *    ipv4ll;
+	guint          ipv4ll_timeout;
 
 	/* IP6 configuration info */
 	NMIP6Config *  ip6_config;
 	IpState        ip6_state;
 	NMIP6Config *  con_ip6_config; /* config from the setting */
-	NMIP6Config *  vpn6_config;  /* routes added by a VPN which uses this device */
 	NMIP6Config *  wwan_ip6_config;
 	NMIP6Config *  ext_ip6_config; /* Stuff added outside NM */
+	GSList *       vpn6_configs;   /* VPNs which use this device */
 	gboolean       nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
 	guint32        ip6_mtu;
 
@@ -324,6 +333,7 @@ typedef struct {
 	NMIP6Config *  ac_ip6_config;
 
 	guint          linklocal6_timeout_id;
+	guint8         linklocal6_dad_counter;
 
 	GHashTable *   ip6_saved_properties;
 
@@ -342,9 +352,9 @@ typedef struct {
 
 	/* master interface for bridge/bond/team slave */
 	NMDevice *      master;
-	gboolean        enslaved;
+	gboolean        is_enslaved;
 	gboolean        master_ready_handled;
-	guint           master_ready_id;
+	gulong          master_ready_id;
 
 	/* slave management */
 	gboolean        is_master;
@@ -353,6 +363,9 @@ typedef struct {
 	NMMetered       metered;
 
 	NMConnectionProvider *con_provider;
+	NMLldpListener *lldp_listener;
+
+	guint check_delete_unrealized_id;
 } NMDevicePrivate;
 
 static gboolean nm_device_set_ip4_config (NMDevice *self,
@@ -372,7 +385,7 @@ static gboolean nm_device_set_ip6_config (NMDevice *self,
                                           gboolean routes_full_sync,
                                           NMDeviceStateReason *reason);
 
-static gboolean nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure);
+static void 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);
 
@@ -384,12 +397,20 @@ static void _carrier_wait_check_queued_act_request (NMDevice *self);
 
 static gboolean nm_device_get_default_unmanaged (NMDevice *self);
 
+static const char *_activation_func_to_string (ActivationHandleFunc func);
+static void activation_source_handle_cb (NMDevice *self, int family);
+
 static void _set_state_full (NMDevice *self,
                              NMDeviceState state,
                              NMDeviceStateReason reason,
                              gboolean quitting);
 
-static void nm_device_update_hw_address (NMDevice *self);
+static gboolean queued_ip4_config_change (gpointer user_data);
+static gboolean queued_ip6_config_change (gpointer user_data);
+
+static void _set_unmanaged_flags (NMDevice *self,
+                                  NMUnmanagedFlags flags,
+                                  gboolean unmanaged);
 
 /***********************************************************/
 
@@ -425,7 +446,7 @@ state_to_string (NMDeviceState state)
 	return queued_state_to_string (state) + strlen (QUEUED_PREFIX);
 }
 
-static const char *reason_table[] = {
+NM_UTILS_STRING_LOOKUP_TABLE_DEFINE_STATIC (_reason_to_string, NMDeviceStateReason, NULL,
 	[NM_DEVICE_STATE_REASON_UNKNOWN]                  = "unknown",
 	[NM_DEVICE_STATE_REASON_NONE]                     = "none",
 	[NM_DEVICE_STATE_REASON_NOW_MANAGED]              = "managed",
@@ -489,15 +510,10 @@ static const char *reason_table[] = {
 	[NM_DEVICE_STATE_REASON_NEW_ACTIVATION]           = "new-activation",
 	[NM_DEVICE_STATE_REASON_PARENT_CHANGED]           = "parent-changed",
 	[NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED]   = "parent-managed-changed",
-};
+);
 
-static const char *
-reason_to_string (NMDeviceStateReason reason)
-{
-	if ((gsize) reason < G_N_ELEMENTS (reason_table))
-		return reason_table[reason];
-	return reason_table[NM_DEVICE_STATE_REASON_UNKNOWN];
-}
+#define reason_to_string(reason) \
+	NM_UTILS_STRING_LOOKUP_TABLE (_reason_to_string, reason)
 
 /***********************************************************/
 
@@ -521,30 +537,6 @@ nm_device_has_capability (NMDevice *self, NMDeviceCapabilities caps)
 
 /***********************************************************/
 
-void
-nm_device_dbus_export (NMDevice *self)
-{
-	static guint32 devcount = 0;
-	NMDevicePrivate *priv;
-
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->path == NULL);
-
-	priv->path = g_strdup_printf ("/org/freedesktop/NetworkManager/Devices/%d", devcount++);
-	_LOGD (LOGD_DEVICE, "exported as %s", priv->path);
-	nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, self);
-}
-
-const char *
-nm_device_get_path (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, NULL);
-
-	return NM_DEVICE_GET_PRIVATE (self)->path;
-}
-
 const char *
 nm_device_get_udi (NMDevice *self)
 {
@@ -564,17 +556,40 @@ nm_device_get_iface (NMDevice *self)
 int
 nm_device_get_ifindex (NMDevice *self)
 {
-	g_return_val_if_fail (self != NULL, 0);
+	g_return_val_if_fail (NM_IS_DEVICE (self), 0);
 
 	return NM_DEVICE_GET_PRIVATE (self)->ifindex;
 }
 
+/**
+ * nm_device_is_software:
+ * @self: the #NMDevice
+ *
+ * Indicates if the device is a software-based virtual device without
+ * backing hardware, which can be added and removed programmatically.
+ *
+ * Returns: %TRUE if the device is a software-based device
+ */
 gboolean
 nm_device_is_software (NMDevice *self)
 {
 	return NM_FLAGS_HAS (NM_DEVICE_GET_PRIVATE (self)->capabilities, NM_DEVICE_CAP_IS_SOFTWARE);
 }
 
+/**
+ * nm_device_is_real:
+ * @self: the #NMDevice
+ *
+ * Returns: %TRUE if the device exists, %FALSE if the device is a placeholder
+ */
+gboolean
+nm_device_is_real (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	return NM_DEVICE_GET_PRIVATE (self)->real;
+}
+
 const char *
 nm_device_get_ip_iface (NMDevice *self)
 {
@@ -701,13 +716,21 @@ nm_device_get_device_type (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->type;
 }
 
+NMLinkType
+nm_device_get_link_type (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_LINK_TYPE_UNKNOWN);
+
+	return NM_DEVICE_GET_PRIVATE (self)->link_type;
+}
+
 /**
  * nm_device_get_metered:
  * @setting: the #NMDevice
  *
  * Returns: the #NMDevice:metered property of the device.
  *
- * Since: 1.0.6
+ * Since: 1.2
  **/
 NMMetered
 nm_device_get_metered (NMDevice *self)
@@ -762,12 +785,20 @@ nm_device_get_priority (NMDevice *self)
 		return 350;
 	case NM_DEVICE_TYPE_VLAN:
 		return 400;
+	case NM_DEVICE_TYPE_MACVLAN:
+		return 410;
 	case NM_DEVICE_TYPE_BRIDGE:
 		return 425;
+	case NM_DEVICE_TYPE_TUN:
+		return 450;
+	case NM_DEVICE_TYPE_VXLAN:
+		return 500;
 	case NM_DEVICE_TYPE_WIFI:
 		return 600;
 	case NM_DEVICE_TYPE_OLPC_MESH:
 		return 650;
+	case NM_DEVICE_TYPE_IP_TUNNEL:
+		return 675;
 	case NM_DEVICE_TYPE_MODEM:
 		return 700;
 	case NM_DEVICE_TYPE_BT:
@@ -795,7 +826,7 @@ _get_ipx_route_metric (NMDevice *self,
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), G_MAXUINT32);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (connection) {
 		s_ip = is_v4
 		       ? nm_connection_get_setting_ip4_config (connection)
@@ -923,12 +954,31 @@ nm_device_get_act_request (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->act_request;
 }
 
+NMSettingsConnection *
+nm_device_get_settings_connection (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	return priv->act_request ? nm_act_request_get_settings_connection (priv->act_request) : NULL;
+}
+
 NMConnection *
-nm_device_get_connection (NMDevice *self)
+nm_device_get_applied_connection (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	return priv->act_request ? nm_act_request_get_connection (priv->act_request) : NULL;
+	return priv->act_request ? nm_act_request_get_applied_connection (priv->act_request) : NULL;
+}
+
+gboolean
+nm_device_has_unmodified_applied_connection (NMDevice *self, NMSettingCompareFlags compare_flags)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->act_request)
+		return FALSE;
+
+	return nm_active_connection_has_unmodified_applied_connection ((NMActiveConnection *) priv->act_request, compare_flags);
 }
 
 RfKillType
@@ -951,13 +1001,13 @@ static gboolean
 nm_device_uses_generated_assumed_connection (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
+	NMSettingsConnection *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);
+		connection = nm_act_request_get_settings_connection (priv->act_request);
 		if (   connection
-		    && nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection)))
+		    && nm_settings_connection_get_nm_generated_assumed (connection))
 			return TRUE;
 	}
 	return FALSE;
@@ -989,17 +1039,8 @@ find_slave_info (NMDevice *self, NMDevice *slave)
 	return NULL;
 }
 
-static void
-free_slave_info (SlaveInfo *info)
-{
-	g_signal_handler_disconnect (info->slave, info->watch_id);
-	g_clear_object (&info->slave);
-	memset (info, 0, sizeof (*info));
-	g_free (info);
-}
-
 /**
- * nm_device_enslave_slave:
+ * nm_device_master_enslave_slave:
  * @self: the master device
  * @slave: the slave device to enslave
  * @connection: (allow-none): the slave device's connection
@@ -1011,7 +1052,7 @@ free_slave_info (SlaveInfo *info)
  *  other devices.
  */
 static gboolean
-nm_device_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connection)
+nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connection)
 {
 	SlaveInfo *info;
 	gboolean success = FALSE;
@@ -1025,7 +1066,7 @@ nm_device_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connecti
 	if (!info)
 		return FALSE;
 
-	if (info->enslaved)
+	if (info->slave_is_enslaved)
 		success = TRUE;
 	else {
 		configure = (info->configure && connection != NULL);
@@ -1033,7 +1074,7 @@ nm_device_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connecti
 			g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
 
 		success = NM_DEVICE_GET_CLASS (self)->enslave_slave (self, slave, connection, configure);
-		info->enslaved = success;
+		info->slave_is_enslaved = success;
 	}
 
 	nm_device_slave_notify_enslave (info->slave, success);
@@ -1059,7 +1100,7 @@ nm_device_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connecti
 }
 
 /**
- * nm_device_release_one_slave:
+ * nm_device_master_release_one_slave:
  * @self: the master device
  * @slave: the slave device to release
  * @configure: whether @self needs to actually release @slave
@@ -1068,49 +1109,55 @@ nm_device_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connecti
  * 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 @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 *self, NMDevice *slave, gboolean configure, NMDeviceStateReason reason)
+static void
+nm_device_master_release_one_slave (NMDevice *self, NMDevice *slave, gboolean configure, NMDeviceStateReason reason)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+	NMDevicePrivate *slave_priv;
 	SlaveInfo *info;
-	gboolean success = FALSE;
+	gs_unref_object NMDevice *self_free = NULL;
 
-	g_return_val_if_fail (slave != NULL, FALSE);
-	g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->release_slave != NULL, FALSE);
+	g_return_if_fail (NM_DEVICE (self));
+	g_return_if_fail (NM_DEVICE (slave));
+	g_return_if_fail (NM_DEVICE_GET_CLASS (self)->release_slave != NULL);
 
 	info = find_slave_info (self, slave);
+
+	_LOGt (LOGD_CORE, "master: release one slave %p/%s%s", slave, nm_device_get_iface (slave),
+	       !info ? " (not registered)" : "");
+
 	if (!info)
-		return FALSE;
-	priv->slaves = g_slist_remove (priv->slaves, info);
+		g_return_if_reached ();
 
-	if (info->enslaved) {
-		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.
-		 */
-	}
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	slave_priv = NM_DEVICE_GET_PRIVATE (slave);
 
-	if (!configure) {
-		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 ();
-		reason = NM_DEVICE_STATE_REASON_UNKNOWN;
-	}
-	nm_device_slave_notify_release (info->slave, reason);
+	g_return_if_fail (self == slave_priv->master);
+	nm_assert (slave == info->slave);
+
+	/* first, let subclasses handle the release ... */
+	if (info->slave_is_enslaved)
+		NM_DEVICE_GET_CLASS (self)->release_slave (self, slave, configure);
+
+	/* raise notifications about the release, including clearing is_enslaved. */
+	nm_device_slave_notify_release (slave, reason);
+
+	/* keep both alive until the end of the function.
+	 * Transfers ownership from slave_priv->master.  */
+	self_free = self;
+
+	priv->slaves = g_slist_remove (priv->slaves, info);
+	slave_priv->master = NULL;
 
-	free_slave_info (info);
+	g_signal_handler_disconnect (slave, info->watch_id);
+	g_object_unref (slave);
+	g_slice_free (SlaveInfo, info);
 
 	/* Ensure the device's hardware address is up-to-date; it often changes
 	 * when slaves change.
 	 */
 	nm_device_update_hw_address (self);
-
-	return success;
 }
 
 /**
@@ -1123,8 +1170,7 @@ nm_device_release_one_slave (NMDevice *self, NMDevice *slave, gboolean configure
 static gboolean
 can_unmanaged_external_down (NMDevice *self)
 {
-	return   nm_device_is_software (self)
-	      && !nm_device_get_is_nm_owned (self);
+	return nm_device_is_software (self) && !NM_DEVICE_GET_PRIVATE (self)->is_nm_owned;
 }
 
 /**
@@ -1138,7 +1184,6 @@ void
 nm_device_finish_init (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean platform_unmanaged = FALSE;
 
 	g_assert (priv->initialized == FALSE);
 
@@ -1147,29 +1192,29 @@ nm_device_finish_init (NMDevice *self)
 	    && priv->ifindex > 0
 	    && (   !priv->up
 	        || !priv->platform_link_initialized))
-		nm_device_set_initial_unmanaged_flag (self, NM_UNMANAGED_EXTERNAL_DOWN, TRUE);
+		nm_device_set_unmanaged_flags_initial (self, NM_UNMANAGED_EXTERNAL_DOWN, TRUE);
 
 	if (priv->master)
-		nm_device_enslave_slave (priv->master, self, NULL);
+		nm_device_master_enslave_slave (priv->master, self, NULL);
 
 	if (priv->ifindex > 0) {
 		if (priv->ifindex == 1) {
-			/* keep 'lo' as default-unmanaged. */
-
-			/* FIXME: either find a better way to unmange 'lo' that cannot be changed
-			 * by user configuration (NM_UNMANGED_LOOPBACK?) or fix managing 'lo'.
-			 * Currently it can happen that NM deletes 127.0.0.1 address. */
-			nm_device_set_initial_unmanaged_flag (self, NM_UNMANAGED_DEFAULT, TRUE);
+			/* Unmanaged the loopback device with an explicit NM_UNMANAGED_LOOPBACK flag.
+			 * Later we might want to manage 'lo' too. Currently that doesn't work because
+			 * NetworkManager might down the interface or remove the 127.0.0.1 address. */
+			nm_device_set_unmanaged_flags_initial (self, NM_UNMANAGED_LOOPBACK, TRUE);
 		} else if (priv->platform_link_initialized || (priv->is_nm_owned && nm_device_is_software (self))) {
+			gboolean platform_unmanaged = FALSE;
+
 			if (nm_platform_link_get_unmanaged (NM_PLATFORM_GET, priv->ifindex, &platform_unmanaged))
-				nm_device_set_initial_unmanaged_flag (self, NM_UNMANAGED_DEFAULT, platform_unmanaged);
+				nm_device_set_unmanaged_flags_initial (self, NM_UNMANAGED_DEFAULT, platform_unmanaged);
 		} else {
 			/* Hardware and externally-created software links stay unmanaged
 			 * until they are fully initialized by the platform. NM created
 			 * links must be available for activation immediately and thus
 			 * do not get the PLATFORM_INIT unmanaged flag set.
 			 */
-			nm_device_set_initial_unmanaged_flag (self, NM_UNMANAGED_PLATFORM_INIT, TRUE);
+			nm_device_set_unmanaged_flags_initial (self, NM_UNMANAGED_PLATFORM_INIT, TRUE);
 		}
 	}
 
@@ -1180,6 +1225,9 @@ static void
 update_dynamic_ip_setup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	GError *error;
+	gconstpointer addr;
+	size_t addr_length;
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
@@ -1205,6 +1253,18 @@ update_dynamic_ip_setup (NMDevice *self)
 	if (priv->dnsmasq_manager) {
 		/* FIXME: todo */
 	}
+
+	if (priv->lldp_listener && nm_lldp_listener_is_running (priv->lldp_listener)) {
+		nm_lldp_listener_stop (priv->lldp_listener);
+		addr = nm_platform_link_get_address (NM_PLATFORM_GET, priv->ifindex, &addr_length);
+
+		if (!nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self),
+		                             nm_device_get_iface (self), addr, addr_length, &error)) {
+			_LOGD (LOGD_DEVICE, "LLDP listener %p could not be restarted: %s",
+			       priv->lldp_listener, error->message);
+			g_clear_error (&error);
+		}
+	}
 }
 
 static void
@@ -1342,23 +1402,40 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 }
 
 static void
-device_set_master (NMDevice *self, int ifindex)
+device_recheck_slave_status (NMDevice *self, const NMPlatformLink *plink)
 {
-	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 (NM_PLATFORM_GET, ifindex));
+	g_return_if_fail (plink);
+
+	if (plink->master <= 0)
+		return;
+
+	if (priv->master) {
+		if (   plink->master > 0
+		    && plink->master == nm_device_get_ifindex (priv->master)) {
+			/* call add-slave again. We expect @self already to be added to
+			 * the master, but this also triggers a recheck-assume. */
+			nm_device_master_add_slave (priv->master, self, FALSE);
+			return;
+		}
+
+		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
+	}
+	if (plink->master > 0) {
+		NMDevice *master;
+
+		master = nm_manager_get_device_by_ifindex (nm_manager_get (), plink->master);
+		if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave)
+			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",
+			       plink->master,
+			       nm_platform_link_get_name (NM_PLATFORM_GET, plink->master));
+		}
 	}
 }
 
@@ -1369,12 +1446,11 @@ device_link_changed (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMUtilsIPv6IfaceId token_iid;
 	gboolean ip_ifname_changed = FALSE;
-	gboolean platform_unmanaged = FALSE;
 	const char *udi;
 	NMPlatformLink info;
 	const NMPlatformLink *pllink;
 	int ifindex;
-	gboolean emit_link_initialized = FALSE;
+	gboolean just_initialized = FALSE;
 	gboolean was_up;
 
 	priv->device_link_changed_id = 0;
@@ -1407,6 +1483,12 @@ device_link_changed (NMDevice *self)
 		g_object_notify (G_OBJECT (self), NM_DEVICE_MTU);
 	}
 
+	if (info.driver && g_strcmp0 (priv->driver, info.driver) != 0) {
+		g_free (priv->driver);
+		priv->driver = g_strdup (info.driver);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_DRIVER);
+	}
+
 	if (info.name[0] && strcmp (priv->iface, info.name) != 0) {
 		_LOGI (LOGD_DEVICE, "interface index %d renamed iface from '%s' to '%s'",
 		       priv->ifindex, priv->iface, info.name);
@@ -1429,15 +1511,6 @@ device_link_changed (NMDevice *self)
 		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) {
-		device_set_master (self, info.master);
-		if (priv->master)
-			nm_device_enslave_slave (priv->master, self, NULL);
-	}
-
 	if (priv->rdisc && nm_platform_link_get_ipv6_token (NM_PLATFORM_GET, priv->ifindex, &token_iid)) {
 		_LOGD (LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface);
 		if (nm_rdisc_set_iid (priv->rdisc, token_iid))
@@ -1452,33 +1525,36 @@ device_link_changed (NMDevice *self)
 		update_dynamic_ip_setup (self);
 
 	if (priv->ifindex > 0 && !priv->platform_link_initialized && info.initialized) {
+		gboolean platform_unmanaged = FALSE;
+
 		priv->platform_link_initialized = TRUE;
 
 		if (nm_platform_link_get_unmanaged (NM_PLATFORM_GET, priv->ifindex, &platform_unmanaged)) {
-			nm_device_set_unmanaged (self,
-			                         NM_UNMANAGED_DEFAULT,
-			                         platform_unmanaged,
-			                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
+			nm_device_set_unmanaged_flags (self,
+			                               NM_UNMANAGED_DEFAULT,
+			                               platform_unmanaged,
+			                               NM_DEVICE_STATE_REASON_USER_REQUESTED);
 		}
 
-		nm_device_set_unmanaged (self,
-		                         NM_UNMANAGED_PLATFORM_INIT,
-		                         FALSE,
-		                         NM_DEVICE_STATE_REASON_NOW_MANAGED);
+		nm_device_set_unmanaged_flags (self,
+		                               NM_UNMANAGED_PLATFORM_INIT,
+		                               FALSE,
+		                               NM_DEVICE_STATE_REASON_NOW_MANAGED);
 
-		emit_link_initialized = TRUE;
+		just_initialized = TRUE;
 	}
 
 	was_up = priv->up;
 	priv->up = NM_FLAGS_HAS (info.flags, IFF_UP);
 
 	if (   priv->platform_link_initialized
-	    && (emit_link_initialized || priv->up != was_up)) {
+	    && (   just_initialized
+	        || priv->up != was_up)) {
 
 		/* Manage externally-created software interfaces only when they are IFF_UP */
 		g_assert (priv->ifindex > 0);
 		if (NM_DEVICE_GET_CLASS (self)->can_unmanaged_external_down (self)) {
-			gboolean external_down = nm_device_get_unmanaged_flag (self, NM_UNMANAGED_EXTERNAL_DOWN);
+			gboolean external_down = !!nm_device_get_unmanaged_flags (self, NM_UNMANAGED_EXTERNAL_DOWN);
 
 			if (external_down && NM_FLAGS_HAS (info.flags, IFF_UP)) {
 				if (nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED) {
@@ -1492,32 +1568,30 @@ device_link_changed (NMDevice *self)
 					 * 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);
+					nm_device_set_unmanaged_flags (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;
+					_set_unmanaged_flags (self, NM_UNMANAGED_EXTERNAL_DOWN, FALSE);
 				}
 			} else if (!external_down && !NM_FLAGS_HAS (info.flags, IFF_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);
+				nm_device_set_unmanaged_flags (self,
+				                               NM_UNMANAGED_EXTERNAL_DOWN,
+				                               TRUE,
+				                               NM_DEVICE_STATE_REASON_USER_REQUESTED);
 			}
 		}
 	}
 
-	if (emit_link_initialized)
-		g_signal_emit (self, signals[LINK_INITIALIZED], 0);
-
+	device_recheck_slave_status (self, &info);
 	return G_SOURCE_REMOVE;
 }
 
@@ -1526,12 +1600,13 @@ device_ip_link_changed (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const NMPlatformLink *pllink;
-	int ip_ifindex;
 
 	priv->device_ip_link_changed_id = 0;
 
-	ip_ifindex = nm_device_get_ip_ifindex (self);
-	pllink = nm_platform_link_get (NM_PLATFORM_GET, ip_ifindex);
+	if (!priv->ip_ifindex)
+		return G_SOURCE_REMOVE;
+
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, priv->ip_ifindex);
 	if (!pllink)
 		return G_SOURCE_REMOVE;
 
@@ -1554,7 +1629,6 @@ link_changed_cb (NMPlatform *platform,
                  int ifindex,
                  NMPlatformLink *info,
                  NMPlatformSignalChangeType change_type,
-                 NMPlatformReason reason,
                  NMDevice *self)
 {
 	NMDevicePrivate *priv;
@@ -1564,12 +1638,6 @@ link_changed_cb (NMPlatform *platform,
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	/* We don't filter by 'reason' because we are interested in *all* link
-	 * changes. For example a call to nm_platform_link_set_up() may result
-	 * in an internal carrier change (i.e. we ask the kernel to set IFF_UP
-	 * and it results in also setting IFF_LOWER_UP.
-	 */
-
 	if (ifindex == nm_device_get_ifindex (self)) {
 		if (!priv->device_link_changed_id) {
 			priv->device_link_changed_id = g_idle_add ((GSourceFunc) device_link_changed, self);
@@ -1592,6 +1660,165 @@ link_changed (NMDevice *self, NMPlatformLink *info)
 		nm_device_set_carrier (self, info->connected);
 }
 
+static gboolean
+link_type_compatible (NMDevice *self,
+                      NMLinkType link_type,
+                      gboolean *out_compatible,
+                      GError **error)
+{
+	NMDeviceClass *klass;
+	NMLinkType device_type;
+	guint i = 0;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	klass = NM_DEVICE_GET_CLASS (self);
+
+	if (!klass->link_types) {
+		NM_SET_OUT (out_compatible, FALSE);
+		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		                     "Device does not support platform links");
+		return FALSE;
+	}
+
+	device_type = self->priv->link_type;
+	if (device_type > NM_LINK_TYPE_UNKNOWN && device_type != link_type) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		             "Needed link type 0x%x does not match the platform link type 0x%X",
+		             device_type, link_type);
+		return FALSE;
+	}
+
+	for (i = 0; klass->link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) {
+		if (klass->link_types[i] == link_type)
+			return TRUE;
+		if (klass->link_types[i] == NM_LINK_TYPE_ANY)
+			return TRUE;
+	}
+
+	NM_SET_OUT (out_compatible, FALSE);
+	g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+	             "Device does not support platform link type 0x%X",
+	             link_type);
+	return FALSE;
+}
+
+/**
+ * nm_device_realize_start():
+ * @self: the #NMDevice
+ * @plink: an existing platform link or %NULL
+ * @out_compatible: %TRUE on return if @self is compatible with @plink
+ * @error: location to store error, or %NULL
+ *
+ * Initializes and sets up the device using existing backing resources. Before
+ * the device is ready for use nm_device_realize_finish() must be called.
+ * @out_compatible will only be set if @plink is not %NULL, and
+ *
+ * Important: if nm_device_realize_start() returns %TRUE, the caller MUST
+ * also call nm_device_realize_finish() to balance g_object_freeze_notify().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+nm_device_realize_start (NMDevice *self,
+                         const NMPlatformLink *plink,
+                         gboolean *out_compatible,
+                         GError **error)
+{
+	NM_SET_OUT (out_compatible, TRUE);
+
+	if (plink) {
+		if (g_strcmp0 (nm_device_get_iface (self), plink->name) != 0) {
+			NM_SET_OUT (out_compatible, FALSE);
+			g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+			                     "Device interface name does not match platform link");
+			return FALSE;
+		}
+
+		if (!link_type_compatible (self, plink->type, out_compatible, error))
+			return FALSE;
+	}
+
+	realize_start_setup (self, plink);
+
+	return TRUE;
+}
+
+/**
+ * nm_device_create_and_realize():
+ * @self: the #NMDevice
+ * @connection: the #NMConnection being activated
+ * @parent: the parent #NMDevice if any
+ * @error: location to store error, or %NULL
+ *
+ * Creates any backing resources needed to realize the device to proceed
+ * with activating @connection.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+nm_device_create_and_realize (NMDevice *self,
+                              NMConnection *connection,
+                              NMDevice *parent,
+                              GError **error)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMPlatformLink plink_copy;
+	const NMPlatformLink *plink = NULL;
+
+	/* Must be set before device is realized */
+	priv->is_nm_owned = !nm_platform_link_get_by_ifname (NM_PLATFORM_GET, priv->iface);
+
+	_LOGD (LOGD_DEVICE, "create (is %snm-owned)", priv->is_nm_owned ? "" : "not ");
+
+	/* Create any resources the device needs */
+	if (NM_DEVICE_GET_CLASS (self)->create_and_realize) {
+		if (!NM_DEVICE_GET_CLASS (self)->create_and_realize (self, connection, parent, &plink, error))
+			return FALSE;
+		plink_copy = *plink;
+		plink = &plink_copy;
+	}
+
+	realize_start_setup (self, plink);
+	nm_device_realize_finish (self, plink);
+
+	g_return_val_if_fail (nm_device_check_connection_compatible (self, connection), TRUE);
+	return TRUE;
+}
+
+static void
+update_device_from_platform_link (NMDevice *self, const NMPlatformLink *plink)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const char *udi;
+
+	g_return_if_fail (plink != NULL);
+
+	udi = nm_platform_link_get_udi (NM_PLATFORM_GET, plink->ifindex);
+	if (udi && !g_strcmp0 (udi, priv->udi)) {
+		g_free (priv->udi);
+		priv->udi = g_strdup (udi);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_UDI);
+	}
+
+	if (!g_strcmp0 (plink->name, priv->iface)) {
+		g_free (priv->iface);
+		priv->iface = g_strdup (plink->name);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_IFACE);
+	}
+
+	priv->ifindex = plink->ifindex;
+	g_object_notify (G_OBJECT (self), NM_DEVICE_IFINDEX);
+
+	priv->up = NM_FLAGS_HAS (plink->flags, IFF_UP);
+	if (plink->driver && g_strcmp0 (plink->driver, priv->driver) != 0) {
+		g_free (priv->driver);
+		priv->driver = g_strdup (plink->driver);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_DRIVER);
+	}
+	priv->platform_link_initialized = plink->initialized;
+}
+
 static void
 config_changed_update_ignore_carrier (NMConfig *config,
                                       NMConfigData *config_data,
@@ -1615,6 +1842,317 @@ check_carrier (NMDevice *self)
 		nm_device_set_carrier (self, nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
 }
 
+static void
+realize_start_notify (NMDevice *self, const NMPlatformLink *plink)
+{
+	/* Stub implementation for realize_start_notify(). It does nothing,
+	 * but allows derived classes to uniformly invoke the parent
+	 * implementation. */
+}
+
+/**
+ * realize_start_setup():
+ * @self: the #NMDevice
+ * @plink: the #NMPlatformLink if backed by a kernel netdevice
+ *
+ * Update the device from backing resource properties (like hardware
+ * addresses, carrier states, driver/firmware info, etc).  This function
+ * should only change properties for this device, and should not perform
+ * any tasks that affect other interfaces (like master/slave or parent/child
+ * stuff).
+ */
+static void
+realize_start_setup (NMDevice *self, const NMPlatformLink *plink)
+{
+	NMDevicePrivate *priv;
+	NMDeviceClass *klass;
+	static guint32 id = 0;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	/* The device should not be realized */
+	g_return_if_fail (!priv->real);
+	g_return_if_fail (priv->ip_ifindex <= 0);
+	g_return_if_fail (priv->ip_iface == NULL);
+
+	_LOGD (LOGD_DEVICE, "start setup of %s, kernel ifindex %d", G_OBJECT_TYPE_NAME (self), plink ? plink->ifindex : 0);
+
+	klass = NM_DEVICE_GET_CLASS (self);
+
+	/* Balanced by a thaw in nm_device_realize_finish() */
+	g_object_freeze_notify (G_OBJECT (self));
+
+	if (plink) {
+		g_return_if_fail (link_type_compatible (self, plink->type, NULL, NULL));
+		update_device_from_platform_link (self, plink);
+	}
+
+	if (priv->ifindex > 0) {
+		priv->physical_port_id = nm_platform_link_get_physical_port_id (NM_PLATFORM_GET, priv->ifindex);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_PHYSICAL_PORT_ID);
+
+		priv->dev_id = nm_platform_link_get_dev_id (NM_PLATFORM_GET, priv->ifindex);
+
+		if (nm_platform_link_is_software (NM_PLATFORM_GET, priv->ifindex))
+			priv->capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
+
+		priv->mtu = nm_platform_link_get_mtu (NM_PLATFORM_GET, priv->ifindex);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_MTU);
+
+		nm_platform_link_get_driver_info (NM_PLATFORM_GET,
+		                                  priv->ifindex,
+		                                  NULL,
+		                                  &priv->driver_version,
+		                                  &priv->firmware_version);
+		if (priv->driver_version)
+			g_object_notify (G_OBJECT (self), NM_DEVICE_DRIVER_VERSION);
+		if (priv->firmware_version)
+			g_object_notify (G_OBJECT (self), NM_DEVICE_FIRMWARE_VERSION);
+
+		if (nm_platform_check_support_user_ipv6ll (NM_PLATFORM_GET))
+			priv->nm_ipv6ll = nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, priv->ifindex);
+	}
+
+	if (klass->get_generic_capabilities)
+		priv->capabilities |= klass->get_generic_capabilities (self);
+
+	if (!priv->udi) {
+		/* Use a placeholder UDI until we get a real one */
+		priv->udi = g_strdup_printf ("/virtual/device/placeholder/%d", id++);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_UDI);
+	}
+
+	/* trigger initial ip config change to initialize ip-config */
+	priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
+	priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
+
+	nm_device_update_hw_address (self);
+	nm_device_update_initial_hw_address (self);
+
+	/* Note: initial hardware address must be read before calling get_ignore_carrier() */
+	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
+		NMConfig *config = nm_config_get ();
+
+		priv->ignore_carrier = nm_config_data_get_ignore_carrier (nm_config_get_data (config), self);
+		g_signal_connect (G_OBJECT (config),
+		                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+		                  G_CALLBACK (config_changed_update_ignore_carrier),
+		                  self);
+
+		check_carrier (self);
+		_LOGD (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;
+	}
+
+	g_object_notify (G_OBJECT (self), NM_DEVICE_CAPABILITIES);
+
+	klass->realize_start_notify (self, plink);
+}
+
+/**
+ * nm_device_realize_finish():
+ * @self: the #NMDevice
+ * @plink: the #NMPlatformLink if backed by a kernel netdevice
+ *
+ * Update the device's master/slave or parent/child relationships from
+ * backing resource properties.  After this function finishes, the device
+ * is ready for network connectivity.
+ */
+void
+nm_device_realize_finish (NMDevice *self, const NMPlatformLink *plink)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (!plink || link_type_compatible (self, plink->type, NULL, NULL));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	g_return_if_fail (!priv->real);
+
+	if (plink) {
+		update_device_from_platform_link (self, plink);
+		device_recheck_slave_status (self, plink);
+	}
+
+	priv->real = TRUE;
+	g_object_notify (G_OBJECT (self), NM_DEVICE_REAL);
+
+	nm_device_recheck_available_connections (self);
+
+	/* Balanced by a freeze in realize_start_setup() */
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+unrealize_notify (NMDevice *self)
+{
+	/* Stub implementation for unrealize_notify(). It does nothing,
+	 * but allows derived classes to uniformly invoke the parent
+	 * implementation. */
+}
+
+static gboolean
+available_connection_check_delete_unrealized_on_idle (gpointer user_data)
+{
+	NMDevice *self = user_data;
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	priv->check_delete_unrealized_id = 0;
+
+	if (   g_hash_table_size (priv->available_connections) == 0
+	    && !nm_device_is_real (self))
+		g_signal_emit (self, signals[REMOVED], 0);
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+available_connection_check_delete_unrealized (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	/* always rescheadule the remove signal. */
+	nm_clear_g_source (&priv->check_delete_unrealized_id);
+
+	if (   g_hash_table_size (priv->available_connections) == 0
+	    && !nm_device_is_real (self))
+		priv->check_delete_unrealized_id = g_idle_add (available_connection_check_delete_unrealized_on_idle, self);
+}
+
+/**
+ * nm_device_unrealize():
+ * @self: the #NMDevice
+ * @remove_resources: if %TRUE, remove backing resources
+ * @error: location to store error, or %NULL
+ *
+ * Clears any properties that depend on backing resources (kernel devices,
+ * etc) and removes those resources if @remove_resources is %TRUE.
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
+{
+	NMDevicePrivate *priv;
+	int ifindex;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	if (!nm_device_is_software (self) || !nm_device_is_real (self)) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_NOT_SOFTWARE,
+		                     "This device is not a software device or is not realized");
+		return FALSE;
+	}
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	g_return_val_if_fail (priv->iface != NULL, FALSE);
+	g_return_val_if_fail (priv->real, FALSE);
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+	ifindex = nm_device_get_ifindex (self);
+
+	_LOGD (LOGD_DEVICE, "unrealize (ifindex %d)", ifindex > 0 ? ifindex : 0);
+
+	if (remove_resources) {
+		if (ifindex > 0)
+			nm_platform_link_delete (NM_PLATFORM_GET, ifindex);
+	}
+
+	NM_DEVICE_GET_CLASS (self)->unrealize_notify (self);
+
+	if (priv->ifindex > 0) {
+		priv->ifindex = 0;
+		g_object_notify (G_OBJECT (self), NM_DEVICE_IFINDEX);
+	}
+	priv->ip_ifindex = 0;
+	if (priv->ip_iface) {
+		g_clear_pointer (&priv->ip_iface, g_free);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE);
+	}
+	if (priv->driver_version) {
+		g_clear_pointer (&priv->driver_version, g_free);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_DRIVER_VERSION);
+	}
+	if (priv->firmware_version) {
+		g_clear_pointer (&priv->firmware_version, g_free);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_FIRMWARE_VERSION);
+	}
+	if (priv->udi) {
+		g_clear_pointer (&priv->udi, g_free);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_UDI);
+	}
+	if (priv->hw_addr) {
+		g_clear_pointer (&priv->hw_addr, g_free);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_HW_ADDRESS);
+	}
+	if (priv->physical_port_id) {
+		g_clear_pointer (&priv->physical_port_id, g_free);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_PHYSICAL_PORT_ID);
+	}
+
+	g_clear_pointer (&priv->perm_hw_addr, g_free);
+	g_clear_pointer (&priv->initial_hw_addr, g_free);
+
+	priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
+	if (NM_DEVICE_GET_CLASS (self)->get_generic_capabilities)
+		priv->capabilities |= NM_DEVICE_GET_CLASS (self)->get_generic_capabilities (self);
+	g_object_notify (G_OBJECT (self), NM_DEVICE_CAPABILITIES);
+
+	priv->real = FALSE;
+	g_object_notify (G_OBJECT (self), NM_DEVICE_REAL);
+
+	nm_device_set_autoconnect (self, DEFAULT_AUTOCONNECT);
+
+	g_object_thaw_notify (G_OBJECT (self));
+
+	nm_device_state_changed (self,
+	                         NM_DEVICE_STATE_UNMANAGED,
+	                         remove_resources ?
+	                             NM_DEVICE_STATE_REASON_USER_REQUESTED : NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
+
+	/* Garbage-collect unneeded unrealized devices. */
+	nm_device_recheck_available_connections (self);
+
+	return TRUE;
+}
+
+/**
+ * nm_device_notify_new_device_added():
+ * @self: the #NMDevice
+ * @device: the newly added device
+ *
+ * Called by the manager to notify the device that a new device has
+ * been found and added.
+ */
+void
+nm_device_notify_new_device_added (NMDevice *self, NMDevice *device)
+{
+	NMDeviceClass *klass;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (klass->notify_new_device_added)
+		klass->notify_new_device_added (self, device);
+}
+
 /**
  * nm_device_notify_component_added():
  * @self: the #NMDevice
@@ -1630,8 +2168,14 @@ check_carrier (NMDevice *self)
 gboolean
 nm_device_notify_component_added (NMDevice *self, GObject *component)
 {
-	if (NM_DEVICE_GET_CLASS (self)->component_added)
-		return NM_DEVICE_GET_CLASS (self)->component_added (self, component);
+	NMDeviceClass *klass;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (G_IS_OBJECT (component), FALSE);
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (klass->component_added)
+		return klass->component_added (self, component);
 	return FALSE;
 }
 
@@ -1687,7 +2231,7 @@ slave_state_changed (NMDevice *slave,
 		return;
 
 	if (slave_new_state == NM_DEVICE_STATE_IP_CONFIG)
-		nm_device_enslave_slave (self, slave, nm_device_get_connection (slave));
+		nm_device_master_enslave_slave (self, slave, nm_device_get_applied_connection (slave));
 	else if (slave_new_state > NM_DEVICE_STATE_ACTIVATED)
 		release = TRUE;
 	else if (   slave_new_state <= NM_DEVICE_STATE_DISCONNECTED
@@ -1697,7 +2241,7 @@ slave_state_changed (NMDevice *slave,
 	}
 
 	if (release) {
-		nm_device_release_one_slave (self, slave, TRUE, reason);
+		nm_device_master_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)
 			_LOGD (LOGD_DEVICE, "last slave removed; remaining activated");
@@ -1713,33 +2257,53 @@ slave_state_changed (NMDevice *slave,
  *
  * 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
+static void
 nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+	NMDevicePrivate *slave_priv;
 	SlaveInfo *info;
 
-	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 (self)->enslave_slave != NULL, FALSE);
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IS_DEVICE (slave));
+	g_return_if_fail (NM_DEVICE_GET_CLASS (self)->enslave_slave != NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	slave_priv = NM_DEVICE_GET_PRIVATE (slave);
+
+	info = find_slave_info (self, slave);
+
+	_LOGt (LOGD_CORE, "master: add one slave %p/%s%s", slave, nm_device_get_iface (slave),
+	       info ? " (already registered)" : "");
 
 	if (configure)
-		g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
+		g_return_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED);
 
-	if (!find_slave_info (self, slave)) {
-		info = g_malloc0 (sizeof (SlaveInfo));
+	if (!info) {
+		g_return_if_fail (!slave_priv->master);
+		g_return_if_fail (!slave_priv->is_enslaved);
+
+		info = g_slice_new0 (SlaveInfo);
 		info->slave = g_object_ref (slave);
 		info->configure = configure;
-		info->watch_id = g_signal_connect (slave, "state-changed",
+		info->watch_id = g_signal_connect (slave,
+		                                   NM_DEVICE_STATE_CHANGED,
 		                                   G_CALLBACK (slave_state_changed), self);
 		priv->slaves = g_slist_append (priv->slaves, info);
-	}
-	nm_device_queue_recheck_assume (self);
+		slave_priv->master = g_object_ref (self);
 
-	return TRUE;
+		/* no need to emit
+		 *
+		 *   g_object_notify (G_OBJECT (slave), NM_DEVICE_MASTER);
+		 *
+		 * because slave_priv->is_enslaved is not true, thus the value
+		 * didn't change yet. */
+	} else
+		g_return_if_fail (slave_priv->master == self);
+
+	nm_device_queue_recheck_assume (self);
+	nm_device_queue_recheck_assume (slave);
 }
 
 
@@ -1749,7 +2313,7 @@ nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure)
  *
  * Returns: any slaves of which @self is the master.  Caller owns returned list.
  */
-GSList *
+static GSList *
 nm_device_master_get_slaves (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -1794,7 +2358,7 @@ nm_device_master_get_slave_by_ifindex (NMDevice *self, int ifindex)
  */
 void
 nm_device_master_check_slave_physical_port (NMDevice *self, NMDevice *slave,
-                                            guint64 log_domain)
+                                            NMLogDomain log_domain)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const char *slave_physical_port_id, *existing_physical_port_id;
@@ -1831,6 +2395,7 @@ nm_device_master_release_slaves (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceStateReason reason;
+	gboolean configure = TRUE;
 
 	/* Don't release the slaves if this connection doesn't belong to NM. */
 	if (nm_device_uses_generated_assumed_connection (self))
@@ -1840,14 +2405,29 @@ nm_device_master_release_slaves (NMDevice *self)
 	if (priv->state == NM_DEVICE_STATE_FAILED)
 		reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
 
+	if (!nm_platform_link_get (NM_PLATFORM_GET, priv->ifindex))
+		configure = FALSE;
+
 	while (priv->slaves) {
 		SlaveInfo *info = priv->slaves->data;
 
-		nm_device_release_one_slave (self, info->slave, TRUE, reason);
+		nm_device_master_release_one_slave (self, info->slave, configure, reason);
 	}
 }
 
 /**
+ * nm_device_is_master:
+ * @self: the device
+ *
+ * Returns: %TRUE if the device can have slaves
+ */
+gboolean
+nm_device_is_master (NMDevice *self)
+{
+	return NM_DEVICE_GET_PRIVATE (self)->is_master;
+}
+
+/**
  * nm_device_get_master:
  * @self: the device
  *
@@ -1863,10 +2443,11 @@ nm_device_get_master (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->enslaved)
+	if (priv->is_enslaved) {
+		g_return_val_if_fail (priv->master, NULL);
 		return priv->master;
-	else
-		return NULL;
+	}
+	return NULL;
 }
 
 /**
@@ -1881,12 +2462,12 @@ static void
 nm_device_slave_notify_enslave (NMDevice *self, gboolean success)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection = nm_device_get_connection (self);
+	NMConnection *connection = nm_device_get_applied_connection (self);
 	gboolean activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG);
 
-	g_assert (priv->master);
+	g_return_if_fail (priv->master);
 
-	if (!priv->enslaved) {
+	if (!priv->is_enslaved) {
 		if (success) {
 			if (activating) {
 				_LOGI (LOGD_DEVICE, "Activation: connection '%s' enslaved, continuing activation",
@@ -1894,8 +2475,9 @@ nm_device_slave_notify_enslave (NMDevice *self, gboolean success)
 			} else
 				_LOGI (LOGD_DEVICE, "enslaved to %s", nm_device_get_iface (priv->master));
 
-			priv->enslaved = TRUE;
+			priv->is_enslaved = TRUE;
 			g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER);
+			g_object_notify (G_OBJECT (priv->master), NM_DEVICE_SLAVES);
 		} else if (activating) {
 			_LOGW (LOGD_DEVICE, "Activation: connection '%s' could not be enslaved",
 			       nm_connection_get_id (connection));
@@ -1924,12 +2506,13 @@ static void
 nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection = nm_device_get_connection (self);
+	NMConnection *connection = nm_device_get_applied_connection (self);
 	NMDeviceState new_state;
 	const char *master_status;
 
-	if (   reason != NM_DEVICE_STATE_REASON_NONE
-	    && priv->state > NM_DEVICE_STATE_DISCONNECTED
+	g_return_if_fail (priv->master);
+
+	if (   priv->state > NM_DEVICE_STATE_DISCONNECTED
 	    && priv->state <= NM_DEVICE_STATE_ACTIVATED) {
 		if (reason == NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED) {
 			new_state = NM_DEVICE_STATE_FAILED;
@@ -1947,14 +2530,13 @@ nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 		       master_status);
 
 		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)");
+	} else
+		_LOGI (LOGD_DEVICE, "released from master device %s", nm_device_get_iface (priv->master));
 
-	if (priv->enslaved) {
-		priv->enslaved = FALSE;
+	if (priv->is_enslaved) {
+		priv->is_enslaved = FALSE;
 		g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER);
+		g_object_notify (G_OBJECT (priv->master), NM_DEVICE_SLAVES);
 	}
 }
 
@@ -1968,7 +2550,7 @@ nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 gboolean
 nm_device_get_enslaved (NMDevice *self)
 {
-	return NM_DEVICE_GET_PRIVATE (self)->enslaved;
+	return NM_DEVICE_GET_PRIVATE (self)->is_enslaved;
 }
 
 /**
@@ -1981,12 +2563,17 @@ nm_device_get_enslaved (NMDevice *self)
 void
 nm_device_removed (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
 
-	if (priv->enslaved)
-		nm_device_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_REMOVED);
-}
+	g_return_if_fail (NM_IS_DEVICE (self));
 
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	if (priv->master) {
+		/* this is called when something externally messes with the slave or during shut-down.
+		 * Release the slave from master, but don't touch the device. */
+		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
+	}
+}
 
 static gboolean
 is_available (NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
@@ -1996,7 +2583,7 @@ is_available (NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
 	if (priv->carrier || priv->ignore_carrier)
 		return TRUE;
 
-	if (NM_FLAGS_HAS (flags, NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER))
+	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER))
 		return TRUE;
 
 	return FALSE;
@@ -2068,7 +2655,7 @@ nm_device_get_autoconnect (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->autoconnect;
 }
 
-static void
+void
 nm_device_set_autoconnect (NMDevice *self, gboolean autoconnect)
 {
 	NMDevicePrivate *priv;
@@ -2116,7 +2703,11 @@ 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)
+	if (!priv->autoconnect)
+		return FALSE;
+
+	/* Unrealized devices can always autoconnect. */
+	if (nm_device_is_real (self) && priv->state < NM_DEVICE_STATE_DISCONNECTED)
 		return FALSE;
 
 	/* The 'autoconnect-allowed' signal is emitted on a device to allow
@@ -2130,15 +2721,13 @@ nm_device_autoconnect_allowed (NMDevice *self)
 	g_value_set_object (&instance, self);
 
 	g_value_init (&retval, G_TYPE_BOOLEAN);
-	if (priv->autoconnect)
-		g_value_set_boolean (&retval, TRUE);
-	else
-		g_value_set_boolean (&retval, FALSE);
+	g_value_set_boolean (&retval, TRUE);
 
 	/* Use g_signal_emitv() rather than g_signal_emit() to avoid the return
 	 * value being changed if no handlers are connected */
 	g_signal_emitv (&instance, signals[AUTOCONNECT_ALLOWED], 0, &retval);
 	g_value_unset (&instance);
+
 	return g_value_get_boolean (&retval);
 }
 
@@ -2199,7 +2788,7 @@ device_has_config (NMDevice *self)
 		return TRUE;
 
 	/* The existence of a software device is good enough. */
-	if (nm_device_is_software (self))
+	if (nm_device_is_software (self) && nm_device_is_real (self))
 		return TRUE;
 
 	/* Slaves are also configured by definition */
@@ -2429,6 +3018,34 @@ nm_device_check_connection_compatible (NMDevice *self, NMConnection *connection)
 	return NM_DEVICE_GET_CLASS (self)->check_connection_compatible (self, connection);
 }
 
+gboolean
+nm_device_check_slave_connection_compatible (NMDevice *self, NMConnection *slave)
+{
+	NMDevicePrivate *priv;
+	NMSettingConnection *s_con;
+	const char *connection_type, *slave_type;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (slave), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->is_master)
+		return FALSE;
+
+	/* All masters should have connection type set */
+	connection_type = NM_DEVICE_GET_CLASS (self)->connection_type;
+	g_return_val_if_fail (connection_type, FALSE);
+
+	s_con = nm_connection_get_setting_connection (slave);
+	g_assert (s_con);
+	slave_type = nm_setting_connection_get_slave_type (s_con);
+	if (!slave_type)
+		return FALSE;
+
+	return strcmp (connection_type, slave_type) == 0;
+}
+
 /**
  * nm_device_can_assume_connections:
  * @self: #NMDevice instance
@@ -2441,7 +3058,8 @@ nm_device_check_connection_compatible (NMDevice *self, NMConnection *connection)
 static gboolean
 nm_device_can_assume_connections (NMDevice *self)
 {
-	return !!NM_DEVICE_GET_CLASS (self)->update_connection;
+	return   !!NM_DEVICE_GET_CLASS (self)->update_connection
+	      && !NM_DEVICE_GET_PRIVATE (self)->is_nm_owned;
 }
 
 /**
@@ -2481,7 +3099,7 @@ nm_device_can_assume_active_connection (NMDevice *self)
 	if (!nm_device_can_assume_connections (self))
 		return FALSE;
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (!connection)
 		return FALSE;
 
@@ -2505,9 +3123,14 @@ nm_device_can_assume_active_connection (NMDevice *self)
 }
 
 static gboolean
-nm_device_emit_recheck_assume (gpointer self)
+nm_device_emit_recheck_assume (gpointer user_data)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevice *self = user_data;
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	priv->recheck_assume_id = 0;
 	if (!nm_device_get_act_request (self)) {
@@ -2522,7 +3145,8 @@ nm_device_queue_recheck_assume (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (nm_device_can_assume_connections (self) && !priv->recheck_assume_id)
+	if (   !priv->recheck_assume_id
+	    && nm_device_can_assume_connections (self))
 		priv->recheck_assume_id = g_idle_add (nm_device_emit_recheck_assume, self);
 }
 
@@ -2544,13 +3168,17 @@ recheck_available (gpointer user_data)
 		new_state = NM_DEVICE_STATE_UNAVAILABLE;
 		nm_device_queue_state (self, new_state, priv->recheck_available.unavailable_reason);
 	}
-	_LOGD (LOGD_DEVICE, "device is %savailable, %s %s",
-	       now_available ? "" : "not ",
-	       new_state == NM_DEVICE_STATE_UNAVAILABLE ? "no change required for" : "will transition to",
-	       state_to_string (new_state == NM_DEVICE_STATE_UNAVAILABLE ? state : new_state));
 
-	priv->recheck_available.available_reason = NM_DEVICE_STATE_REASON_NONE;
-	priv->recheck_available.unavailable_reason = NM_DEVICE_STATE_REASON_NONE;
+	if (new_state > NM_DEVICE_STATE_UNKNOWN) {
+		_LOGD (LOGD_DEVICE, "device is %savailable, %s %s",
+			   now_available ? "" : "not ",
+			   new_state == NM_DEVICE_STATE_UNAVAILABLE ? "no change required for" : "will transition to",
+			   state_to_string (new_state == NM_DEVICE_STATE_UNAVAILABLE ? state : new_state));
+
+		priv->recheck_available.available_reason = NM_DEVICE_STATE_REASON_NONE;
+		priv->recheck_available.unavailable_reason = NM_DEVICE_STATE_REASON_NONE;
+	}
+
 	return G_SOURCE_REMOVE;
 }
 
@@ -2587,63 +3215,119 @@ dnsmasq_state_changed_cb (NMDnsMasqManager *manager, guint32 status, gpointer us
 	}
 }
 
-static void
-activation_source_clear (NMDevice *self, gboolean remove_source, int family)
+/*****************************************************************************/
+
+static gboolean
+activation_source_handle_cb4 (gpointer user_data)
+{
+	activation_source_handle_cb (user_data, AF_INET);
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+activation_source_handle_cb6 (gpointer user_data)
+{
+	activation_source_handle_cb (user_data, AF_INET6);
+	return G_SOURCE_REMOVE;
+}
+
+static ActivationHandleData *
+activation_source_get_by_family (NMDevice *self,
+                                 int family,
+                                 GSourceFunc *out_idle_func)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint *act_source_id;
-	gpointer *act_source_func;
 
 	if (family == AF_INET6) {
-		act_source_id = &priv->act_source6_id;
-		act_source_func = &priv->act_source6_func;
+		NM_SET_OUT (out_idle_func, activation_source_handle_cb6);
+		return &priv->act_handle6;
 	} else {
-		act_source_id = &priv->act_source_id;
-		act_source_func = &priv->act_source_func;
+		NM_SET_OUT (out_idle_func, activation_source_handle_cb4);
+		g_return_val_if_fail (family == AF_INET, &priv->act_handle4);
+		return &priv->act_handle4;
 	}
+}
 
-	if (*act_source_id) {
-		if (remove_source)
-			g_source_remove (*act_source_id);
-		*act_source_id = 0;
-		*act_source_func = NULL;
+static void
+activation_source_clear (NMDevice *self, int family)
+{
+	ActivationHandleData *act_data;
+
+	act_data = activation_source_get_by_family (self, family, NULL);
+
+	if (act_data->id) {
+		_LOGD (LOGD_DEVICE, "activation-stage: clear %s,%d (id %u)",
+		       _activation_func_to_string (act_data->func), family, act_data->id);
+		nm_clear_g_source (&act_data->id);
+		act_data->func = NULL;
 	}
 }
 
 static void
-activation_source_schedule (NMDevice *self, GSourceFunc func, int family)
+activation_source_handle_cb (NMDevice *self, int family)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint *act_source_id;
-	gpointer *act_source_func;
+	ActivationHandleData *act_data, a;
 
-	if (family == AF_INET6) {
-		act_source_id = &priv->act_source6_id;
-		act_source_func = &priv->act_source6_func;
-	} else {
-		act_source_id = &priv->act_source_id;
-		act_source_func = &priv->act_source_func;
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	act_data = activation_source_get_by_family (self, family, NULL);
+
+	g_return_if_fail (act_data->id);
+	g_return_if_fail (act_data->func);
+
+	a = *act_data;
+
+	act_data->func = NULL;
+	act_data->id = 0;
+
+	_LOGD (LOGD_DEVICE, "activation-stage: invoke %s,%d (id %u)",
+	       _activation_func_to_string (a.func), family, a.id);
+
+	a.func (self);
+
+	_LOGD (LOGD_DEVICE, "activation-stage: complete %s,%d (id %u)",
+	       _activation_func_to_string (a.func), family, a.id);
+}
+
+static void
+activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int family)
+{
+	ActivationHandleData *act_data;
+	GSourceFunc source_func;
+	guint new_id = 0;
+
+	act_data = activation_source_get_by_family (self, family, &source_func);
+
+	if (act_data->id && act_data->func != func) {
+		/* Don't bother rescheduling the same function that's about to
+		 * run anyway.  Fixes issues with crappy wireless drivers sending
+		 * streams of associate events before NM has had a chance to process
+		 * the first one.
+		 */
+		_LOGD (LOGD_DEVICE, "activation-stage: already scheduled %s,%d (id %u)",
+		       _activation_func_to_string (func), family, act_data->id);
+		return;
 	}
 
-	if (*act_source_id) {
-		if (*act_source_func == func) {
-			/* Don't bother rescheduling the same function that's about to
-			 * run anyway.  Fixes issues with crappy wireless drivers sending
-			 * streams of associate events before NM has had a chance to process
-			 * the first one.
-			 */
-			_LOGD (LOGD_DEVICE, "activation stage already scheduled");
-			return;
-		} else {
-			_LOGW (LOGD_DEVICE, "a different activation stage already scheduled");
-			activation_source_clear (self, TRUE, family);
-		}
+	new_id = g_idle_add (source_func, self);
+
+	if (act_data->id) {
+		_LOGW (LOGD_DEVICE, "activation-stage: schedule %s,%d which replaces %s,%d (id %u -> %u)",
+		       _activation_func_to_string (func), family,
+		       _activation_func_to_string (act_data->func), family,
+		       act_data->id, new_id);
+		nm_clear_g_source (&act_data->id);
+	} else {
+		_LOGD (LOGD_DEVICE, "activation-stage: schedule %s,%d (id %u)",
+		       _activation_func_to_string (func), family, new_id);
 	}
 
-	*act_source_id = g_idle_add (func, self);
-	*act_source_func = func;
+	act_data->func = func;
+	act_data->id = new_id;
 }
 
+/*****************************************************************************/
+
 static gboolean
 get_ip_config_may_fail (NMDevice *self, int family)
 {
@@ -2652,7 +3336,7 @@ get_ip_config_may_fail (NMDevice *self, int family)
 
 	g_return_val_if_fail (self != NULL, TRUE);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	/* Fail the connection if the failed IP method is required to complete */
@@ -2675,26 +3359,31 @@ master_ready (NMDevice *self,
               NMActiveConnection *active)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActiveConnection *master;
+	NMActiveConnection *master_connection;
+	NMDevice *master;
 
 	g_return_if_fail (priv->state == NM_DEVICE_STATE_PREPARE);
 	g_return_if_fail (!priv->master_ready_handled);
 
 	/* Notify a master device that it has a new slave */
 	g_return_if_fail (nm_active_connection_get_master_ready (active));
-	master = nm_active_connection_get_master (active);
+	master_connection = nm_active_connection_get_master (active);
 
 	priv->master_ready_handled = TRUE;
 	nm_clear_g_signal_handler (active, &priv->master_ready_id);
 
-	priv->master = g_object_ref (nm_active_connection_get_device (master));
-	nm_device_master_add_slave (priv->master,
-	                            self,
-	                            nm_active_connection_get_assumed (active) ? FALSE : TRUE);
+	master = nm_active_connection_get_device (master_connection);
 
 	_LOGD (LOGD_DEVICE, "master connection ready; master device %s",
-	       nm_device_get_iface (priv->master));
+	       nm_device_get_iface (master));
+
+	if (priv->master && priv->master != master)
+		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 
+	/* If the master didn't change, add-slave only rechecks whether to assume a connection. */
+	nm_device_master_add_slave (master,
+	                            self,
+	                            nm_active_connection_get_assumed (active) ? FALSE : TRUE);
 }
 
 static void
@@ -2706,6 +3395,45 @@ master_ready_cb (NMActiveConnection *active,
 	nm_device_activate_schedule_stage2_device_config (self);
 }
 
+static void
+lldp_neighbors_changed (NMLldpListener *lldp_listener, GParamSpec *pspec,
+                        gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+
+	g_object_notify (G_OBJECT (self), NM_DEVICE_LLDP_NEIGHBORS);
+}
+
+static gboolean
+lldp_rx_enabled (NMDevice *self)
+{
+	NMConnection *connection;
+	NMSettingConnection *s_con;
+	NMSettingConnectionLldp lldp = NM_SETTING_CONNECTION_LLDP_DEFAULT;
+
+	connection = nm_device_get_applied_connection (self);
+	g_return_val_if_fail (connection, FALSE);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_return_val_if_fail (s_con, FALSE);
+
+	lldp = nm_setting_connection_get_lldp (s_con);
+	if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT) {
+		gs_free char *value = NULL;
+
+		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                               "connection.lldp",
+		                                               self);
+		lldp = _nm_utils_ascii_str_to_int64 (value, 10,
+		                                     NM_SETTING_CONNECTION_LLDP_DEFAULT,
+		                                     NM_SETTING_CONNECTION_LLDP_ENABLE_RX,
+		                                     NM_SETTING_CONNECTION_LLDP_DEFAULT);
+		if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT)
+			lldp = NM_SETTING_CONNECTION_LLDP_DISABLE;
+	}
+	return lldp == NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
+}
+
 static NMActStageReturn
 act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason)
 {
@@ -2713,48 +3441,39 @@ act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason)
 }
 
 /*
- * nm_device_activate_stage1_device_prepare
+ * activate_stage1_device_prepare
  *
  * Prepare for device activation
  *
  */
-static gboolean
-nm_device_activate_stage1_device_prepare (gpointer user_data)
+static void
+activate_stage1_device_prepare (NMDevice *self)
 {
-	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 	NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request);
 
-	/* Clear the activation source ID now that this stage has run */
-	activation_source_clear (self, FALSE, 0);
-
 	priv->ip4_state = priv->ip6_state = IP_NONE;
 
 	/* Notify the new ActiveConnection along with the state change */
 	g_object_notify (G_OBJECT (self), NM_DEVICE_ACTIVE_CONNECTION);
 
-	_LOGD (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 */
 	if (!nm_active_connection_get_assumed (active)) {
 		ret = NM_DEVICE_GET_CLASS (self)->act_stage1_prepare (self, &reason);
 		if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
-			goto out;
+			return;
 		} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-			goto out;
+			return;
 		}
 		g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 	}
 
 	nm_device_activate_schedule_stage2_device_config (self);
-
-out:
-	_LOGD (LOGD_DEVICE, "Activation: Stage 1 of 5 (Device Prepare) complete.");
-	return FALSE;
 }
 
 
@@ -2774,9 +3493,7 @@ nm_device_activate_schedule_stage1_device_prepare (NMDevice *self)
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	g_return_if_fail (priv->act_request);
 
-	activation_source_schedule (self, nm_device_activate_stage1_device_prepare, 0);
-
-	_LOGD (LOGD_DEVICE, "Activation: Stage 1 of 5 (Device Prepare) scheduled...");
+	activation_source_schedule (self, activate_stage1_device_prepare, AF_INET);
 }
 
 static NMActStageReturn
@@ -2787,16 +3504,15 @@ act_stage2_config (NMDevice *self, NMDeviceStateReason *reason)
 }
 
 /*
- * nm_device_activate_stage2_device_config
+ * activate_stage2_device_config
  *
  * Determine device parameters and set those on the device, ie
  * for wireless devices, set SSID, keys, etc.
  *
  */
-static gboolean
-nm_device_activate_stage2_device_config (gpointer user_data)
+static void
+activate_stage2_device_config (NMDevice *self)
 {
-	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
@@ -2804,10 +3520,6 @@ nm_device_activate_stage2_device_config (gpointer user_data)
 	NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request);
 	GSList *iter;
 
-	/* Clear the activation source ID now that this stage has run */
-	activation_source_clear (self, FALSE, 0);
-
-	_LOGD (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 */
@@ -2817,15 +3529,15 @@ nm_device_activate_stage2_device_config (gpointer user_data)
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_FIRMWARE_MISSING);
 			else
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-			goto out;
+			return;
 		}
 
 		ret = NM_DEVICE_GET_CLASS (self)->act_stage2_config (self, &reason);
 		if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
-			goto out;
+			return;
 		else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-			goto out;
+			return;
 		}
 		g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 	}
@@ -2836,19 +3548,39 @@ nm_device_activate_stage2_device_config (gpointer user_data)
 		NMDeviceState slave_state = nm_device_get_state (info->slave);
 
 		if (slave_state == NM_DEVICE_STATE_IP_CONFIG)
-			nm_device_enslave_slave (self, info->slave, nm_device_get_connection (info->slave));
+			nm_device_master_enslave_slave (self, info->slave, nm_device_get_applied_connection (info->slave));
 		else if (   nm_device_uses_generated_assumed_connection (self)
 		         && slave_state <= NM_DEVICE_STATE_DISCONNECTED)
 			nm_device_queue_recheck_assume (info->slave);
 	}
 
-	_LOGD (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) successful.");
+	if (lldp_rx_enabled (self)) {
+		gs_free_error GError *error = NULL;
+		gconstpointer addr;
+		size_t addr_length;
 
-	nm_device_activate_schedule_stage3_ip_config_start (self);
+		if (priv->lldp_listener)
+			nm_lldp_listener_stop (priv->lldp_listener);
+		else {
+			priv->lldp_listener = nm_lldp_listener_new ();
+			g_signal_connect (priv->lldp_listener,
+			                  "notify::" NM_LLDP_LISTENER_NEIGHBORS,
+			                  G_CALLBACK (lldp_neighbors_changed),
+			                  self);
+		}
 
-out:
-	_LOGD (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) complete.");
-	return FALSE;
+		addr = nm_platform_link_get_address (NM_PLATFORM_GET, priv->ifindex, &addr_length);
+
+		if (nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self),
+		                            nm_device_get_iface (self), addr, addr_length, &error))
+			_LOGD (LOGD_DEVICE, "LLDP listener %p started", priv->lldp_listener);
+		else {
+			_LOGD (LOGD_DEVICE, "LLDP listener %p could not be started: %s",
+			       priv->lldp_listener, error->message);
+		}
+	}
+
+	nm_device_activate_schedule_stage3_ip_config_start (self);
 }
 
 
@@ -2893,56 +3625,74 @@ nm_device_activate_schedule_stage2_device_config (NMDevice *self)
 		}
 	}
 
-	activation_source_schedule (self, nm_device_activate_stage2_device_config, 0);
-
-	_LOGD (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) scheduled...");
+	activation_source_schedule (self, activate_stage2_device_config, AF_INET);
 }
 
-/*********************************************/
-/* avahi-autoipd stuff */
-
+/*
+ * nm_device_check_ip_failed
+ *
+ * Progress the device to appropriate state if both IPv4 and IPv6 failed
+ */
 static void
-aipd_timeout_remove (NMDevice *self)
+nm_device_check_ip_failed (NMDevice *self, gboolean may_fail)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceState state;
 
-	if (priv->aipd_timeout) {
-		g_source_remove (priv->aipd_timeout);
-		priv->aipd_timeout = 0;
+	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);
 }
 
+/*********************************************/
+/* IPv4LL stuff */
+
 static void
-aipd_cleanup (NMDevice *self)
+ipv4ll_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->aipd_watch) {
-		g_source_remove (priv->aipd_watch);
-		priv->aipd_watch = 0;
+	if (priv->ipv4ll) {
+		sd_ipv4ll_set_callback (priv->ipv4ll, NULL, NULL);
+		sd_ipv4ll_stop (priv->ipv4ll);
+		priv->ipv4ll = sd_ipv4ll_unref (priv->ipv4ll);
 	}
 
-	if (priv->aipd_pid > 0) {
-		nm_utils_kill_child_sync (priv->aipd_pid, SIGKILL, LOGD_AUTOIP4, "avahi-autoipd", NULL, 0, 0);
-		priv->aipd_pid = -1;
-	}
-
-	aipd_timeout_remove (self);
+	nm_clear_g_source (&priv->ipv4ll_timeout);
 }
 
 static NMIP4Config *
-aipd_get_ip4_config (NMDevice *self, guint32 lla)
+ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 {
 	NMIP4Config *config = NULL;
 	NMPlatformIP4Address address;
 	NMPlatformIP4Route route;
 
-	config = nm_ip4_config_new ();
+	config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
 	g_assert (config);
 
 	memset (&address, 0, sizeof (address));
-	address.address = lla;
-	address.plen = 16;
+	nm_platform_ip4_address_set_addr (&address, lla, 16);
 	address.source = NM_IP_CONFIG_SOURCE_IP4LL;
 	nm_ip4_config_add_address (config, &address);
 
@@ -2960,22 +3710,21 @@ aipd_get_ip4_config (NMDevice *self, guint32 lla)
 #define IPV4LL_NETWORK (htonl (0xA9FE0000L))
 #define IPV4LL_NETMASK (htonl (0xFFFF0000L))
 
-void
-nm_device_handle_autoip4_event (NMDevice *self,
-                                const char *event,
-                                const char *address)
+static void
+nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 {
+	NMDevice *self = data;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection = NULL;
 	const char *method;
-	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
-
-	g_return_if_fail (event != NULL);
+	struct in_addr address;
+	NMIP4Config *config;
+	int r;
 
 	if (priv->act_request == NULL)
 		return;
 
-	connection = nm_act_request_get_connection (priv->act_request);
+	connection = nm_act_request_get_applied_connection (priv->act_request);
 	g_assert (connection);
 
 	/* Ignore if the connection isn't an AutoIP connection */
@@ -2983,86 +3732,62 @@ nm_device_handle_autoip4_event (NMDevice *self,
 	if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) != 0)
 		return;
 
-	if (strcmp (event, "BIND") == 0) {
-		guint32 lla;
-		NMIP4Config *config;
-
-		if (inet_pton (AF_INET, address, &lla) <= 0) {
-			_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);
+	switch (event) {
+	case SD_IPV4LL_EVENT_BIND:
+		r = sd_ipv4ll_get_address (ll, &address);
+		if (r < 0) {
+			_LOGE (LOGD_AUTOIP4, "invalid IPv4 link-local address received, error %d.", r);
+			priv->ip4_state = IP_FAIL;
+			nm_device_check_ip_failed (self, FALSE);
 			return;
 		}
 
-		if ((lla & IPV4LL_NETMASK) != IPV4LL_NETWORK) {
-			_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);
+		if ((address.s_addr & IPV4LL_NETMASK) != IPV4LL_NETWORK) {
+			_LOGE (LOGD_AUTOIP4, "invalid address %08x received (not link-local).", address.s_addr);
+			priv->ip4_state = IP_FAIL;
+			nm_device_check_ip_failed (self, FALSE);
 			return;
 		}
 
-		config = aipd_get_ip4_config (self, lla);
+		config = ipv4ll_get_ip4_config (self, address.s_addr);
 		if (config == NULL) {
-			_LOGE (LOGD_AUTOIP4, "failed to get autoip config");
-			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+			_LOGE (LOGD_AUTOIP4, "failed to get IPv4LL config");
+			priv->ip4_state = IP_FAIL;
+			nm_device_check_ip_failed (self, FALSE);
 			return;
 		}
 
 		if (priv->ip4_state == IP_CONF) {
-			aipd_timeout_remove (self);
+			nm_clear_g_source (&priv->ipv4ll_timeout);
 			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)) {
+			if (!ip4_config_merge_and_apply (self, config, TRUE, NULL)) {
 				_LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
-				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+				priv->ip4_state = IP_FAIL;
+				nm_device_check_ip_failed (self, FALSE);
 			}
 		} else
 			g_assert_not_reached ();
 
 		g_object_unref (config);
-	} else {
-		_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);
+		break;
+	default:
+		_LOGW (LOGD_AUTOIP4, "IPv4LL address no longer valid after event %d.", event);
+		priv->ip4_state = IP_FAIL;
+		nm_device_check_ip_failed (self, FALSE);
 	}
 }
 
-static void
-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;
-
-	if (!priv->aipd_watch)
-		return;
-	priv->aipd_watch = 0;
-
-	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);
-
-	state = nm_device_get_state (self);
-	if (nm_device_is_activating (self) || (state == NM_DEVICE_STATE_ACTIVATED))
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
-}
-
 static gboolean
-aipd_timeout_cb (gpointer user_data)
+ipv4ll_timeout_cb (gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->aipd_timeout) {
-		_LOGI (LOGD_AUTOIP4, "avahi-autoipd timed out.");
-		priv->aipd_timeout = 0;
-		aipd_cleanup (self);
+	if (priv->ipv4ll_timeout) {
+		_LOGI (LOGD_AUTOIP4, "IPv4LL configuration timed out.");
+		priv->ipv4ll_timeout = 0;
+		ipv4ll_cleanup (self);
 
 		if (priv->ip4_state == IP_CONF)
 			nm_device_activate_schedule_ip4_config_timeout (self);
@@ -3071,66 +3796,68 @@ aipd_timeout_cb (gpointer user_data)
 	return FALSE;
 }
 
-/* default to installed helper, but can be modified for testing */
-const char *nm_device_autoipd_helper_path = LIBEXECDIR "/nm-avahi-autoipd.action";
-
 static NMActStageReturn
-aipd_start (NMDevice *self, NMDeviceStateReason *reason)
+ipv4ll_start (NMDevice *self, NMDeviceStateReason *reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *argv[6];
-	char *cmdline;
-	const char *aipd_binary;
-	int i = 0;
-	GError *error = NULL;
+	const struct ether_addr *addr;
+	int ifindex, r;
+	size_t addr_len;
 
-	aipd_cleanup (self);
+	ipv4ll_cleanup (self);
 
-	/* Find avahi-autoipd */
-	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;
+	r = sd_ipv4ll_new (&priv->ipv4ll);
+	if (r < 0) {
+		_LOGE (LOGD_AUTOIP4, "IPv4LL: new() failed with error %d", r);
+		goto fail;
 	}
 
-	argv[i++] = aipd_binary;
-	argv[i++] = "--script";
-	argv[i++] = nm_device_autoipd_helper_path;
+	r = sd_ipv4ll_attach_event (priv->ipv4ll, NULL, 0);
+	if (r < 0) {
+		_LOGE (LOGD_AUTOIP4, "IPv4LL: attach_event() failed with error %d", r);
+		goto fail;
+	}
 
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_AUTOIP4))
-		argv[i++] = "--debug";
-	argv[i++] = nm_device_get_ip_iface (self);
-	argv[i++] = NULL;
+	ifindex = nm_device_get_ip_ifindex (self);
+	addr = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &addr_len);
+	if (!addr || addr_len != ETH_ALEN) {
+		_LOGE (LOGD_AUTOIP4, "IPv4LL: can't retrieve hardware address");
+		goto fail;
+	}
 
-	cmdline = g_strjoinv (" ", (char **) argv);
-	_LOGD (LOGD_AUTOIP4, "running: %s", cmdline);
-	g_free (cmdline);
+	r = sd_ipv4ll_set_mac (priv->ipv4ll, addr);
+	if (r < 0) {
+		_LOGE (LOGD_AUTOIP4, "IPv4LL: set_mac() failed with error %d", r);
+		goto fail;
+	}
 
-	if (!g_spawn_async ("/", (char **) argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
-	                    nm_utils_setpgid, NULL, &(priv->aipd_pid), &error)) {
-		_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;
+	r = sd_ipv4ll_set_index (priv->ipv4ll, ifindex);
+	if (r < 0) {
+		_LOGE (LOGD_AUTOIP4, "IPv4LL: set_index() failed with error %d", r);
+		goto fail;
 	}
 
-	_LOGD (LOGD_DEVICE | LOGD_AUTOIP4,
-	       "Activation: Stage 3 of 5 (IP Configure Start) started"
-	       " avahi-autoipd...");
+	r = sd_ipv4ll_set_callback (priv->ipv4ll, nm_device_handle_ipv4ll_event, self);
+	if (r < 0) {
+		_LOGE (LOGD_AUTOIP4, "IPv4LL: set_callback() failed with error %d", r);
+		goto fail;
+	}
+
+	r = sd_ipv4ll_start (priv->ipv4ll);
+	if (r < 0) {
+		_LOGE (LOGD_AUTOIP4, "IPv4LL: start() failed with error %d", r);
+		goto fail;
+	}
 
-	/* Monitor the child process so we know when it dies */
-	priv->aipd_watch = g_child_watch_add (priv->aipd_pid, aipd_watch_cb, self);
+	_LOGI (LOGD_DEVICE | LOGD_AUTOIP4, "IPv4LL: started");
 
 	/* Start a timeout to bound the address attempt */
-	priv->aipd_timeout = g_timeout_add_seconds (20, aipd_timeout_cb, self);
+	priv->ipv4ll_timeout = g_timeout_add_seconds (20, ipv4ll_timeout_cb, self);
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
+fail:
+	*reason = NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED;
+	return NM_ACT_STAGE_RETURN_FAILURE;
 }
 
 /*********************************************/
@@ -3185,16 +3912,17 @@ static void
 ensure_con_ip4_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	NMConnection *connection;
 
 	if (priv->con_ip4_config)
 		return;
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (!connection)
 		return;
 
-	priv->con_ip4_config = nm_ip4_config_new ();
+	priv->con_ip4_config = nm_ip4_config_new (ip_ifindex);
 	nm_ip4_config_merge_setting (priv->con_ip4_config,
 	                             nm_connection_get_setting_ip4_config (connection),
 	                             nm_device_get_ip4_route_metric (self));
@@ -3210,16 +3938,17 @@ static void
 ensure_con_ip6_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	NMConnection *connection;
 
 	if (priv->con_ip6_config)
 		return;
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (!connection)
 		return;
 
-	priv->con_ip6_config = nm_ip6_config_new ();
+	priv->con_ip6_config = nm_ip6_config_new (ip_ifindex);
 	nm_ip6_config_merge_setting (priv->con_ip6_config,
 	                             nm_connection_get_setting_ip6_config (connection),
 	                             nm_device_get_ip6_route_metric (self));
@@ -3243,10 +3972,7 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 
 	if (priv->dhcp4_client) {
 		/* Stop any ongoing DHCP transaction on this device */
-		if (priv->dhcp4_state_sigid) {
-			g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_state_sigid);
-			priv->dhcp4_state_sigid = 0;
-		}
+		nm_clear_g_signal_handler (priv->dhcp4_client, &priv->dhcp4_state_sigid);
 
 		nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE);
 
@@ -3258,11 +3984,20 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	}
 
 	if (priv->dhcp4_config) {
-		g_clear_object (&priv->dhcp4_config);
+		nm_exported_object_clear_and_unexport (&priv->dhcp4_config);
 		g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG);
 	}
 }
 
+static void
+_ip4_config_merge_default (gpointer value, gpointer user_data)
+{
+	NMIP4Config *src = (NMIP4Config *) value;
+	NMIP4Config *dst = (NMIP4Config *) user_data;
+
+	nm_ip4_config_merge (dst, src, NM_IP_CONFIG_MERGE_DEFAULT);
+}
+
 static gboolean
 ip4_config_merge_and_apply (NMDevice *self,
                             NMIP4Config *config,
@@ -3288,7 +4023,7 @@ ip4_config_merge_and_apply (NMDevice *self,
 	}
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (connection) {
 		NMSettingIPConfig *s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
@@ -3298,7 +4033,7 @@ ip4_config_merge_and_apply (NMDevice *self,
 		}
 	}
 
-	composite = nm_ip4_config_new ();
+	composite = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
 
 	if (commit)
 		ensure_con_ip4_config (self);
@@ -3308,8 +4043,9 @@ ip4_config_merge_and_apply (NMDevice *self,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
 		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
 	}
-	if (priv->vpn4_config)
-		nm_ip4_config_merge (composite, priv->vpn4_config, NM_IP_CONFIG_MERGE_DEFAULT);
+
+	g_slist_foreach (priv->vpn4_configs, _ip4_config_merge_default, composite);
+
 	if (priv->ext_ip4_config)
 		nm_ip4_config_merge (composite, priv->ext_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT);
 
@@ -3327,7 +4063,6 @@ ip4_config_merge_and_apply (NMDevice *self,
 	if (priv->con_ip4_config)
 		nm_ip4_config_merge (composite, priv->con_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT);
 
-
 	/* Add the default route.
 	 *
 	 * We keep track of the default route of a device in a private field.
@@ -3366,7 +4101,7 @@ ip4_config_merge_and_apply (NMDevice *self,
 
 	if (   !priv->v4_commit_first_time
 	    && connection_is_never_default) {
-		/* If the connection is explicitly configured as never-default, we enforce the (absense of the)
+		/* If the connection is explicitly configured as never-default, we enforce the (absence of the)
 		 * default-route only once. That allows the user to configure a connection as never-default,
 		 * but he can add default routes externally (via a dispatcher script) and NM will not interfere. */
 		goto END_ADD_DEFAULT_ROUTE;
@@ -3390,7 +4125,7 @@ ip4_config_merge_and_apply (NMDevice *self,
 		goto END_ADD_DEFAULT_ROUTE;
 
 	has_direct_route = (   gateway == 0
-	                    || nm_ip4_config_get_subnet_for_host (composite, gateway)
+	                    || nm_ip4_config_destination_is_direct (composite, gateway, 32)
 	                    || nm_ip4_config_get_direct_route_for_host (composite, gateway));
 
 	priv->default_route.v4_has = TRUE;
@@ -3452,7 +4187,8 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 	} else {
 		/* Notify dispatcher scripts of new DHCP4 config */
 		nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE,
-		                    nm_device_get_connection (self),
+		                    nm_device_get_settings_connection (self),
+		                    nm_device_get_applied_connection (self),
 		                    self,
 		                    NULL,
 		                    NULL,
@@ -3472,7 +4208,7 @@ dhcp4_restart_cb (gpointer user_data)
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	priv->dhcp4_restart_id = 0;
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 
 	if (dhcp4_start (self, connection, &reason) == NM_ACT_STAGE_RETURN_FAILURE)
 		priv->dhcp4_restart_id = g_timeout_add_seconds (120, dhcp4_restart_cb, self);
@@ -3517,22 +4253,6 @@ dhcp4_fail (NMDevice *self, gboolean timeout)
 }
 
 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,
                      NMIP4Config *ip4_config,
@@ -3558,7 +4278,8 @@ dhcp4_state_changed (NMDhcpClient *client,
 			break;
 		}
 
-		dhcp4_update_config (self, priv->dhcp4_config, options);
+		nm_dhcp4_config_set_options (priv->dhcp4_config, options);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG);
 
 		if (priv->ip4_state == IP_CONF)
 			nm_device_activate_schedule_ip4_config_result (self, ip4_config);
@@ -3584,6 +4305,28 @@ dhcp4_state_changed (NMDhcpClient *client,
 	}
 }
 
+static int
+dhcp4_get_timeout (NMDevice *self, NMSettingIP4Config *s_ip4)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gs_free char *value = NULL;
+	int timeout;
+
+	timeout = nm_setting_ip4_config_get_dhcp_timeout (s_ip4);
+	if (timeout)
+		return timeout;
+
+	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+	                                               "ipv4.dhcp-timeout",
+	                                               self);
+	timeout = _nm_utils_ascii_str_to_int64 (value, 10,
+	                                        0, G_MAXINT32, 0);
+	if (timeout)
+		return timeout;
+
+	return priv->dhcp_timeout;
+}
+
 static NMActStageReturn
 dhcp4_start (NMDevice *self,
              NMConnection *connection,
@@ -3598,8 +4341,7 @@ dhcp4_start (NMDevice *self,
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
 	/* Clear old exported DHCP options */
-	if (priv->dhcp4_config)
-		g_object_unref (priv->dhcp4_config);
+	nm_exported_object_clear_and_unexport (&priv->dhcp4_config);
 	priv->dhcp4_config = nm_dhcp4_config_new ();
 
 	hw_addr = nm_platform_link_get_address (NM_PLATFORM_GET, nm_device_get_ip_ifindex (self), &hw_addr_len);
@@ -3618,8 +4360,9 @@ dhcp4_start (NMDevice *self,
 	                                                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_fqdn (NM_SETTING_IP4_CONFIG (s_ip4)),
 	                                                nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4)),
-	                                                priv->dhcp_timeout,
+	                                                dhcp4_get_timeout (self, NM_SETTING_IP4_CONFIG (s_ip4)),
 	                                                priv->dhcp_anycast_address,
 	                                                NULL);
 
@@ -3657,7 +4400,7 @@ nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 	/* Terminate old DHCP instance and release the old lease */
 	dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, release);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	/* Start DHCP again on the interface */
@@ -3687,10 +4430,11 @@ reserve_shared_ip (NMDevice *self, NMSettingIPConfig *s_ip4, NMPlatformIP4Addres
 	if (s_ip4 && nm_setting_ip_config_get_num_addresses (s_ip4)) {
 		/* Use the first user-supplied address */
 		NMIPAddress *user = nm_setting_ip_config_get_address (s_ip4, 0);
+		in_addr_t a;
 
 		g_assert (user);
-		nm_ip_address_get_address_binary (user, &address->address);
-		address->plen = nm_ip_address_get_prefix (user);
+		nm_ip_address_get_address_binary (user, &a);
+		nm_platform_ip4_address_set_addr (address, a, 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 */
@@ -3703,8 +4447,7 @@ reserve_shared_ip (NMDevice *self, NMSettingIPConfig *s_ip4, NMPlatformIP4Addres
 				return FALSE;
 			}
 		}
-		address->address = start + count;
-		address->plen = 24;
+		nm_platform_ip4_address_set_addr (address, start + count, 24);
 
 		g_hash_table_insert (shared_ips,
 		                     GUINT_TO_POINTER (address->address),
@@ -3727,7 +4470,7 @@ shared4_new_config (NMDevice *self, NMConnection *connection, NMDeviceStateReaso
 		return NULL;
 	}
 
-	config = nm_ip4_config_new ();
+	config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
 	address.source = NM_IP_CONFIG_SOURCE_SHARED;
 	nm_ip4_config_add_address (config, &address);
 
@@ -3852,7 +4595,7 @@ act_stage3_ip4_config_start (NMDevice *self,
 
 	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	if (   connection_ip4_method_requires_carrier (connection, NULL)
@@ -3884,10 +4627,10 @@ act_stage3_ip4_config_start (NMDevice *self,
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
 		ret = dhcp4_start (self, connection, reason);
 	else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) == 0)
-		ret = aipd_start (self, reason);
+		ret = ipv4ll_start (self, reason);
 	else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) {
 		/* Use only IPv4 config from the connection data */
-		*out_config = nm_ip4_config_new ();
+		*out_config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
 		g_assert (*out_config);
 		ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
@@ -3917,14 +4660,10 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE;
 	g_clear_object (&priv->dhcp6_ip6_config);
 	g_clear_pointer (&priv->dhcp6_event_id, g_free);
-
 	nm_clear_g_source (&priv->dhcp6_restart_id);
 
 	if (priv->dhcp6_client) {
-		if (priv->dhcp6_state_sigid) {
-			g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_state_sigid);
-			priv->dhcp6_state_sigid = 0;
-		}
+		nm_clear_g_signal_handler (priv->dhcp6_client, &priv->dhcp6_state_sigid);
 
 		if (   cleanup_type == CLEANUP_TYPE_DECONFIGURE
 		    || cleanup_type == CLEANUP_TYPE_REMOVED)
@@ -3936,11 +4675,20 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE);
 
 	if (priv->dhcp6_config) {
-		g_clear_object (&priv->dhcp6_config);
+		nm_exported_object_clear_and_unexport (&priv->dhcp6_config);
 		g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG);
 	}
 }
 
+static void
+_ip6_config_merge_default (gpointer value, gpointer user_data)
+{
+	NMIP6Config *src = (NMIP6Config *) value;
+	NMIP6Config *dst = (NMIP6Config *) user_data;
+
+	nm_ip6_config_merge (dst, src, NM_IP_CONFIG_MERGE_DEFAULT);
+}
+
 static gboolean
 ip6_config_merge_and_apply (NMDevice *self,
                             gboolean commit,
@@ -3958,7 +4706,7 @@ ip6_config_merge_and_apply (NMDevice *self,
 	gboolean ignore_auto_dns = FALSE;
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (connection) {
 		NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection);
 
@@ -3969,10 +4717,11 @@ ip6_config_merge_and_apply (NMDevice *self,
 	}
 
 	/* If no config was passed in, create a new one */
-	composite = nm_ip6_config_new ();
+	composite = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
 
 	if (commit)
 		ensure_con_ip6_config (self);
+	g_assert (composite);
 
 	/* Merge all the IP configs into the composite config */
 	if (priv->ac_ip6_config) {
@@ -3985,8 +4734,9 @@ ip6_config_merge_and_apply (NMDevice *self,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
 		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
 	}
-	if (priv->vpn6_config)
-		nm_ip6_config_merge (composite, priv->vpn6_config, NM_IP_CONFIG_MERGE_DEFAULT);
+
+	g_slist_foreach (priv->vpn6_configs, _ip6_config_merge_default, composite);
+
 	if (priv->ext_ip6_config)
 		nm_ip6_config_merge (composite, priv->ext_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT);
 
@@ -4121,7 +4871,7 @@ static void
 dhcp6_lease_change (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
+	NMSettingsConnection *settings_connection;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 
 	if (priv->dhcp6_ip6_config == NULL) {
@@ -4132,8 +4882,8 @@ dhcp6_lease_change (NMDevice *self)
 
 	g_assert (priv->dhcp6_client);  /* sanity check */
 
-	connection = nm_device_get_connection (self);
-	g_assert (connection);
+	settings_connection = nm_device_get_settings_connection (self);
+	g_assert (settings_connection);
 
 	/* Apply the updated config */
 	if (ip6_config_merge_and_apply (self, TRUE, &reason) == FALSE) {
@@ -4141,7 +4891,10 @@ dhcp6_lease_change (NMDevice *self)
 		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, self, NULL, NULL, NULL);
+		nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE,
+		                    settings_connection,
+		                    nm_device_get_applied_connection (self),
+		                    self, NULL, NULL, NULL);
 	}
 }
 
@@ -4221,22 +4974,6 @@ dhcp6_timeout (NMDevice *self, NMDhcpClient *client)
 }
 
 static void
-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,
@@ -4273,7 +5010,7 @@ dhcp6_state_changed (NMDhcpClient *client,
 			if (ip6_config) {
 				priv->dhcp6_ip6_config = g_object_ref (ip6_config);
 				priv->dhcp6_event_id = g_strdup (event_id);
-				dhcp6_update_config (self, priv->dhcp6_config, options);
+				nm_dhcp6_config_set_options (priv->dhcp6_config, options);
 				g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG);
 			}
 		}
@@ -4320,6 +5057,9 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	GByteArray *tmp = NULL;
 	const guint8 *hw_addr;
 	size_t hw_addr_len = 0;
+	const struct in6_addr *ll_addr = NULL;
+	NMIP6Config *ip6_config;
+	int i;
 
 	g_assert (connection);
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
@@ -4331,10 +5071,22 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 		g_byte_array_append (tmp, hw_addr, hw_addr_len);
 	}
 
+	ip6_config = priv->ext_ip6_config;
+	for (i = 0; ip6_config && 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)) {
+			ll_addr = &addr->address;
+			break;
+		}
+	}
+	g_return_val_if_fail (ll_addr, FALSE);
+
 	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,
+	                                                ll_addr,
 	                                                nm_connection_get_uuid (connection),
 	                                                nm_device_get_ip6_route_metric (self),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
@@ -4363,14 +5115,14 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason)
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip6;
 
-	g_clear_object (&priv->dhcp6_config);
+	nm_exported_object_clear_and_unexport (&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);
 	g_clear_pointer (&priv->dhcp6_event_id, g_free);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	if (!nm_setting_ip_config_get_may_fail (s_ip6) ||
@@ -4443,10 +5195,14 @@ linklocal6_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->linklocal6_timeout_id) {
-		g_source_remove (priv->linklocal6_timeout_id);
-		priv->linklocal6_timeout_id = 0;
-	}
+	nm_clear_g_source (&priv->linklocal6_timeout_id);
+}
+
+static void
+linklocal6_failed (NMDevice *self)
+{
+	linklocal6_cleanup (self);
+	nm_device_activate_schedule_ip6_config_timeout (self);
 }
 
 static gboolean
@@ -4454,11 +5210,8 @@ linklocal6_timeout_cb (gpointer user_data)
 {
 	NMDevice *self = user_data;
 
-	linklocal6_cleanup (self);
-
 	_LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses failed due to timeout");
-
-	nm_device_activate_schedule_ip6_config_timeout (self);
+	linklocal6_failed (self);
 	return G_SOURCE_REMOVE;
 }
 
@@ -4474,7 +5227,7 @@ linklocal6_complete (NMDevice *self)
 
 	linklocal6_cleanup (self);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
@@ -4502,9 +5255,11 @@ 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;
+	NMConnection *connection;
+	NMSettingIP6Config *s_ip6 = NULL;
+	GError *error = NULL;
 
 	if (priv->nm_ipv6ll == FALSE)
 		return;
@@ -4515,27 +5270,59 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 			const NMPlatformIP6Address *addr;
 
 			addr = nm_ip6_config_get_address (priv->ip6_config, i);
-			if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) {
+			if (   IN6_IS_ADDR_LINKLOCAL (&addr->address)
+			    && !(addr->flags & IFA_F_DADFAILED)) {
 				/* 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));
+
+	connection = nm_device_get_applied_connection (self);
+	if (connection)
+		s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
+
+	if (s_ip6 && nm_setting_ip6_config_get_addr_gen_mode (s_ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
+		if (!nm_utils_ipv6_addr_set_stable_privacy (&lladdr,
+		                                            nm_device_get_iface (self),
+			                                    nm_connection_get_uuid (connection),
+		                                            priv->linklocal6_dad_counter++,
+			                                    &error)) {
+			_LOGW (LOGD_IP6, "linklocal6: failed to generate an address: %s", error->message);
+			g_clear_error (&error);
+			linklocal6_failed (self);
+			return;
+		}
+		_LOGD (LOGD_IP6, "linklocal6: using IPv6 stable-privacy addressing");
+	} else {
+		NMUtilsIPv6IfaceId iid;
+
+		if (priv->linklocal6_timeout_id) {
+			/* We already started and attempt to add a LL address. For the EUI-64
+			 * mode we can't pick a new one, we'll just fail. */
+			_LOGW (LOGD_IP6, "linklocal6: DAD failed for an EUI-64 address");
+			linklocal6_failed (self);
+			return;
+		}
+
+		if (!nm_device_get_ip_iface_identifier (self, &iid)) {
+			_LOGW (LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue");
+			return;
+		}
+		_LOGD (LOGD_IP6, "linklocal6: using EUI-64 identifier to generate IPv6LL address");
+
+		nm_utils_ipv6_addr_set_interface_identfier (&lladdr, iid);
+	}
+
+	_LOGD (LOGD_IP6, "linklocal6: adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL));
 	if (!nm_platform_ip6_address_add (NM_PLATFORM_GET,
 	                                  ip_ifindex,
 	                                  lladdr,
-	                                  in6addr_any,
 	                                  64,
+	                                  in6addr_any,
 	                                  NM_PLATFORM_LIFETIME_PERMANENT,
 	                                  NM_PLATFORM_LIFETIME_PERMANENT,
 	                                  0)) {
@@ -4556,7 +5343,7 @@ linklocal6_start (NMDevice *self)
 	if (have_ip6_address (priv->ip6_config, TRUE))
 		return NM_ACT_STAGE_RETURN_FINISH;
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
@@ -4576,53 +5363,6 @@ linklocal6_start (NMDevice *self)
 
 /******************************************/
 
-static void
-print_support_extended_ifa_flags (NMSettingIP6ConfigPrivacy use_tempaddr)
-{
-	static gint8 warn = 0;
-	static gint8 s_libnl = -1, s_kernel;
-
-	if (warn >= 2)
-		return;
-
-	if (s_libnl == -1) {
-		s_libnl = !!nm_platform_check_support_libnl_extended_ifa_flags ();
-		s_kernel = !!nm_platform_check_support_kernel_extended_ifa_flags (NM_PLATFORM_GET);
-
-		if (s_libnl && s_kernel) {
-			nm_log_dbg (LOGD_IP6, "kernel and libnl support extended IFA_FLAGS (needed by NM for IPv6 private addresses)");
-			warn = 2;
-			return;
-		}
-	}
-
-	if (   use_tempaddr != NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR
-	    && use_tempaddr != NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR) {
-		if (warn == 0) {
-			nm_log_dbg (LOGD_IP6, "%s%s%s %s not support extended IFA_FLAGS (needed by NM for IPv6 private addresses)",
-			                      !s_kernel ? "kernel" : "",
-			                      !s_kernel && !s_libnl ? " and " : "",
-			                      !s_libnl ? "libnl" : "",
-			                      !s_kernel && !s_libnl ? "do" : "does");
-			warn = 1;
-		}
-		return;
-	}
-
-	if (!s_libnl && !s_kernel) {
-		nm_log_warn (LOGD_IP6, "libnl and the kernel do not support extended IFA_FLAGS needed by NM for "
-		                       "IPv6 private addresses. This feature is not available");
-	} else if (!s_libnl) {
-		nm_log_warn (LOGD_IP6, "libnl does not support extended IFA_FLAGS needed by NM for "
-		                       "IPv6 private addresses. This feature is not available");
-	} else if (!s_kernel) {
-		nm_log_warn (LOGD_IP6, "The kernel does not support extended IFA_FLAGS needed by NM for "
-		                       "IPv6 private addresses. This feature is not available");
-	}
-
-	warn = 2;
-}
-
 static void nm_device_ipv6_set_mtu (NMDevice *self, guint32 mtu);
 
 static void
@@ -4680,24 +5420,20 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int i;
-	static int system_support = -1;
+	int system_support;
 	guint ifa_flags = 0x00;
 
-	if (system_support == -1) {
-		/*
-		 * Check, if both libnl and the kernel are recent enough,
-		 * to help user space handling RA. If it's not supported,
-		 * we have no ipv6-privacy and must add autoconf addresses
-		 * as /128. The reason for the /128 is to prevent the kernel
-		 * from adding a prefix route for this address.
-		 **/
-		system_support = nm_platform_check_support_libnl_extended_ifa_flags () &&
-		                 nm_platform_check_support_kernel_extended_ifa_flags (NM_PLATFORM_GET);
-	}
+	/*
+	 * Check, whether kernel is recent enough to help user space handling RA.
+	 * If it's not supported, we have no ipv6-privacy and must add autoconf
+	 * addresses as /128. The reason for the /128 is to prevent the kernel
+	 * from adding a prefix route for this address.
+	 **/
+	system_support = nm_platform_check_support_kernel_extended_ifa_flags (NM_PLATFORM_GET);
 
 	if (system_support)
 		ifa_flags = IFA_F_NOPREFIXROUTE;
-	if (priv->rdisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR
+	if (   priv->rdisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR
 	    || priv->rdisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
 	{
 		/* without system_support, this flag will be ignored. Still set it, doesn't seem to do any harm. */
@@ -4707,7 +5443,7 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 	g_return_if_fail (priv->act_request);
 
 	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = nm_ip6_config_new ();
+		priv->ac_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
 
 	if (changed & NM_RDISC_CONFIG_GATEWAYS) {
 		/* Use the first gateway as ordered in router discovery cache. */
@@ -4805,10 +5541,11 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 			       "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)
+				if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) {
 					nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+					return;
+				}
 			}
-			return;
 		}
 	}
 
@@ -4854,10 +5591,15 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	g_assert (priv->rdisc);
 
 	if (nm_platform_link_get_ipv6_token (NM_PLATFORM_GET, priv->ifindex, &iid)) {
-		_LOGD (LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface);
-	} else if (!nm_device_get_ip_iface_identifier (self, &iid)) {
-		_LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 cannot continue");
-		return FALSE;
+		_LOGD (LOGD_IP6, "addrconf6: IPv6 tokenized identifier present");
+		nm_rdisc_set_iid (priv->rdisc, iid);
+	} else if (nm_device_get_ip_iface_identifier (self, &iid)) {
+		_LOGD (LOGD_IP6, "addrconf6: using the device EUI-64 identifier");
+		nm_rdisc_set_iid (priv->rdisc, iid);
+	} else {
+		/* Don't abort the addrconf at this point -- if rdisc needs the iid
+		 * it will notice this itself. */
+		_LOGI (LOGD_IP6, "addrconf6: no interface identifier; IPv6 adddress creation may fail");
 	}
 
 	/* Apply any manual configuration before starting RA */
@@ -4878,7 +5620,6 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	                                           G_CALLBACK (rdisc_ra_timeout),
 	                                           self);
 
-	nm_rdisc_set_iid (priv->rdisc, iid);
 	nm_rdisc_start (priv->rdisc);
 	return TRUE;
 }
@@ -4889,9 +5630,10 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	NMActStageReturn ret;
-	const char *ip_iface = nm_device_get_ip_iface (self);
+	NMSettingIP6Config *s_ip6 = NULL;
+	GError *error = NULL;
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	g_warn_if_fail (priv->ac_ip6_config == NULL);
@@ -4900,14 +5642,27 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 		priv->ac_ip6_config = NULL;
 	}
 
-	priv->rdisc = nm_lndp_rdisc_new (nm_device_get_ip_ifindex (self), ip_iface);
+	s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
+	g_assert (s_ip6);
+
+	priv->rdisc = nm_lndp_rdisc_new (nm_device_get_ip_ifindex (self),
+	                                 nm_device_get_ip_iface (self),
+	                                 nm_connection_get_uuid (connection),
+	                                 nm_setting_ip6_config_get_addr_gen_mode (s_ip6),
+	                                 &error);
 	if (!priv->rdisc) {
-		_LOGE (LOGD_IP6, "failed to start router discovery (%s)", ip_iface);
+		_LOGE (LOGD_IP6, "addrconf6: failed to start router discovery: %s", error->message);
+		g_error_free (error);
 		return FALSE;
 	}
 
 	priv->rdisc_use_tempaddr = use_tempaddr;
-	print_support_extended_ifa_flags (use_tempaddr);
+
+	if (   NM_IN_SET (use_tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
+	    && !nm_platform_check_support_kernel_extended_ifa_flags (NM_PLATFORM_GET)) {
+		_LOGW (LOGD_IP6, "The kernel does not support extended IFA_FLAGS needed by NM for "
+		                 "IPv6 private addresses. This feature is not available");
+	}
 
 	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);
@@ -4929,15 +5684,8 @@ addrconf6_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	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_clear_g_signal_handler (priv->rdisc, &priv->rdisc_changed_id);
+	nm_clear_g_signal_handler (priv->rdisc, &priv->rdisc_timeout_id);
 
 	nm_device_remove_pending_action (self, PENDING_ACTION_AUTOCONF6, FALSE);
 
@@ -5060,7 +5808,7 @@ _ip6_privacy_get (NMDevice *self)
 
 	/* 1.) First look at the per-connection setting. If it is not -1 (unknown),
 	 * use it. */
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (connection) {
 		NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection);
 
@@ -5126,7 +5874,7 @@ act_stage3_ip6_config_start (NMDevice *self,
 
 	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	if (   connection_ip6_method_requires_carrier (connection, NULL)
@@ -5208,7 +5956,7 @@ act_stage3_ip6_config_start (NMDevice *self,
 			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 ();
+		*out_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
 		g_assert (*out_config);
 
 		ret = NM_ACT_STAGE_RETURN_SUCCESS;
@@ -5318,61 +6066,20 @@ 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
+ * activate_stage3_ip_config_start
  *
  * Begin automatic/manual IP configuration
  *
  */
-static gboolean
-nm_device_activate_stage3_ip_config_start (gpointer user_data)
+static void
+activate_stage3_ip_config_start (NMDevice *self)
 {
-	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActiveConnection *master;
 	NMDevice *master_device;
 
-	/* Clear the activation source ID now that this stage has run */
-	activation_source_clear (self, FALSE, 0);
-
 	priv->ip4_state = priv->ip6_state = IP_WAIT;
 
-	_LOGD (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 */
@@ -5393,50 +6100,75 @@ nm_device_activate_stage3_ip_config_start (gpointer user_data)
 		master_device = nm_active_connection_get_device (master);
 		if (priv->ip4_state == IP_WAIT && priv->ip6_state == IP_WAIT) {
 			_LOGI (LOGD_DEVICE, "Activation: connection '%s' waiting on master '%s'",
-			       nm_connection_get_id (nm_device_get_connection (self)),
+			       nm_connection_get_id (nm_device_get_applied_connection (self)),
 			       master_device ? nm_device_get_iface (master_device) : "(unknown)");
 		}
-		goto out;
+		return;
 	}
 
 	/* IPv4 */
 	if (   nm_device_activate_ip4_state_in_wait (self)
 	    && !nm_device_activate_stage3_ip4_start (self))
-		goto out;
+		return;
 
 	/* IPv6 */
 	if (   nm_device_activate_ip6_state_in_wait (self)
 	    && !nm_device_activate_stage3_ip6_start (self))
-		goto out;
+		return;
 
 	nm_device_check_ip_failed (self, TRUE);
-
-out:
-	_LOGD (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) complete.");
-	return FALSE;
 }
 
+static gboolean
+fw_change_zone_handle (NMDevice *self,
+                       NMFirewallManagerCallId call_id,
+                       GError *error)
+{
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	g_return_val_if_fail (priv->fw_call == call_id, FALSE);
+	priv->fw_call = NULL;
+
+	return !nm_utils_error_is_cancelled (error, FALSE);
+}
 
 static void
-fw_change_zone_cb (GError *error, gpointer user_data)
+fw_change_zone_cb_stage2 (NMFirewallManager *firewall_manager,
+                          NMFirewallManagerCallId call_id,
+                          GError *error,
+                          gpointer user_data)
 {
-	NMDevice *self;
+	NMDevice *self = user_data;
 	NMDevicePrivate *priv;
 
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (!fw_change_zone_handle (self, call_id, error))
 		return;
 
-	self = NM_DEVICE (user_data);
+	/* FIXME: fail the device on error? */
+
 	priv = NM_DEVICE_GET_PRIVATE (self);
+	priv->fw_ready = TRUE;
 
-	priv->fw_call = NULL;
+	nm_device_activate_schedule_stage3_ip_config_start (self);
+}
 
-	if (error) {
-		/* FIXME: fail the device activation? */
-	}
+static void
+fw_change_zone_cb_ip_check (NMFirewallManager *firewall_manager,
+                            NMFirewallManagerCallId call_id,
+                            GError *error,
+                            gpointer user_data)
+{
+	NMDevice *self = user_data;
 
-	activation_source_schedule (self, nm_device_activate_stage3_ip_config_start, 0);
-	_LOGD (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) scheduled.");
+	if (!fw_change_zone_handle (self, call_id, error))
+		return;
+
+	/* FIXME: fail the device on error? */
+	nm_device_start_ip_check (self);
 }
 
 /*
@@ -5457,29 +6189,31 @@ 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);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 	s_con = nm_connection_get_setting_connection (connection);
 
-	zone = nm_setting_connection_get_zone (s_con);
-
-	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);
-		_LOGD (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) scheduled.");
-		return;
+	if (!priv->fw_ready) {
+		if (nm_device_uses_assumed_connection (self))
+			priv->fw_ready = TRUE;
+		else {
+			if (!priv->fw_call) {
+				zone = nm_setting_connection_get_zone (s_con);
+
+				_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,
+				                                                        fw_change_zone_cb_stage2,
+				                                                        self);
+			}
+			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,
-	                                                        fw_change_zone_cb,
-	                                                        self);
+	activation_source_schedule (self, activate_stage3_ip_config_start, AF_INET);
 }
 
 static NMActStageReturn
@@ -5499,35 +6233,25 @@ act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
  * Time out on retrieving the IPv4 config.
  *
  */
-static gboolean
-nm_device_activate_ip4_config_timeout (gpointer user_data)
+static void
+activate_stage4_ip4_config_timeout (NMDevice *self)
 {
-	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	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);
-
-	_LOGD (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)
-		goto out;
+		return;
 	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-		goto out;
+		return;
 	}
-	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);	
+	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
 	priv->ip4_state = IP_FAIL;
 
 	nm_device_check_ip_failed (self, FALSE);
-
-out:
-	_LOGD (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 4 of 5 (IPv4 Configure Timeout) complete.");
-	return FALSE;
 }
 
 
@@ -5547,9 +6271,7 @@ nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	g_return_if_fail (priv->act_request);
 
-	activation_source_schedule (self, nm_device_activate_ip4_config_timeout, AF_INET);
-
-	_LOGD (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 4 of 5 (IPv4 Configure Timeout) scheduled...");
+	activation_source_schedule (self, activate_stage4_ip4_config_timeout, AF_INET);
 }
 
 
@@ -5566,40 +6288,30 @@ act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
 
 
 /*
- * nm_device_activate_ip6_config_timeout
+ * activate_stage4_ip6_config_timeout
  *
  * Time out on retrieving the IPv6 config.
  *
  */
-static gboolean
-nm_device_activate_ip6_config_timeout (gpointer user_data)
+static void
+activate_stage4_ip6_config_timeout (NMDevice *self)
 {
-	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	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);
-
-	_LOGD (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)
-		goto out;
-	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		return;
+	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-		goto out;
+		return;
 	}
 	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
 	priv->ip6_state = IP_FAIL;
 
 	nm_device_check_ip_failed (self, FALSE);
-
-out:
-	_LOGD (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 4 of 5 (IPv6 Configure Timeout) complete.");
-	return FALSE;
 }
 
 
@@ -5619,9 +6331,7 @@ nm_device_activate_schedule_ip6_config_timeout (NMDevice *self)
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	g_return_if_fail (priv->act_request);
 
-	activation_source_schedule (self, nm_device_activate_ip6_config_timeout, AF_INET6);
-
-	_LOGD (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 4 of 5 (IPv6 Configure Timeout) scheduled...");
+	activation_source_schedule (self, activate_stage4_ip6_config_timeout, AF_INET6);
 }
 
 static gboolean
@@ -5721,7 +6431,7 @@ start_sharing (NMDevice *self, NMIP4Config *config)
 		return FALSE;
 	}
 
-	priv->dnsmasq_state_id = g_signal_connect (priv->dnsmasq_manager, "state-changed",
+	priv->dnsmasq_state_id = g_signal_connect (priv->dnsmasq_manager, NM_DNS_MASQ_MANAGER_STATE_CHANGED,
 	                                           G_CALLBACK (dnsmasq_state_changed_cb),
 	                                           self);
 	return TRUE;
@@ -5738,7 +6448,7 @@ send_arps (NMDevice *self, const char *mode_arg)
 	NMIPAddress *addr;
 	GError *error = NULL;
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (!connection)
 		return;
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
@@ -5776,10 +6486,14 @@ send_arps (NMDevice *self, const char *mode_arg)
 }
 
 static gboolean
-arp_announce_round2 (gpointer self)
+arp_announce_round2 (gpointer user_data)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevice *self = user_data;
+	NMDevicePrivate *priv;
 
+	g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 	priv->arp_round2_id = 0;
 
 	if (   priv->state >= NM_DEVICE_STATE_IP_CONFIG
@@ -5794,10 +6508,7 @@ arp_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->arp_round2_id) {
-		g_source_remove (priv->arp_round2_id);
-		priv->arp_round2_id = 0;
-	}
+	nm_clear_g_source (&priv->arp_round2_id);
 }
 
 static void
@@ -5813,7 +6524,7 @@ arp_announce (NMDevice *self)
 	/* We only care about manually-configured addresses; DHCP- and autoip-configured
 	 * ones should already have been seen on the network at this point.
 	 */
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (!connection)
 		return;
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
@@ -5827,10 +6538,9 @@ arp_announce (NMDevice *self)
 	priv->arp_round2_id = g_timeout_add_seconds (2, arp_announce_round2, self);
 }
 
-static gboolean
-nm_device_activate_ip4_config_commit (gpointer user_data)
+static void
+activate_stage5_ip4_config_commit (NMDevice *self)
 {
-	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
 	const char *method;
@@ -5838,14 +6548,9 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 	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);
-
-	_LOGD (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);
+	connection = nm_act_request_get_applied_connection (req);
 	g_assert (connection);
 
 	/* Interface must be IFF_UP before IP config can be applied */
@@ -5860,7 +6565,7 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 	if (!ip4_config_merge_and_apply (self, NULL, TRUE, &reason)) {
 		_LOGD (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Commit) failed");
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-		goto out;
+		return;
 	}
 
 	/* Start IPv4 sharing if we need it */
@@ -5870,7 +6575,7 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 		if (!start_sharing (self, priv->ip4_config)) {
 			_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;
+			return;
 		}
 	}
 
@@ -5882,7 +6587,8 @@ nm_device_activate_ip4_config_commit (gpointer user_data)
 	    && (nm_device_get_state (self) > NM_DEVICE_STATE_IP_CONFIG)) {
 		/* Notify dispatcher scripts of new DHCP4 config */
 		nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE,
-		                    nm_device_get_connection (self),
+		                    nm_device_get_settings_connection (self),
+		                    nm_device_get_applied_connection (self),
 		                    self,
 		                    NULL,
 		                    NULL,
@@ -5898,15 +6604,10 @@ nm_device_activate_ip4_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);
-
-out:
-	_LOGD (LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv4 Commit) complete.");
-
-	return FALSE;
 }
 
 static void
-nm_device_queued_ip_config_change_clear (NMDevice *self)
+queued_ip4_config_change_clear (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
@@ -5915,6 +6616,13 @@ nm_device_queued_ip_config_change_clear (NMDevice *self)
 		g_source_remove (priv->queued_ip4_config_id);
 		priv->queued_ip4_config_id = 0;
 	}
+}
+
+static void
+queued_ip6_config_change_clear (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
 	if (priv->queued_ip6_config_id) {
 		_LOGD (LOGD_DEVICE, "clearing queued IP6 config change");
 		g_source_remove (priv->queued_ip6_config_id);
@@ -5934,10 +6642,8 @@ nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *conf
 	if (config)
 		priv->dev_ip4_config = g_object_ref (config);
 
-	nm_device_queued_ip_config_change_clear (self);
-	activation_source_schedule (self, nm_device_activate_ip4_config_commit, AF_INET);
-
-	_LOGD (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Configure Commit) scheduled...");
+	queued_ip4_config_change_clear (self);
+	activation_source_schedule (self, activate_stage5_ip4_config_commit, AF_INET);
 }
 
 gboolean
@@ -5954,24 +6660,18 @@ nm_device_activate_ip4_state_in_wait (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT;
 }
 
-static gboolean
-nm_device_activate_ip6_config_commit (gpointer user_data)
+static void
+activate_stage5_ip6_config_commit (NMDevice *self)
 {
-	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
 	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);
-
-	_LOGD (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);
+	connection = nm_act_request_get_applied_connection (req);
 	g_assert (connection);
 
 	/* Interface must be IFF_UP before IP config can be applied */
@@ -5983,19 +6683,23 @@ nm_device_activate_ip6_config_commit (gpointer user_data)
 	}
 
 	if (ip6_config_merge_and_apply (self, TRUE, &reason)) {
-		/* If IPv6 wasn't the first IP to complete, and DHCP was used,
-		 * then ensure dispatcher scripts get the DHCP lease information.
-		 */
-		if (   priv->dhcp6_client
-		    && nm_device_activate_ip6_state_in_conf (self)
-		    && (nm_device_get_state (self) > NM_DEVICE_STATE_IP_CONFIG)) {
-			/* Notify dispatcher scripts of new DHCP6 config */
-			nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE,
-			                    nm_device_get_connection (self),
-			                    self,
-			                    NULL,
-			                    NULL,
-			                    NULL);
+		if (   priv->dhcp6_mode != NM_RDISC_DHCP_LEVEL_NONE
+		    && priv->ip6_state == IP_CONF) {
+			if (priv->dhcp6_ip6_config) {
+				/* If IPv6 wasn't the first IP to complete, and DHCP was used,
+				 * then ensure dispatcher scripts get the DHCP lease information.
+				 */
+				nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE,
+						    nm_device_get_settings_connection (self),
+						    nm_device_get_applied_connection (self),
+						    self,
+						    NULL,
+						    NULL,
+						    NULL);
+			} else {
+				/* still waiting for first dhcp6 lease. */
+				return;
+			}
 		}
 
 		/* Enter the IP_CHECK state if this is the first method to complete */
@@ -6010,10 +6714,6 @@ nm_device_activate_ip6_config_commit (gpointer user_data)
 		_LOGW (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 5 of 5 (IPv6 Commit) failed");
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 	}
-
-	_LOGD (LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv6 Commit) complete.");
-
-	return FALSE;
 }
 
 void
@@ -6029,9 +6729,7 @@ nm_device_activate_schedule_ip6_config_result (NMDevice *self)
 	if (priv->ip6_state == IP_FAIL)
 		priv->ip6_state = IP_CONF;
 
-	activation_source_schedule (self, nm_device_activate_ip6_config_commit, AF_INET6);
-
-	_LOGD (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 5 of 5 (IPv6 Commit) scheduled...");
+	activation_source_schedule (self, activate_stage5_ip6_config_commit, AF_INET6);
 }
 
 gboolean
@@ -6073,10 +6771,7 @@ dnsmasq_cleanup (NMDevice *self)
 	if (!priv->dnsmasq_manager)
 		return;
 
-	if (priv->dnsmasq_state_id) {
-		g_signal_handler_disconnect (priv->dnsmasq_manager, priv->dnsmasq_state_id);
-		priv->dnsmasq_state_id = 0;
-	}
+	nm_clear_g_signal_handler (priv->dnsmasq_manager, &priv->dnsmasq_state_id);
 
 	nm_dnsmasq_manager_stop (priv->dnsmasq_manager);
 	g_object_unref (priv->dnsmasq_manager);
@@ -6108,14 +6803,6 @@ nm_device_get_is_nm_owned (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->is_nm_owned;
 }
 
-void
-nm_device_set_nm_owned (NMDevice *self)
-{
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	NM_DEVICE_GET_PRIVATE (self)->is_nm_owned = TRUE;
-}
-
 /*
  * delete_on_deactivate_link_delete
  *
@@ -6129,16 +6816,21 @@ delete_on_deactivate_link_delete (gpointer user_data)
 	DeleteOnDeactivateData *data = user_data;
 	NMDevice *self = data->device;
 
+	_LOGD (LOGD_DEVICE, "delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)",
+	       data->ifindex, data->idle_add_id);
+
 	if (data->device) {
 		NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (data->device);
+		gs_free_error GError *error = NULL;
 
 		g_object_remove_weak_pointer (G_OBJECT (data->device), (void **) &data->device);
 		priv->delete_on_deactivate_data = NULL;
-	}
 
-	_LOGD (LOGD_DEVICE, "delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)",
-	       data->ifindex, data->idle_add_id);
-	nm_platform_link_delete (NM_PLATFORM_GET, data->ifindex);
+		if (!nm_device_unrealize (data->device, TRUE, &error))
+			_LOGD (LOGD_DEVICE, "delete_on_deactivate: unrealizing %d failed (%s)", data->ifindex, error->message);
+	} else
+		nm_platform_link_delete (NM_PLATFORM_GET, data->ifindex);
+
 	g_free (data);
 	return FALSE;
 }
@@ -6173,7 +6865,7 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
 		return;
 	if (priv->queued_act_request)
 		return;
-	if (!nm_device_is_software (self))
+	if (!nm_device_is_software (self) || !nm_device_is_real (self))
 		return;
 	if (nm_device_get_state (self) == NM_DEVICE_STATE_UNMANAGED)
 		return;
@@ -6193,8 +6885,330 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
 }
 
 static void
+_cleanup_ip4_pre (NMDevice *self, CleanupType cleanup_type)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	priv->ip4_state =  IP_NONE;
+	queued_ip4_config_change_clear (self);
+
+	dhcp4_cleanup (self, cleanup_type, FALSE);
+	arp_cleanup (self);
+	dnsmasq_cleanup (self);
+	ipv4ll_cleanup (self);
+}
+
+static void
+_cleanup_ip6_pre (NMDevice *self, CleanupType cleanup_type)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	priv->ip6_state = IP_NONE;
+	queued_ip6_config_change_clear (self);
+
+	dhcp6_cleanup (self, cleanup_type, FALSE);
+	linklocal6_cleanup (self);
+	addrconf6_cleanup (self);
+}
+
+G_GNUC_NULL_TERMINATED
+static gboolean
+_hash_check_invalid_keys (GHashTable *hash, const char *setting_name, GError **error, ...)
+{
+	va_list ap;
+	const char *key;
+	guint found_keys = 0;
+
+#if NM_MORE_ASSERTS > 10
+	/* Assert that the keys are unique. */
+	{
+		gs_unref_hashtable GHashTable *check_dups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+
+		va_start (ap, error);
+		while ((key = va_arg (ap, const char *))) {
+			if (!g_hash_table_add (check_dups, (char *) key))
+				nm_assert (FALSE);
+		}
+		va_end (ap);
+		nm_assert (g_hash_table_size (check_dups) > 0);
+	}
+#endif
+
+	if (!hash || g_hash_table_size (hash) == 0)
+		return TRUE;
+
+	va_start (ap, error);
+	while ((key = va_arg (ap, const char *))) {
+		if (g_hash_table_contains (hash, key))
+			found_keys++;
+	}
+	va_end (ap);
+
+	if (found_keys != g_hash_table_size (hash)) {
+		GHashTableIter iter;
+		const char *k = NULL;
+		const char *first_invalid_key = NULL;
+
+		if (!error)
+			return FALSE;
+
+		g_hash_table_iter_init (&iter, hash);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &k, NULL)) {
+			va_start (ap, error);
+			while ((key = va_arg (ap, const char *))) {
+				if (!strcmp (key, k)) {
+					first_invalid_key = k;
+					break;
+				}
+			}
+			va_end (ap);
+			if (first_invalid_key)
+				break;
+		}
+		g_set_error (error,
+		             NM_DEVICE_ERROR,
+		             NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+		             "Can't reapply changes to '%s%s%s' setting",
+		             setting_name ? : "",
+		             setting_name ? "." : "",
+		             first_invalid_key ? : "<UNKNOWN>");
+		g_return_val_if_fail (first_invalid_key, FALSE);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+void
+nm_device_reactivate_ip4_config (NMDevice *self,
+                                 NMSettingIPConfig *s_ip4_old,
+                                 NMSettingIPConfig *s_ip4_new)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->ip4_state != IP_NONE) {
+		g_clear_object (&priv->con_ip4_config);
+		priv->con_ip4_config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+		nm_ip4_config_merge_setting (priv->con_ip4_config,
+		                             s_ip4_new,
+		                             nm_device_get_ip4_route_metric (self));
+
+		if (strcmp (nm_setting_ip_config_get_method (s_ip4_new),
+		            nm_setting_ip_config_get_method (s_ip4_old))) {
+			_cleanup_ip4_pre (self, CLEANUP_TYPE_DECONFIGURE);
+			priv->ip4_state = IP_WAIT;
+			if (!nm_device_activate_stage3_ip4_start (self))
+				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
+		} else
+			ip4_config_merge_and_apply (self, NULL, TRUE, NULL);
+	}
+}
+
+void
+nm_device_reactivate_ip6_config (NMDevice *self,
+                                 NMSettingIPConfig *s_ip6_old,
+                                 NMSettingIPConfig *s_ip6_new)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->ip6_state != IP_NONE) {
+		g_clear_object (&priv->con_ip6_config);
+		priv->con_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+		nm_ip6_config_merge_setting (priv->con_ip6_config,
+		                             s_ip6_new,
+		                             nm_device_get_ip6_route_metric (self));
+
+		if (strcmp (nm_setting_ip_config_get_method (s_ip6_new),
+		            nm_setting_ip_config_get_method (s_ip6_old))) {
+			_cleanup_ip6_pre (self, CLEANUP_TYPE_DECONFIGURE);
+			priv->ip6_state = IP_WAIT;
+			if (!nm_device_activate_stage3_ip6_start (self))
+				_LOGW (LOGD_IP6, "Failed to apply IPv6 configuration");
+		} else
+			ip6_config_merge_and_apply (self, TRUE, NULL);
+	}
+}
+
+
+/* reapply_connection:
+ * @connection: the new connection settings to be applied or %NULL to reapply
+ *   the current settings connection
+ * @error: the error if %FALSE is returned
+ *
+ * Change configuration of an already configured device if possible.
+ * Updates the device's applied connection upon success.
+ *
+ * Return: %FALSE if the new configuration can not be reapplied.
+ */
+static gboolean
+reapply_connection (NMDevice *self,
+                    NMConnection *connection,
+                    GError **error)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMConnection *applied = nm_device_get_applied_connection (self);
+	gs_unref_object NMConnection *applied_clone = NULL;
+	gs_unref_hashtable GHashTable *diffs = NULL;
+	NMConnection *con_old, *con_new;
+	NMSettingIPConfig *s_ip4_old, *s_ip4_new;
+	NMSettingIPConfig *s_ip6_old, *s_ip6_new;
+
+	if (priv->state != NM_DEVICE_STATE_ACTIVATED) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_NOT_ACTIVE,
+		                     "Device is not activated");
+		return FALSE;
+	}
+
+	nm_connection_diff (connection,
+	                    applied,
+	                    NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP |
+	                    NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS,
+	                    &diffs);
+
+	/**************************************************************************
+	 * check for unsupported changes and reject to reapply
+	 *************************************************************************/
+	if (!_hash_check_invalid_keys (diffs, NULL, error,
+	                               NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	                               NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	                               NM_SETTING_CONNECTION_SETTING_NAME,
+	                               NULL))
+		return FALSE;
+
+	if (!_hash_check_invalid_keys (diffs ? g_hash_table_lookup (diffs, NM_SETTING_CONNECTION_SETTING_NAME) : NULL,
+	                               NM_SETTING_CONNECTION_SETTING_NAME,
+	                               error,
+	                               NM_SETTING_CONNECTION_ZONE,
+	                               NM_SETTING_CONNECTION_METERED,
+	                               NULL))
+		return FALSE;
+
+	_LOGD (LOGD_DEVICE, "reapply");
+
+	/**************************************************************************
+	 * Update applied connection
+	 *************************************************************************/
+
+	if (diffs) {
+		con_old = applied_clone  = nm_simple_connection_new_clone (applied);
+		con_new = applied;
+		nm_connection_replace_settings_from_connection (applied, connection);
+	} else
+		con_old = con_new = applied;
+
+	s_ip4_new = nm_connection_get_setting_ip4_config (con_new);
+	s_ip4_old = nm_connection_get_setting_ip4_config (con_old);
+	s_ip6_new = nm_connection_get_setting_ip6_config (con_new);
+	s_ip6_old = nm_connection_get_setting_ip6_config (con_old);
+
+	/**************************************************************************
+	 * Reapply changes
+	 *************************************************************************/
+
+	nm_device_update_firewall_zone (self);
+	nm_device_update_metered (self);
+
+	nm_device_reactivate_ip4_config (self, s_ip4_old, s_ip4_new);
+	nm_device_reactivate_ip6_config (self, s_ip6_old, s_ip6_new);
+
+	return TRUE;
+}
+
+static void
+reapply_cb (NMDevice *self,
+            GDBusMethodInvocation *context,
+            NMAuthSubject *subject,
+            GError *error,
+            gpointer user_data)
+{
+	gs_unref_object NMConnection *connection = NM_CONNECTION (user_data);
+	GError *local = NULL;
+
+	if (error) {
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, subject, error->message);
+		g_dbus_method_invocation_return_gerror (context, error);
+		return;
+	}
+
+	if (!reapply_connection (self,
+	                         connection ? : (NMConnection *) nm_device_get_settings_connection (self),
+	                         &local)) {
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, subject, local->message);
+		g_dbus_method_invocation_take_error (context, local);
+		local = NULL;
+	} else {
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, TRUE, subject, NULL);
+		g_dbus_method_invocation_return_value (context, NULL);
+	}
+}
+
+static void
+impl_device_reapply (NMDevice *self,
+                     GDBusMethodInvocation *context,
+                     GVariant *settings,
+                     guint flags)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMSettingsConnection *settings_connection;
+	NMConnection *connection = NULL;
+	GError *error = NULL;
+
+	/* No flags supported as of now. */
+	if (flags != 0) {
+		error = g_error_new_literal (NM_DEVICE_ERROR,
+		                             NM_DEVICE_ERROR_NOT_ACTIVE,
+		                             "Invalid flags specified");
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, context, error->message);
+		g_dbus_method_invocation_take_error (context, error);
+		return;
+	}
+
+	if (priv->state != NM_DEVICE_STATE_ACTIVATED) {
+		error = g_error_new_literal (NM_DEVICE_ERROR,
+		                             NM_DEVICE_ERROR_NOT_ACTIVE,
+		                             "Device is not activated");
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, context, error->message);
+		g_dbus_method_invocation_take_error (context, error);
+		return;
+	}
+
+	settings_connection = nm_device_get_settings_connection (self);
+	g_return_if_fail (settings_connection);
+
+	if (settings && g_variant_n_children (settings)) {
+		/* New settings specified inline. */
+		connection = nm_simple_connection_new_from_dbus (settings, &error);
+		if (!connection) {
+			g_prefix_error (&error, "The settings specified are invalid: ");
+			nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, context, error->message);
+			g_dbus_method_invocation_take_error (context, error);
+			return;
+		}
+		nm_connection_clear_secrets (connection);
+	}
+
+	/* Ask the manager to authenticate this request for us */
+	g_signal_emit (self, signals[AUTH_REQUEST], 0,
+	               context,
+	               nm_device_get_applied_connection (self),
+	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	               TRUE,
+	               reapply_cb,
+	               connection);
+}
+
+static void
 disconnect_cb (NMDevice *self,
-               DBusGMethodInvocation *context,
+               GDBusMethodInvocation *context,
+               NMAuthSubject *subject,
                GError *error,
                gpointer user_data)
 {
@@ -6202,7 +7216,8 @@ disconnect_cb (NMDevice *self,
 	GError *local = NULL;
 
 	if (error) {
-		dbus_g_method_return_error (context, error);
+		g_dbus_method_invocation_return_gerror (context, error);
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_DISCONNECT, self, FALSE, subject, error->message);
 		return;
 	}
 
@@ -6211,15 +7226,16 @@ disconnect_cb (NMDevice *self,
 		local = g_error_new_literal (NM_DEVICE_ERROR,
 		                             NM_DEVICE_ERROR_NOT_ACTIVE,
 		                             "Device is not active");
-		dbus_g_method_return_error (context, local);
-		g_error_free (local);
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_DISCONNECT, self, FALSE, subject, local->message);
+		g_dbus_method_invocation_take_error (context, local);
 	} else {
 		nm_device_set_autoconnect (self, FALSE);
 
 		nm_device_state_changed (self,
 		                         NM_DEVICE_STATE_DEACTIVATING,
 		                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
-		dbus_g_method_return (context);
+		g_dbus_method_invocation_return_value (context, NULL);
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_DISCONNECT, self, TRUE, subject, NULL);
 	}
 }
 
@@ -6233,7 +7249,7 @@ _clear_queued_act_request (NMDevicePrivate *priv)
 }
 
 static void
-impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context)
+impl_device_disconnect (NMDevice *self, GDBusMethodInvocation *context)
 {
 	NMConnection *connection;
 	GError *error = NULL;
@@ -6242,12 +7258,11 @@ impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context)
 		error = g_error_new_literal (NM_DEVICE_ERROR,
 		                             NM_DEVICE_ERROR_NOT_ACTIVE,
 		                             "This device is not active");
-		dbus_g_method_return_error (context, error);
-		g_error_free (error);
+		g_dbus_method_invocation_take_error (context, error);
 		return;
 	}
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	/* Ask the manager to authenticate this request for us */
@@ -6262,31 +7277,37 @@ impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context)
 
 static void
 delete_cb (NMDevice *self,
-           DBusGMethodInvocation *context,
+           GDBusMethodInvocation *context,
+           NMAuthSubject *subject,
            GError *error,
            gpointer user_data)
 {
+	GError *local = NULL;
+
 	if (error) {
-		dbus_g_method_return_error (context, error);
+		g_dbus_method_invocation_return_gerror (context, error);
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_DELETE, self, FALSE, subject, error->message);
 		return;
 	}
 
 	/* Authorized */
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_device_get_ifindex (self));
-	dbus_g_method_return (context);
+	nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_DELETE, self, TRUE, subject, NULL);
+	if (nm_device_unrealize (self, TRUE, &local))
+		g_dbus_method_invocation_return_value (context, NULL);
+	else
+		g_dbus_method_invocation_take_error (context, local);
 }
 
 static void
-impl_device_delete (NMDevice *self, DBusGMethodInvocation *context)
+impl_device_delete (NMDevice *self, GDBusMethodInvocation *context)
 {
 	GError *error = NULL;
 
-	if (!nm_device_is_software (self)) {
+	if (!nm_device_is_software (self) || !nm_device_is_real (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);
+		                             "This device is not a software device or is not realized");
+		g_dbus_method_invocation_take_error (context, error);
 		return;
 	}
 
@@ -6317,7 +7338,7 @@ _device_activate (NMDevice *self, NMActRequest *req)
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	connection = nm_act_request_get_connection (req);
+	connection = nm_act_request_get_applied_connection (req);
 	g_assert (connection);
 
 	_LOGI (LOGD_DEVICE, "Activation: starting connection '%s' (%s)",
@@ -6378,7 +7399,7 @@ _carrier_wait_check_act_request_must_queue (NMDevice *self, NMActRequest *req)
 	if (priv->carrier_wait_id == 0)
 		return FALSE;
 
-	connection = nm_act_request_get_connection (req);
+	connection = nm_act_request_get_applied_connection (req);
 	if (!connection_requires_carrier (connection))
 		return FALSE;
 
@@ -6408,19 +7429,19 @@ _carrier_wait_check_act_request_must_queue (NMDevice *self, NMActRequest *req)
 }
 
 void
-nm_device_steal_connection (NMDevice *self, NMConnection *connection)
+nm_device_steal_connection (NMDevice *self, NMSettingsConnection *connection)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	_LOGW (LOGD_DEVICE, "disconnecting connection '%s' for new activation request.",
-	       nm_connection_get_id (connection));
+	       nm_settings_connection_get_id (connection));
 
 	if (   priv->queued_act_request
-	    && connection == nm_active_connection_get_connection (NM_ACTIVE_CONNECTION (priv->queued_act_request)))
+	    && connection == nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (priv->queued_act_request)))
 		_clear_queued_act_request (priv);
 
 	if (   priv->act_request
-	    && connection == nm_active_connection_get_connection (NM_ACTIVE_CONNECTION (priv->act_request))
+	    && connection == nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (priv->act_request))
 	    && priv->state < NM_DEVICE_STATE_DEACTIVATING)
 		nm_device_state_changed (self,
 		                         NM_DEVICE_STATE_DEACTIVATING,
@@ -6481,7 +7502,7 @@ nm_device_is_activating (NMDevice *self)
 	 * handler is actually run.  If there's an activation handler scheduled
 	 * we're activating anyway.
 	 */
-	return priv->act_source_id ? TRUE : FALSE;
+	return priv->act_handle4.id ? TRUE : FALSE;
 }
 
 /* IP Configuration stuff */
@@ -6516,13 +7537,19 @@ nm_device_set_ip4_config (NMDevice *self,
 	gboolean has_changes = FALSE;
 	gboolean success = TRUE;
 	NMDeviceStateReason reason_local = NM_DEVICE_STATE_REASON_NONE;
-	int ip_ifindex;
+	int ip_ifindex, config_ifindex;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	ip_ifindex = nm_device_get_ip_ifindex (self);
 
+	if (new_config) {
+		config_ifindex = nm_ip4_config_get_ifindex (new_config);
+		if (config_ifindex > 0)
+			g_return_val_if_fail (ip_ifindex == config_ifindex, FALSE);
+	}
+
 	old_config = priv->ip4_config;
 
 	/* Always commit to nm-platform to update lifetimes */
@@ -6548,25 +7575,23 @@ nm_device_set_ip4_config (NMDevice *self,
 			nm_ip4_config_replace (old_config, new_config, &has_changes);
 			if (has_changes) {
 				_LOGD (LOGD_IP4, "update IP4Config instance (%s)",
-				       nm_ip4_config_get_dbus_path (old_config));
+				       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
 			}
 		} else {
 			has_changes = TRUE;
 			priv->ip4_config = g_object_ref (new_config);
 
-			if (success && !nm_ip4_config_get_dbus_path (new_config)) {
-				/* Export over D-Bus */
-				nm_ip4_config_export (new_config);
-			}
+			if (success && !nm_exported_object_is_exported (NM_EXPORTED_OBJECT (new_config)))
+				nm_exported_object_export (NM_EXPORTED_OBJECT (new_config));
 
 			_LOGD (LOGD_IP4, "set IP4Config instance (%s)",
-			       nm_ip4_config_get_dbus_path (new_config));
+			       nm_exported_object_get_path (NM_EXPORTED_OBJECT (new_config)));
 		}
 	} else if (old_config) {
 		has_changes = TRUE;
 		priv->ip4_config = NULL;
 		_LOGD (LOGD_IP4, "clear IP4Config instance (%s)",
-		       nm_ip4_config_get_dbus_path (old_config));
+		       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
 		/* Device config is invalid if combined config is invalid */
 		g_clear_object (&priv->dev_ip4_config);
 	}
@@ -6580,17 +7605,26 @@ nm_device_set_ip4_config (NMDevice *self,
 			g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_CONFIG);
 		g_signal_emit (self, signals[IP4_CONFIG_CHANGED], 0, priv->ip4_config, old_config);
 
-		if (old_config != priv->ip4_config && old_config)
-			g_object_unref (old_config);
+		if (old_config != priv->ip4_config)
+			nm_exported_object_clear_and_unexport (&old_config);
 
 		if (nm_device_uses_generated_assumed_connection (self)) {
-			NMConnection *connection = nm_device_get_connection (self);
+			NMConnection *connection = nm_device_get_applied_connection (self);
+			NMConnection *settings_connection = NM_CONNECTION (nm_device_get_settings_connection (self));
 			NMSetting *s_ip4;
 
 			g_object_freeze_notify (G_OBJECT (connection));
+			g_object_freeze_notify (G_OBJECT (settings_connection));
+
+			nm_connection_remove_setting (settings_connection, NM_TYPE_SETTING_IP4_CONFIG);
+			s_ip4 = nm_ip4_config_create_setting (priv->ip4_config);
+			nm_connection_add_setting (settings_connection, s_ip4);
+
 			nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
 			s_ip4 = nm_ip4_config_create_setting (priv->ip4_config);
 			nm_connection_add_setting (connection, s_ip4);
+
+			g_object_thaw_notify (G_OBJECT (settings_connection));
 			g_object_thaw_notify (G_OBJECT (connection));
 		}
 
@@ -6603,18 +7637,49 @@ nm_device_set_ip4_config (NMDevice *self,
 	return success;
 }
 
+static gboolean
+_replace_vpn_config_in_list (GSList **plist, GObject *old, GObject *new)
+{
+	GSList *old_link;
+
+	/* Below, assert that we have an @old instance to replace and that
+	 * @new is not yet tracked. But still, behave correctly in any
+	 * case. */
+
+	if (   old
+	    && (old_link = g_slist_find (*plist, old))) {
+		if (old != new) {
+			if (new)
+				old_link->data = g_object_ref (new);
+			else
+				*plist = g_slist_remove_link (*plist, old_link);
+			g_object_unref (old);
+		}
+		return TRUE;
+	}
+
+	if (new) {
+		if (!g_slist_find (*plist, new))
+			*plist = g_slist_append (*plist, g_object_ref (new));
+		else
+			g_return_val_if_reached (TRUE);
+		g_return_val_if_fail (!old, TRUE);
+		return TRUE;
+	}
+
+	/* return FALSE if both @old and @new are unset. */
+	g_return_val_if_fail (!old, FALSE);
+	return FALSE;
+}
+
 void
-nm_device_set_vpn4_config (NMDevice *self, NMIP4Config *config)
+nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->vpn4_config == config)
+	if (!_replace_vpn_config_in_list (&priv->vpn4_configs, (GObject *) old, (GObject *) config))
 		return;
 
-	g_clear_object (&priv->vpn4_config);
-	if (config)
-		priv->vpn4_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 VPN routes for device");
@@ -6649,13 +7714,19 @@ nm_device_set_ip6_config (NMDevice *self,
 	gboolean has_changes = FALSE;
 	gboolean success = TRUE;
 	NMDeviceStateReason reason_local = NM_DEVICE_STATE_REASON_NONE;
-	int ip_ifindex;
+	int ip_ifindex, config_ifindex;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	ip_ifindex = nm_device_get_ip_ifindex (self);
 
+	if (new_config) {
+		config_ifindex = nm_ip6_config_get_ifindex (new_config);
+		if (config_ifindex > 0)
+			g_return_val_if_fail (ip_ifindex == config_ifindex, FALSE);
+	}
+
 	old_config = priv->ip6_config;
 
 	/* Always commit to nm-platform to update lifetimes */
@@ -6675,25 +7746,23 @@ nm_device_set_ip6_config (NMDevice *self,
 			nm_ip6_config_replace (old_config, new_config, &has_changes);
 			if (has_changes) {
 				_LOGD (LOGD_IP6, "update IP6Config instance (%s)",
-				       nm_ip6_config_get_dbus_path (old_config));
+				       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
 			}
 		} else {
 			has_changes = TRUE;
 			priv->ip6_config = g_object_ref (new_config);
 
-			if (success && !nm_ip6_config_get_dbus_path (new_config)) {
-				/* Export over D-Bus */
-				nm_ip6_config_export (new_config);
-			}
+			if (success && !nm_exported_object_is_exported (NM_EXPORTED_OBJECT (new_config)))
+				nm_exported_object_export (NM_EXPORTED_OBJECT (new_config));
 
 			_LOGD (LOGD_IP6, "set IP6Config instance (%s)",
-			       nm_ip6_config_get_dbus_path (new_config));
+			       nm_exported_object_get_path (NM_EXPORTED_OBJECT (new_config)));
 		}
 	} else if (old_config) {
 		has_changes = TRUE;
 		priv->ip6_config = NULL;
 		_LOGD (LOGD_IP6, "clear IP6Config instance (%s)",
-		       nm_ip6_config_get_dbus_path (old_config));
+		       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
 	}
 
 	nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), self);
@@ -6703,17 +7772,26 @@ nm_device_set_ip6_config (NMDevice *self,
 			g_object_notify (G_OBJECT (self), NM_DEVICE_IP6_CONFIG);
 		g_signal_emit (self, signals[IP6_CONFIG_CHANGED], 0, priv->ip6_config, old_config);
 
-		if (old_config != priv->ip6_config && old_config)
-			g_object_unref (old_config);
+		if (old_config != priv->ip6_config)
+			nm_exported_object_clear_and_unexport (&old_config);
 
 		if (nm_device_uses_generated_assumed_connection (self)) {
-			NMConnection *connection = nm_device_get_connection (self);
+			NMConnection *connection = nm_device_get_applied_connection (self);
+			NMConnection *settings_connection = NM_CONNECTION (nm_device_get_settings_connection (self));
 			NMSetting *s_ip6;
 
 			g_object_freeze_notify (G_OBJECT (connection));
+			g_object_freeze_notify (G_OBJECT (settings_connection));
+
+			nm_connection_remove_setting (settings_connection, NM_TYPE_SETTING_IP6_CONFIG);
+			s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
+			nm_connection_add_setting (settings_connection, s_ip6);
+
 			nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
 			s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
 			nm_connection_add_setting (connection, s_ip6);
+
+			g_object_thaw_notify (G_OBJECT (settings_connection));
 			g_object_thaw_notify (G_OBJECT (connection));
 		}
 
@@ -6727,17 +7805,13 @@ nm_device_set_ip6_config (NMDevice *self,
 }
 
 void
-nm_device_set_vpn6_config (NMDevice *self, NMIP6Config *config)
+nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->vpn6_config == config)
+	if (!_replace_vpn_config_in_list (&priv->vpn6_configs, (GObject *) old, (GObject *) config))
 		return;
 
-	g_clear_object (&priv->vpn6_config);
-	if (config)
-		priv->vpn6_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 VPN routes for device");
@@ -6821,7 +7895,8 @@ ip_check_pre_up (NMDevice *self)
 	priv->dispatcher.post_state = NM_DEVICE_STATE_SECONDARIES;
 	priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
 	if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_UP,
-	                         nm_device_get_connection (self),
+	                         nm_device_get_settings_connection (self),
+	                         nm_device_get_applied_connection (self),
 	                         self,
 	                         dispatcher_complete_proceed_state,
 	                         self,
@@ -6904,7 +7979,7 @@ 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);
-	guint log_domain = priv->gw_ping.log_domain;
+	NMLogDomain log_domain = priv->gw_ping.log_domain;
 	gboolean success = FALSE;
 
 	if (!priv->gw_ping.watch)
@@ -6951,7 +8026,7 @@ ip_check_ping_timeout_cb (gpointer user_data)
 
 static gboolean
 start_ping (NMDevice *self,
-            guint log_domain,
+            NMLogDomain log_domain,
             const char *binary,
             const char *address,
             guint timeout)
@@ -6985,7 +8060,7 @@ nm_device_start_ip_check (NMDevice *self)
 	guint timeout = 0;
 	const char *ping_binary = NULL;
 	char buf[INET6_ADDRSTRLEN] = { 0 };
-	guint log_domain = LOGD_IP4;
+	NMLogDomain log_domain = LOGD_IP4;
 
 	/* Shouldn't be any active ping here, since IP_CHECK happens after the
 	 * first IP method completes.  Any subsequently completing IP method doesn't
@@ -6996,7 +8071,7 @@ nm_device_start_ip_check (NMDevice *self)
 	g_assert (!priv->gw_ping.pid);
 	g_assert (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	s_con = nm_connection_get_setting_connection (connection);
@@ -7210,12 +8285,33 @@ nm_device_get_firmware_missing (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->firmware_missing;
 }
 
+void
+nm_device_set_nm_plugin_missing (NMDevice *self, gboolean new_missing)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	if (priv->nm_plugin_missing != new_missing) {
+		priv->nm_plugin_missing = new_missing;
+		g_object_notify (G_OBJECT (self), NM_DEVICE_NM_PLUGIN_MISSING);
+	}
+}
+
+gboolean
+nm_device_get_nm_plugin_missing (NMDevice *self)
+{
+	return NM_DEVICE_GET_PRIVATE (self)->nm_plugin_missing;
+}
+
 static NMIP4Config *
 find_ip4_lease_config (NMDevice *self,
                        NMConnection *connection,
                        NMIP4Config *ext_ip4_config)
 {
 	const char *ip_iface = nm_device_get_ip_iface (self);
+	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	GSList *leases, *liter;
 	NMIP4Config *found = NULL;
 
@@ -7224,6 +8320,7 @@ find_ip4_lease_config (NMDevice *self,
 
 	leases = nm_dhcp_manager_get_lease_ip_configs (nm_dhcp_manager_get (),
 	                                               ip_iface,
+	                                               ip_ifindex,
 	                                               nm_connection_get_uuid (connection),
 	                                               FALSE,
 	                                               nm_device_get_ip4_route_metric (self));
@@ -7310,6 +8407,24 @@ capture_lease_config (NMDevice *self,
 }
 
 static void
+_ip4_config_intersect (gpointer value, gpointer user_data)
+{
+	NMIP4Config *dst = (NMIP4Config *) value;
+	NMIP4Config *src = (NMIP4Config *) user_data;
+
+	nm_ip4_config_intersect (dst, src);
+}
+
+static void
+_ip4_config_subtract (gpointer value, gpointer user_data)
+{
+	NMIP4Config *dst = (NMIP4Config *) user_data;
+	NMIP4Config *src = (NMIP4Config *) value;
+
+	nm_ip4_config_subtract (dst, src);
+}
+
+static void
 update_ip4_config (NMDevice *self, gboolean initial)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -7339,15 +8454,16 @@ update_ip4_config (NMDevice *self, gboolean initial)
 		 * kernel routes. */
 
 		/* This function was called upon external changes. Remove the configuration
-		 * (adresses,routes) that is no longer present externally from the interal
-		 * config. This way, we don't readd addresses that were manually removed
+		 * (addresses,routes) that is no longer present externally from the internal
+		 * config. This way, we don't re-add addresses that were manually removed
 		 * by the user. */
 		if (priv->con_ip4_config)
 			nm_ip4_config_intersect (priv->con_ip4_config, priv->ext_ip4_config);
 		if (priv->dev_ip4_config)
 			nm_ip4_config_intersect (priv->dev_ip4_config, priv->ext_ip4_config);
-		if (priv->vpn4_config)
-			nm_ip4_config_intersect (priv->vpn4_config, priv->ext_ip4_config);
+
+		g_slist_foreach (priv->vpn4_configs, _ip4_config_intersect, priv->ext_ip4_config);
+
 		if (priv->wwan_ip4_config)
 			nm_ip4_config_intersect (priv->wwan_ip4_config, priv->ext_ip4_config);
 
@@ -7358,8 +8474,9 @@ update_ip4_config (NMDevice *self, gboolean initial)
 			nm_ip4_config_subtract (priv->ext_ip4_config, priv->con_ip4_config);
 		if (priv->dev_ip4_config)
 			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);
+
+		g_slist_foreach (priv->vpn4_configs, _ip4_config_subtract, priv->ext_ip4_config);
+
 		if (priv->wwan_ip4_config)
 			nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_config);
 
@@ -7368,6 +8485,24 @@ update_ip4_config (NMDevice *self, gboolean initial)
 }
 
 static void
+_ip6_config_intersect (gpointer value, gpointer user_data)
+{
+	NMIP6Config *dst = (NMIP6Config *) value;
+	NMIP6Config *src = (NMIP6Config *) user_data;
+
+	nm_ip6_config_intersect (dst, src);
+}
+
+static void
+_ip6_config_subtract (gpointer value, gpointer user_data)
+{
+	NMIP6Config *dst = (NMIP6Config *) user_data;
+	NMIP6Config *src = (NMIP6Config *) value;
+
+	nm_ip6_config_subtract (dst, src);
+}
+
+static void
 update_ip6_config (NMDevice *self, gboolean initial)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -7393,8 +8528,8 @@ update_ip6_config (NMDevice *self, gboolean initial)
 		                            have_ip6_address (priv->ext_ip6_config, TRUE);
 
 		/* This function was called upon external changes. Remove the configuration
-		 * (adresses,routes) that is no longer present externally from the interal
-		 * config. This way, we don't readd addresses that were manually removed
+		 * (addresses,routes) that is no longer present externally from the internal
+		 * config. This way, we don't re-add addresses that were manually removed
 		 * by the user. */
 		if (priv->con_ip6_config)
 			nm_ip6_config_intersect (priv->con_ip6_config, priv->ext_ip6_config);
@@ -7404,8 +8539,7 @@ update_ip6_config (NMDevice *self, gboolean initial)
 			nm_ip6_config_intersect (priv->dhcp6_ip6_config, priv->ext_ip6_config);
 		if (priv->wwan_ip6_config)
 			nm_ip6_config_intersect (priv->wwan_ip6_config, priv->ext_ip6_config);
-		if (priv->vpn6_config)
-			nm_ip6_config_intersect (priv->vpn6_config, priv->ext_ip6_config);
+		g_slist_foreach (priv->vpn6_configs, _ip6_config_intersect, priv->ext_ip6_config);
 
 		/* Remove parts from ext_ip6_config to only contain the information that
 		 * was configured externally -- we already have the same configuration from
@@ -7418,8 +8552,7 @@ update_ip6_config (NMDevice *self, gboolean initial)
 			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);
+		g_slist_foreach (priv->vpn6_configs, _ip6_config_subtract, priv->ext_ip6_config);
 
 		ip6_config_merge_and_apply (self, FALSE, NULL);
 	}
@@ -7462,6 +8595,8 @@ queued_ip6_config_change (gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	GSList *iter;
+	gboolean need_ipv6ll = FALSE;
 
 	/* Wait for any queued state changes */
 	if (priv->queued_state.id)
@@ -7471,13 +8606,38 @@ queued_ip6_config_change (gpointer user_data)
 	g_object_ref (self);
 	update_ip6_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);
+	if (   nm_platform_link_get (NM_PLATFORM_GET, priv->ifindex)
+	    && priv->state < NM_DEVICE_STATE_DEACTIVATING) {
+		/* Handle DAD falures */
+		for (iter = priv->dad6_failed_addrs; iter; iter = g_slist_next (iter)) {
+			NMPlatformIP6Address *addr = iter->data;
+
+			if (addr->source >= NM_IP_CONFIG_SOURCE_USER)
+				continue;
+
+			_LOGI (LOGD_IP6, "ipv6: duplicate address check failed for the %s address",
+			       nm_platform_ip6_address_to_string (addr, NULL, 0));
+
+			if (IN6_IS_ADDR_LINKLOCAL (&addr->address))
+				need_ipv6ll = TRUE;
+			else if (priv->rdisc)
+				nm_rdisc_dad_failed (priv->rdisc, &addr->address);
+		}
+
+		/* 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))
+			need_ipv6ll = TRUE;
+
+		if (need_ipv6ll)
+			check_and_add_ipv6ll_addr (self);
+	}
+
+	g_slist_free_full (priv->dad6_failed_addrs, g_free);
+	priv->dad6_failed_addrs = NULL;
 
 	g_object_unref (self);
 
@@ -7490,15 +8650,16 @@ device_ipx_changed (NMPlatform *platform,
                     int ifindex,
                     gpointer platform_object,
                     NMPlatformSignalChangeType change_type,
-                    NMPlatformReason reason,
                     NMDevice *self)
 {
 	NMDevicePrivate *priv;
+	NMPlatformIP6Address *addr;
 
 	if (nm_device_get_ip_ifindex (self) != ifindex)
 		return;
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
+
 	switch (obj_type) {
 	case NMP_OBJECT_TYPE_IP4_ADDRESS:
 	case NMP_OBJECT_TYPE_IP4_ROUTE:
@@ -7508,6 +8669,16 @@ device_ipx_changed (NMPlatform *platform,
 		}
 		break;
 	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+		addr = platform_object;
+
+		if (   priv->state > NM_DEVICE_STATE_DISCONNECTED
+		    && priv->state < NM_DEVICE_STATE_DEACTIVATING
+                    && (   (change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->flags & IFA_F_DADFAILED)
+		        || (change_type == NM_PLATFORM_SIGNAL_REMOVED && addr->flags & IFA_F_TENTATIVE))) {
+			priv->dad6_failed_addrs = g_slist_append (priv->dad6_failed_addrs,
+			                                          g_memdup (addr, sizeof (NMPlatformIP6Address)));
+		}
+		/* fallthrough */
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
 		if (!priv->queued_ip6_config_id) {
 			priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
@@ -7540,23 +8711,24 @@ nm_device_get_managed (NMDevice *self)
 	 * default-unmanaged flag (eg, only NM_UNMANAGED_DEFAULT is set) then
 	 * the device is managed whenever it's not in the UNMANAGED state.
 	 */
-	managed = !(priv->unmanaged_flags & ~NM_UNMANAGED_DEFAULT);
-	if (managed && (priv->unmanaged_flags & NM_UNMANAGED_DEFAULT))
+	managed = !NM_FLAGS_ANY (priv->unmanaged_flags, ~NM_UNMANAGED_DEFAULT);
+	if (managed && NM_FLAGS_HAS (priv->unmanaged_flags, NM_UNMANAGED_DEFAULT))
 		managed = (priv->state > NM_DEVICE_STATE_UNMANAGED);
 
 	return managed;
 }
 
 /**
- * nm_device_get_unmanaged_flag():
+ * nm_device_get_unmanaged_flags():
  * @self: the #NMDevice
+ * @flag: return only the selected flags
  *
- * Returns: %TRUE if the device is unmanaged for @flag.
+ * Returns: the unmanage flags of the device (filtered with @flag)
  */
-gboolean
-nm_device_get_unmanaged_flag (NMDevice *self, NMUnmanagedFlags flag)
+NMUnmanagedFlags
+nm_device_get_unmanaged_flags (NMDevice *self, NMUnmanagedFlags flag)
 {
-	return NM_FLAGS_ANY (NM_DEVICE_GET_PRIVATE (self)->unmanaged_flags, flag);
+	return NM_DEVICE_GET_PRIVATE (self)->unmanaged_flags & flag;
 }
 
 /**
@@ -7568,14 +8740,42 @@ nm_device_get_unmanaged_flag (NMDevice *self, NMUnmanagedFlags flag)
 static gboolean
 nm_device_get_default_unmanaged (NMDevice *self)
 {
-	return nm_device_get_unmanaged_flag (self, NM_UNMANAGED_DEFAULT);
+	return !!nm_device_get_unmanaged_flags (self, NM_UNMANAGED_DEFAULT);
+}
+
+static void
+_set_unmanaged_flags (NMDevice *self,
+                      NMUnmanagedFlags flags,
+                      gboolean unmanaged)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (unmanaged) {
+		if (!NM_FLAGS_ALL (priv->unmanaged_flags, flags)) {
+			_LOGD (LOGD_DEVICE, "unmanaged: flags set to 0x%0llx (was 0x%0llx, %s 0x%0llx)",
+			       (long long unsigned) (priv->unmanaged_flags | flags),
+			       (long long unsigned) priv->unmanaged_flags,
+			       "set",
+			       (long long unsigned) flags);
+			priv->unmanaged_flags |= flags;
+		}
+	} else {
+		if (NM_FLAGS_ANY (priv->unmanaged_flags, flags)) {
+			_LOGD (LOGD_DEVICE, "unmanaged: flags set to 0x%0llx (was 0x%0llx, %s 0x%0llx)",
+			       (long long unsigned) (priv->unmanaged_flags & (~flags)),
+			       (long long unsigned) priv->unmanaged_flags,
+			       "clear",
+			       (long long unsigned) flags);
+			priv->unmanaged_flags &= ~flags;
+		}
+	}
 }
 
 void
-nm_device_set_unmanaged (NMDevice *self,
-                         NMUnmanagedFlags flag,
-                         gboolean unmanaged,
-                         NMDeviceStateReason reason)
+nm_device_set_unmanaged_flags (NMDevice *self,
+                               NMUnmanagedFlags flag,
+                               gboolean unmanaged,
+                               NMDeviceStateReason reason)
 {
 	NMDevicePrivate *priv;
 	gboolean was_managed, now_managed;
@@ -7586,10 +8786,7 @@ nm_device_set_unmanaged (NMDevice *self,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	was_managed = nm_device_get_managed (self);
-	if (unmanaged)
-		priv->unmanaged_flags |= flag;
-	else
-		priv->unmanaged_flags &= ~flag;
+	_set_unmanaged_flags (self, flag, unmanaged);
 	now_managed = nm_device_get_managed (self);
 
 	if (was_managed != now_managed) {
@@ -7605,34 +8802,41 @@ nm_device_set_unmanaged (NMDevice *self,
 }
 
 void
-nm_device_set_unmanaged_quitting (NMDevice *self)
+nm_device_set_unmanaged_flags_by_device_spec (NMDevice *self, const GSList *unmanaged_specs)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+	gboolean unmanaged;
 
-	/* It's OK to block here because we're quitting */
-	if (nm_device_is_activating (self) || priv->state == NM_DEVICE_STATE_ACTIVATED)
-		_set_state_full (self, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_NOW_UNMANAGED, TRUE);
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->managed_touched_by_user)
+		return;
 
-	nm_device_set_unmanaged (self,
-	                         NM_UNMANAGED_INTERNAL,
-	                         TRUE,
-	                         NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
+	unmanaged = nm_device_spec_match_list (self, unmanaged_specs);
+	nm_device_set_unmanaged_flags (self,
+	                               NM_UNMANAGED_USER,
+	                               unmanaged,
+	                               unmanaged
+	                                   ? NM_DEVICE_STATE_REASON_NOW_UNMANAGED
+	                                   : NM_DEVICE_STATE_REASON_NOW_MANAGED);
 }
 
 /**
- * nm_device_set_initial_unmanaged_flag():
+ * nm_device_set_unmanaged_flags_initial():
  * @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
+ * Like nm_device_set_unmanaged_flags(), 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 *self,
-                                      NMUnmanagedFlags flag,
-                                      gboolean unmanaged)
+nm_device_set_unmanaged_flags_initial (NMDevice *self,
+                                       NMUnmanagedFlags flag,
+                                       gboolean unmanaged)
 {
 	NMDevicePrivate *priv;
 
@@ -7642,13 +8846,30 @@ nm_device_set_initial_unmanaged_flag (NMDevice *self,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	g_return_if_fail (priv->initialized == FALSE);
 
-	if (unmanaged)
-		priv->unmanaged_flags |= flag;
-	else
-		priv->unmanaged_flags &= ~flag;
+	_set_unmanaged_flags (self, flag, unmanaged);
 }
 
 void
+nm_device_set_unmanaged_quitting (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean need_deactivate = nm_device_is_activating (self) ||
+	                           priv->state == NM_DEVICE_STATE_ACTIVATED;
+
+	/* It's OK to block here because we're quitting */
+	if (need_deactivate)
+		_set_state_full (self, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_NOW_UNMANAGED, TRUE);
+
+	nm_device_set_unmanaged_flags (self,
+	                               NM_UNMANAGED_INTERNAL,
+	                               TRUE,
+	                               need_deactivate ? NM_DEVICE_STATE_REASON_REMOVED
+	                                               : NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
+}
+
+/*****************************************************************************/
+
+void
 nm_device_set_dhcp_timeout (NMDevice *self, guint32 timeout)
 {
 	g_return_if_fail (NM_IS_DEVICE (self));
@@ -7670,7 +8891,84 @@ nm_device_set_dhcp_anycast_address (NMDevice *self, const char *addr)
 	priv->dhcp_anycast_address = g_strdup (addr);
 }
 
-static void
+void
+nm_device_reapply_settings_immediately (NMDevice *self)
+{
+	NMConnection *applied_connection;
+	NMSettingsConnection *settings_connection;
+	NMDeviceState state;
+	NMSettingConnection *s_con_settings;
+	NMSettingConnection *s_con_applied;
+	const char *zone;
+	NMMetered metered;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	state = nm_device_get_state (self);
+	if (   state <= NM_DEVICE_STATE_DISCONNECTED
+	    || state > NM_DEVICE_STATE_ACTIVATED)
+		return;
+
+	applied_connection = nm_device_get_applied_connection (self);
+	settings_connection = nm_device_get_settings_connection (self);
+
+	if (!nm_settings_connection_has_unmodified_applied_connection (settings_connection,
+	                                                               applied_connection,
+	                                                               NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY))
+		return;
+
+	s_con_settings = nm_connection_get_setting_connection ((NMConnection *) settings_connection);
+	s_con_applied = nm_connection_get_setting_connection (applied_connection);
+
+	if (g_strcmp0 ((zone = nm_setting_connection_get_zone (s_con_settings)),
+	               nm_setting_connection_get_zone (s_con_applied)) != 0) {
+
+		_LOGD (LOGD_DEVICE, "reapply setting: zone = %s%s%s", NM_PRINT_FMT_QUOTE_STRING (zone));
+
+		g_object_set (G_OBJECT (s_con_applied),
+		              NM_SETTING_CONNECTION_ZONE, zone,
+		              NULL);
+
+		nm_device_update_firewall_zone (self);
+	}
+
+	if ((metered = nm_setting_connection_get_metered (s_con_settings)) != nm_setting_connection_get_metered (s_con_applied)) {
+
+		_LOGD (LOGD_DEVICE, "reapply setting: metered = %d", (int) metered);
+
+		g_object_set (G_OBJECT (s_con_applied),
+		              NM_SETTING_CONNECTION_METERED, metered,
+		              NULL);
+
+		nm_device_update_metered (self);
+	}
+}
+
+void
+nm_device_update_firewall_zone (NMDevice *self)
+{
+	NMConnection *applied_connection;
+	NMSettingConnection *s_con;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	applied_connection = nm_device_get_applied_connection (self);
+	if (!applied_connection)
+		return;
+
+	s_con = nm_connection_get_setting_connection (applied_connection);
+	if (    nm_device_get_state (self) == NM_DEVICE_STATE_ACTIVATED
+	    && !nm_device_uses_assumed_connection (self)) {
+		nm_firewall_manager_add_or_change_zone (nm_firewall_manager_get (),
+		                                        nm_device_get_ip_iface (self),
+		                                        nm_setting_connection_get_zone (s_con),
+		                                        FALSE, /* change zone */
+		                                        NULL,
+		                                        NULL);
+	}
+}
+
+void
 nm_device_update_metered (NMDevice *self)
 {
 #define NM_METERED_INVALID ((NMMetered) -1)
@@ -7688,7 +8986,7 @@ nm_device_update_metered (NMDevice *self)
 		value = NM_METERED_UNKNOWN;
 
 	if (value == NM_METERED_INVALID) {
-		connection = nm_device_get_connection (self);
+		connection = nm_device_get_applied_connection (self);
 		if (connection) {
 			setting = nm_connection_get_setting_connection (connection);
 			if (setting) {
@@ -7723,6 +9021,34 @@ nm_device_update_metered (NMDevice *self)
 	}
 }
 
+static gboolean
+_nm_device_check_connection_available (NMDevice *self,
+                                       NMConnection *connection,
+                                       NMDeviceCheckConAvailableFlags flags,
+                                       const char *specific_object)
+{
+	NMDeviceState state;
+
+	state = nm_device_get_state (self);
+	if (state < NM_DEVICE_STATE_UNMANAGED)
+		return FALSE;
+	if (   state < NM_DEVICE_STATE_UNAVAILABLE
+	    && nm_device_get_unmanaged_flags (self, NM_UNMANAGED_ALL & ~NM_UNMANAGED_DEFAULT))
+		return FALSE;
+	if (   state < NM_DEVICE_STATE_DISCONNECTED
+	    && !nm_device_is_software (self)
+	    && (   (   !NM_FLAGS_ANY (flags, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)
+	            && !nm_device_is_available (self, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE))
+	        || (    NM_FLAGS_ANY (flags, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)
+	            && !nm_device_is_available (self, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST))))
+		return FALSE;
+
+	if (!nm_device_check_connection_compatible (self, connection))
+		return FALSE;
+
+	return NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, flags, specific_object);
+}
+
 /**
  * nm_device_check_connection_available():
  * @self: the #NMDevice
@@ -7744,25 +9070,33 @@ nm_device_check_connection_available (NMDevice *self,
                                       NMDeviceCheckConAvailableFlags flags,
                                       const char *specific_object)
 {
-	NMDeviceState state;
+	gboolean available;
 
-	state = nm_device_get_state (self);
-	if (state < NM_DEVICE_STATE_UNMANAGED)
-		return FALSE;
-	if (   state < NM_DEVICE_STATE_UNAVAILABLE
-	    && nm_device_get_unmanaged_flag (self, NM_UNMANAGED_ALL & ~NM_UNMANAGED_DEFAULT))
-		return FALSE;
-	if (   state < NM_DEVICE_STATE_DISCONNECTED
-	    && (   (   !NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER)
-	            && !nm_device_is_available (self, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE))
-	        || (    NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER)
-	            && !nm_device_is_available (self, NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER))))
-		return FALSE;
+	available = _nm_device_check_connection_available (self, connection, flags, specific_object);
 
-	if (!nm_device_check_connection_compatible (self, connection))
-		return FALSE;
+#if NM_MORE_ASSERTS >= 2
+	{
+		/* The meaning of the flags is so that *adding* a flag relaxes a condition, thus making
+		 * the device *more* available. Assert against that requirement by testing all the flags. */
+		NMDeviceCheckConAvailableFlags i, j, k;
+		gboolean available_all[NM_DEVICE_CHECK_CON_AVAILABLE_ALL + 1] = { FALSE };
+
+		for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++)
+			available_all[i] = _nm_device_check_connection_available (self, connection, i, specific_object);
+
+		for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++) {
+			for (j = 1; j <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; j <<= 1) {
+				if (NM_FLAGS_HAS (i, j)) {
+					k = i & ~j;
+					nm_assert (   available_all[i] == available_all[k]
+					           || available_all[i]);
+				}
+			}
+		}
+	}
+#endif
 
-	return NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, flags, specific_object);
+	return available;
 }
 
 static void
@@ -7842,6 +9176,8 @@ nm_device_recheck_available_connections (NMDevice *self)
 
 		_signal_available_connections_changed (self);
 	}
+
+	available_connection_check_delete_unrealized (self);
 }
 
 /**
@@ -7883,29 +9219,44 @@ nm_device_get_available_connections (NMDevice *self, const char *specific_object
 static void
 cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
 {
-	if (_try_add_available_connection (NM_DEVICE (user_data), connection))
-		_signal_available_connections_changed (NM_DEVICE (user_data));
+	NMDevice *self = user_data;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	if (_try_add_available_connection (self, connection))
+		_signal_available_connections_changed (self);
 }
 
 static void
 cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
 {
-	if (_del_available_connection (NM_DEVICE (user_data), connection))
-		_signal_available_connections_changed (NM_DEVICE (user_data));
+	NMDevice *self = user_data;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	if (_del_available_connection (self, connection)) {
+		_signal_available_connections_changed (self);
+		available_connection_check_delete_unrealized (self);
+	}
 }
 
 static void
 cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
 {
+	NMDevice *self = user_data;
 	gboolean added, deleted;
 
+	g_return_if_fail (NM_IS_DEVICE (self));
+
 	/* FIXME: don't remove it from the hash if it's just going to get re-added */
-	deleted = _del_available_connection (NM_DEVICE (user_data), connection);
-	added = _try_add_available_connection (NM_DEVICE (user_data), connection);
+	deleted = _del_available_connection (self, connection);
+	added = _try_add_available_connection (self, connection);
 
 	/* Only signal if the connection was removed OR added, but not both */
-	if (added != deleted)
-		_signal_available_connections_changed (NM_DEVICE (user_data));
+	if (added != deleted) {
+		_signal_available_connections_changed (self);
+		available_connection_check_delete_unrealized (self);
+	}
 }
 
 gboolean
@@ -7979,12 +9330,13 @@ gboolean
 nm_device_remove_pending_action (NMDevice *self, const char *action, gboolean assert_is_pending)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	GSList *iter;
+	GSList *iter, *next;
 	guint count = 0;
 
 	g_return_val_if_fail (action, FALSE);
 
-	for (iter = priv->pending_actions; iter; iter = iter->next) {
+	for (iter = priv->pending_actions; iter; iter = next) {
+		next = iter->next;
 		if (!strcmp (action, iter->data)) {
 			_LOGD (LOGD_DEVICE, "remove_pending_action (%d): '%s'",
 			       count + g_slist_length (iter->next), /* length excluding 'iter' */
@@ -8018,38 +9370,23 @@ nm_device_has_pending_action (NMDevice *self)
 /***********************************************************/
 
 static void
-_cleanup_ip_pre (NMDevice *self, CleanupType cleanup_type)
-{
-	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, cleanup_type, FALSE);
-	arp_cleanup (self);
-	dhcp6_cleanup (self, cleanup_type, FALSE);
-	linklocal6_cleanup (self);
-	addrconf6_cleanup (self);
-	dnsmasq_cleanup (self);
-	aipd_cleanup (self);
-}
-
-static void
 _cancel_activation (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* Clean up when device was deactivated during call to firewall */
 	if (priv->fw_call) {
-		nm_firewall_manager_cancel_call (nm_firewall_manager_get (), priv->fw_call);
+		nm_firewall_manager_cancel_call (priv->fw_call);
+		g_warn_if_fail (!priv->fw_call);
 		priv->fw_call = NULL;
 	}
+	priv->fw_ready = FALSE;
 
 	ip_check_gw_ping_cleanup (self);
 
 	/* Break the activation chain */
-	activation_source_clear (self, TRUE, AF_INET);
-	activation_source_clear (self, TRUE, AF_INET6);
+	activation_source_clear (self, AF_INET);
+	activation_source_clear (self, AF_INET6);
 }
 
 static void
@@ -8059,19 +9396,22 @@ _cleanup_generic_pre (NMDevice *self, CleanupType cleanup_type)
 
 	_cancel_activation (self);
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	if (   cleanup_type == CLEANUP_TYPE_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,
+		                                      NULL,
 		                                      NULL);
 	}
 
 	/* Clear any queued transitions */
 	nm_device_queued_state_clear (self);
 
-	_cleanup_ip_pre (self, cleanup_type);
+	_cleanup_ip4_pre (self, cleanup_type);
+	_cleanup_ip6_pre (self, cleanup_type);
 }
 
 static void
@@ -8098,6 +9438,8 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	priv->v4_commit_first_time = TRUE;
 	priv->v6_commit_first_time = TRUE;
 
+	priv->linklocal6_dad_counter = 0;
+
 	/* Clean up IP configs; this does not actually deconfigure the
 	 * interface; the caller must flush routes and addresses explicitly.
 	 */
@@ -8107,15 +9449,18 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	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->con_ip6_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);
 
+	g_slist_free_full (priv->vpn4_configs, g_object_unref);
+	priv->vpn4_configs = NULL;
+	g_slist_free_full (priv->vpn6_configs, g_object_unref);
+	priv->vpn6_configs = NULL;
+
 	clear_act_request (self);
 
 	/* Clear legacy IPv4 address property */
@@ -8177,11 +9522,9 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	nm_device_master_release_slaves (self);
 
 	/* slave: mark no longer enslaved */
-	if (nm_platform_link_get_master (NM_PLATFORM_GET, priv->ifindex) <= 0) {
-		g_clear_object (&priv->master);
-		priv->enslaved = FALSE;
-		g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER);
-	}
+	if (   priv->master
+	    && nm_platform_link_get_master (NM_PLATFORM_GET, priv->ifindex) <= 0)
+		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 
 	/* Take out any entries in the routing table and any IP address the device had. */
 	ifindex = nm_device_get_ip_ifindex (self);
@@ -8190,6 +9533,9 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 		nm_platform_address_flush (NM_PLATFORM_GET, ifindex);
 	}
 
+	if (priv->lldp_listener)
+		nm_lldp_listener_stop (priv->lldp_listener);
+
 	nm_device_update_metered (self);
 	_cleanup_generic_post (self, cleanup_type);
 }
@@ -8241,13 +9587,14 @@ nm_device_spawn_iface_helper (NMDevice *self)
 	const char *method;
 	GPtrArray *argv;
 	gs_free char *dhcp4_address = NULL;
+	char *logging_backend;
 
 	if (priv->state != NM_DEVICE_STATE_ACTIVATED)
 		return;
 	if (!nm_device_can_assume_connections (self))
 		return;
 
-	connection = nm_device_get_connection (self);
+	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
 	argv = g_ptr_array_sized_new (10);
@@ -8259,6 +9606,17 @@ nm_device_spawn_iface_helper (NMDevice *self)
 	g_ptr_array_add (argv, g_strdup ("--uuid"));
 	g_ptr_array_add (argv, g_strdup (nm_connection_get_uuid (connection)));
 
+	logging_backend = nm_config_get_is_debug (nm_config_get ())
+	                  ? g_strdup ("debug")
+	                  : nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
+	                                              NM_CONFIG_KEYFILE_GROUP_LOGGING,
+	                                              NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
+	                                              NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
+	if (logging_backend) {
+		g_ptr_array_add (argv, g_strdup ("--logging-backend"));
+		g_ptr_array_add (argv, logging_backend);
+	}
+
 	dhcp4_address = find_dhcp4_address (self);
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
@@ -8278,7 +9636,7 @@ nm_device_spawn_iface_helper (NMDevice *self)
 			g_ptr_array_add (argv, g_strdup ("--dhcp4-required"));
 
 		if (priv->dhcp4_client) {
-			const char *hostname;
+			const char *hostname, *fqdn;
 			GBytes *client_id;
 
 			client_id = nm_dhcp_client_get_client_id (priv->dhcp4_client);
@@ -8294,6 +9652,12 @@ nm_device_spawn_iface_helper (NMDevice *self)
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-hostname"));
 				g_ptr_array_add (argv, g_strdup (hostname));
 			}
+
+			fqdn = nm_dhcp_client_get_fqdn (priv->dhcp4_client);
+			if (fqdn) {
+				g_ptr_array_add (argv, g_strdup ("--dhcp4-fqdn"));
+				g_ptr_array_add (argv, g_strdup (fqdn));
+			}
 		}
 
 		configured = TRUE;
@@ -8325,6 +9689,9 @@ nm_device_spawn_iface_helper (NMDevice *self)
 			g_ptr_array_add (argv, hex_iid);
 		}
 
+		g_ptr_array_add (argv, g_strdup ("--addr-gen-mode"));
+		g_ptr_array_add (argv, g_strdup_printf ("%d", nm_setting_ip6_config_get_addr_gen_mode (NM_SETTING_IP6_CONFIG (s_ip6))));
+
 		configured = TRUE;
 	}
 
@@ -8398,15 +9765,14 @@ deactivate_async_ready (NMDevice *self,
 	/* If operation cancelled, just return */
 	if (   g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
 	    || (priv->deactivating_cancellable && g_cancellable_is_cancelled (priv->deactivating_cancellable))) {
-		nm_log_warn (LOGD_DEVICE, "Deactivation (%s) cancelled",
-		             nm_device_get_iface (self));
+		_LOGW (LOGD_DEVICE, "Deactivation cancelled");
 	}
 	/* In every other case, transition to the DISCONNECTED state */
 	else {
-		if (error)
-			nm_log_warn (LOGD_DEVICE, "Deactivation (%s) failed: %s",
-			             nm_device_get_iface (self),
-			             error->message);
+		if (error) {
+			_LOGW (LOGD_DEVICE, "Deactivation failed: %s",
+			       error->message);
+		}
 		nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
 	}
 
@@ -8453,17 +9819,21 @@ _set_state_full (NMDevice *self,
                  NMDeviceStateReason reason,
                  gboolean quitting)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
 	NMDeviceState old_state;
 	NMActRequest *req;
 	gboolean no_firmware = FALSE;
-	NMConnection *connection;
+	NMSettingsConnection *connection;
+	NMConnection *applied_connection;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* Track re-entry */
 	g_warn_if_fail (priv->in_state_changed == FALSE);
-	priv->in_state_changed = TRUE;
 
-	g_return_if_fail (NM_IS_DEVICE (self));
+	old_state = priv->state;
 
 	/* 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
@@ -8471,14 +9841,16 @@ _set_state_full (NMDevice *self,
 	 */
 	if (   (priv->state == state)
 	    && !(state == NM_DEVICE_STATE_UNAVAILABLE && priv->firmware_missing)) {
-		priv->in_state_changed = FALSE;
+		_LOGD (LOGD_DEVICE, "device state change: %s -> %s (reason '%s') [%d %d %d] (skip due to missing firmware)",
+		       state_to_string (old_state),
+		       state_to_string (state),
+		       reason_to_string (reason),
+		       old_state,
+		       state,
+		       reason);
 		return;
 	}
 
-	old_state = priv->state;
-	priv->state = state;
-	priv->state_reason = reason;
-
 	_LOGI (LOGD_DEVICE, "device state change: %s -> %s (reason '%s') [%d %d %d]",
 	       state_to_string (old_state),
 	       state_to_string (state),
@@ -8487,6 +9859,11 @@ _set_state_full (NMDevice *self,
 	       state,
 	       reason);
 
+	priv->in_state_changed = TRUE;
+
+	priv->state = state;
+	priv->state_reason = reason;
+
 	/* Clear any queued transitions */
 	nm_device_queued_state_clear (self);
 
@@ -8573,7 +9950,8 @@ _set_state_full (NMDevice *self,
 			/* Clean up any half-done IP operations if the device's layer2
 			 * finds out it needs authentication during IP config.
 			 */
-			_cleanup_ip_pre (self, CLEANUP_TYPE_DECONFIGURE);
+			_cleanup_ip4_pre (self, CLEANUP_TYPE_DECONFIGURE);
+			_cleanup_ip6_pre (self, CLEANUP_TYPE_DECONFIGURE);
 		}
 		break;
 	default:
@@ -8587,7 +9965,7 @@ _set_state_full (NMDevice *self,
 
 	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);
+	g_signal_emit_by_name (self, NM_DEVICE_STATE_CHANGED, state, old_state, reason);
 
 	/* Post-process the event after internal notification */
 
@@ -8622,13 +10000,15 @@ _set_state_full (NMDevice *self,
 
 		if (quitting) {
 			nm_dispatcher_call_sync (DISPATCHER_ACTION_PRE_DOWN,
-			                         nm_act_request_get_connection (req),
+			                         nm_act_request_get_settings_connection (req),
+			                         nm_act_request_get_applied_connection (req),
 			                         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),
+			                         nm_act_request_get_settings_connection (req),
+			                         nm_act_request_get_applied_connection (req),
 			                         self,
 			                         deactivate_dispatcher_complete,
 			                         self,
@@ -8659,7 +10039,10 @@ _set_state_full (NMDevice *self,
 	case NM_DEVICE_STATE_ACTIVATED:
 		_LOGI (LOGD_DEVICE, "Activation: successful, device activated.");
 		nm_device_update_metered (self);
-		nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), self, NULL, NULL, NULL);
+		nm_dispatcher_call (DISPATCHER_ACTION_UP,
+		                    nm_act_request_get_settings_connection (req),
+		                    nm_act_request_get_applied_connection (req),
+		                    self, NULL, NULL, NULL);
 		break;
 	case NM_DEVICE_STATE_FAILED:
 		/* Usually upon failure the activation chain is interrupted in
@@ -8678,10 +10061,10 @@ _set_state_full (NMDevice *self,
 			break;
 		}
 
-		connection = nm_device_get_connection (self);
+		connection = nm_device_get_settings_connection (self);
 		_LOGW (LOGD_DEVICE | LOGD_WIFI,
 		       "Activation: failed for connection '%s'",
-		       connection ? nm_connection_get_id (connection) : "<unknown>");
+		       connection ? nm_settings_connection_get_id (connection) : "<unknown>");
 
 		/* Notify any slaves of the unexpected failure */
 		nm_device_master_release_slaves (self);
@@ -8691,11 +10074,8 @@ _set_state_full (NMDevice *self,
 		 * failed (zero timestamp), connections that succeeded (non-zero timestamp),
 		 * and those we haven't tried yet (no timestamp).
 		 */
-		if (connection && !nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), NULL)) {
-			nm_settings_connection_update_timestamp (NM_SETTINGS_CONNECTION (connection),
-			                                         (guint64) 0,
-			                                         TRUE);
-		}
+		if (connection && !nm_settings_connection_get_timestamp (connection, NULL))
+			nm_settings_connection_update_timestamp (connection, (guint64) 0, TRUE);
 
 		/* Schedule the transition to DISCONNECTED.  The device can't transition
 		 * immediately because we can't change states again from the state
@@ -8704,7 +10084,28 @@ _set_state_full (NMDevice *self,
 		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 (self);
+		/* Now that IP config has completed, check if the firewall
+		 * zone must be set again for the IP interface.
+		 */
+		applied_connection = nm_device_get_applied_connection (self);
+
+		if (   applied_connection
+		    && priv->ifindex != priv->ip_ifindex
+		    && !nm_device_uses_assumed_connection (self)) {
+			NMSettingConnection *s_con;
+			const char *zone;
+
+			s_con = nm_connection_get_setting_connection (applied_connection);
+			zone = nm_setting_connection_get_zone (s_con);
+			g_assert (!priv->fw_call);
+			priv->fw_call = nm_firewall_manager_add_or_change_zone (nm_firewall_manager_get (),
+			                                                        nm_device_get_ip_iface (self),
+			                                                        zone,
+			                                                        FALSE,
+			                                                        fw_change_zone_cb_ip_check,
+			                                                        self);
+		} else
+			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.
@@ -8724,10 +10125,17 @@ _set_state_full (NMDevice *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), self);
-		else
-			nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), self, NULL, NULL, NULL);
+		if (quitting) {
+			nm_dispatcher_call_sync (DISPATCHER_ACTION_DOWN,
+			                         nm_act_request_get_settings_connection (req),
+			                         nm_act_request_get_applied_connection (req),
+			                         self);
+		} else {
+			nm_dispatcher_call (DISPATCHER_ACTION_DOWN,
+			                    nm_act_request_get_settings_connection (req),
+			                    nm_act_request_get_applied_connection (req),
+			                    self, NULL, NULL, NULL);
+		}
 	}
 
 	/* IP-related properties are only valid when the device has IP configuration.
@@ -8836,7 +10244,7 @@ nm_device_queued_state_clear (NMDevice *self)
 	if (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_clear_g_source (&priv->queued_state.id);
 		nm_device_remove_pending_action (self, queued_state_to_string (priv->queued_state.state), TRUE);
 	}
 	memset (&priv->queued_state, 0, sizeof (priv->queued_state));
@@ -8864,20 +10272,26 @@ nm_device_get_hw_address (NMDevice *self)
 	return priv->hw_addr_len ? priv->hw_addr : NULL;
 }
 
-static void
+void
 nm_device_update_hw_address (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex = nm_device_get_ifindex (self);
 	const guint8 *hwaddr;
 	gsize hwaddrlen = 0;
+	static const guint8 zero_hwaddr[ETH_ALEN];
 
 	if (ifindex <= 0)
 		return;
 
 	hwaddr = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &hwaddrlen);
 
+	if (   priv->type == NM_DEVICE_TYPE_ETHERNET
+	    && nm_utils_hwaddr_matches (hwaddr, hwaddrlen, zero_hwaddr, sizeof (zero_hwaddr)))
+		hwaddrlen = 0;
+
 	if (hwaddrlen) {
+		priv->hw_addr_len = 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);
@@ -8889,12 +10303,39 @@ nm_device_update_hw_address (NMDevice *self)
 		/* Invalid or no hardware address */
 		if (priv->hw_addr_len != 0) {
 			g_clear_pointer (&priv->hw_addr, g_free);
+			priv->hw_addr_len = 0;
 			_LOGD (LOGD_HW | LOGD_DEVICE,
 			       "previous hardware address is no longer valid");
 			g_object_notify (G_OBJECT (self), NM_DEVICE_HW_ADDRESS);
 		}
 	}
-	priv->hw_addr_len = hwaddrlen;
+}
+
+void
+nm_device_update_initial_hw_address (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->hw_addr_len) {
+		priv->initial_hw_addr = g_strdup (priv->hw_addr);
+		_LOGD (LOGD_DEVICE | LOGD_HW, "read initial MAC address %s", priv->initial_hw_addr);
+
+		if (priv->ifindex > 0) {
+			guint8 buf[NM_UTILS_HWADDR_LEN_MAX];
+			size_t len = 0;
+
+			if (nm_platform_link_get_permanent_address (NM_PLATFORM_GET, priv->ifindex, buf, &len)) {
+				g_warn_if_fail (len == priv->hw_addr_len);
+				priv->perm_hw_addr = nm_utils_hwaddr_ntoa (buf, priv->hw_addr_len);
+				_LOGD (LOGD_DEVICE | LOGD_HW, "read permanent MAC address %s",
+				       priv->perm_hw_addr);
+			} else {
+				/* Fall back to current address */
+				_LOGD (LOGD_HW | LOGD_ETHER, "unable to read permanent MAC address");
+				priv->perm_hw_addr = g_strdup (priv->hw_addr);
+			}
+		}
+	}
 }
 
 gboolean
@@ -9004,7 +10445,7 @@ spec_match_list (NMDevice *self, const GSList *specs)
 			break;
 		}
 	}
-	if (priv->hw_addr_len) {
+	if (priv->hw_addr_len && priv->hw_addr) {
 		m = nm_match_spec_hwaddr (specs, priv->hw_addr);
 		matched = MAX (matched, m);
 	}
@@ -9021,12 +10462,34 @@ spec_match_list (NMDevice *self, const GSList *specs)
 
 /***********************************************************/
 
-#define DEFAULT_AUTOCONNECT TRUE
+static const char *
+_activation_func_to_string (ActivationHandleFunc func)
+{
+#define FUNC_TO_STRING_CHECK_AND_RETURN(func, f) \
+	G_STMT_START { \
+		if ((func) == (f)) \
+			return #f; \
+	} G_STMT_END
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage1_device_prepare);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage2_device_config);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage3_ip_config_start);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip4_config_timeout);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip6_config_timeout);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip4_config_commit);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip6_config_commit);
+	g_return_val_if_reached ("unknown");
+}
+
+/***********************************************************/
 
 static void
 nm_device_init (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_DEVICE, NMDevicePrivate);
+
+	self->priv = priv;
 
 	priv->type = NM_DEVICE_TYPE_UNKNOWN;
 	priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
@@ -9052,133 +10515,50 @@ constructor (GType type,
              GObjectConstructParam *construct_params)
 {
 	GObject *object;
+	GObjectClass *klass;
 	NMDevice *self;
 	NMDevicePrivate *priv;
-	NMPlatform *platform;
-	static guint32 id = 0;
+	const NMPlatformLink *pllink;
 
-	object = G_OBJECT_CLASS (nm_device_parent_class)->constructor (type,
-	                         n_construct_params,
-	                         construct_params);
+	klass = G_OBJECT_CLASS (nm_device_parent_class);
+	object = klass->constructor (type, n_construct_params, construct_params);
 	if (!object)
 		return NULL;
 
 	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) {
+		pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, priv->iface);
 
-	if (!priv->iface) {
-		_LOGE (LOGD_DEVICE, "No device interface provided, ignoring");
-		goto error;
-	}
-
-	if (!priv->udi) {
-		/* Use a placeholder UDI until we get a real one */
-		priv->udi = g_strdup_printf ("/virtual/device/placeholder/%d", id++);
+		if (pllink && link_type_compatible (self, pllink->type, NULL, NULL)) {
+			priv->ifindex = pllink->ifindex;
+			priv->up = NM_FLAGS_HAS (pllink->flags, IFF_UP);
+		}
 	}
 
-	if (NM_DEVICE_GET_CLASS (self)->get_generic_capabilities)
-		priv->capabilities |= NM_DEVICE_GET_CLASS (self)->get_generic_capabilities (self);
+	return object;
+}
 
-	if (priv->ifindex > 0) {
-		priv->physical_port_id = nm_platform_link_get_physical_port_id (NM_PLATFORM_GET, priv->ifindex);
-		priv->dev_id = nm_platform_link_get_dev_id (NM_PLATFORM_GET, priv->ifindex);
-		if (nm_platform_link_is_software (NM_PLATFORM_GET, priv->ifindex))
-			priv->capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
-		priv->mtu = nm_platform_link_get_mtu (NM_PLATFORM_GET, priv->ifindex);
+static void
+constructed (GObject *object)
+{
+	NMDevice *self = NM_DEVICE (object);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMPlatform *platform;
 
-		nm_platform_link_get_driver_info (NM_PLATFORM_GET,
-		                                  priv->ifindex,
-		                                  NULL,
-		                                  &priv->driver_version,
-		                                  &priv->firmware_version);
-	}
+	platform = nm_platform_get ();
 
 	if (NM_DEVICE_GET_CLASS (self)->get_generic_capabilities)
 		priv->capabilities |= NM_DEVICE_GET_CLASS (self)->get_generic_capabilities (self);
 
 	/* Watch for external IP config changes */
-	platform = nm_platform_get ();
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self);
 
-	/* trigger initial ip config change to initialize ip-config */
-	priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
-	priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
-
-	if (nm_platform_check_support_user_ipv6ll (NM_PLATFORM_GET)) {
-		int ip_ifindex = nm_device_get_ip_ifindex (self);
-
-		if (ip_ifindex > 0)
-			priv->nm_ipv6ll = nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, ip_ifindex);
-	}
-
-	return object;
-
-error:
-	g_object_unref (self);
-	return NULL;
-}
-
-static void
-constructed (GObject *object)
-{
-	NMDevice *self = NM_DEVICE (object);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int master;
-
-	nm_device_update_hw_address (self);
-
-	if (priv->hw_addr_len) {
-		priv->initial_hw_addr = g_strdup (priv->hw_addr);
-		_LOGD (LOGD_DEVICE | LOGD_HW, "read initial MAC address %s", priv->initial_hw_addr);
-
-		if (priv->ifindex > 0) {
-			guint8 buf[NM_UTILS_HWADDR_LEN_MAX];
-			size_t len = 0;
-
-			if (nm_platform_link_get_permanent_address (NM_PLATFORM_GET, priv->ifindex, buf, &len)) {
-				g_warn_if_fail (len == priv->hw_addr_len);
-				priv->perm_hw_addr = nm_utils_hwaddr_ntoa (buf, priv->hw_addr_len);
-				_LOGD (LOGD_DEVICE | LOGD_HW, "read permanent MAC address %s",
-				       priv->perm_hw_addr);
-			} else {
-				/* Fall back to current address */
-				_LOGD (LOGD_HW | LOGD_ETHER, "unable to read permanent MAC address");
-				priv->perm_hw_addr = g_strdup (priv->hw_addr);
-			}
-		}
-	}
-
-	/* Note: initial hardware address must be read before calling get_ignore_carrier() */
-	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
-		NMConfig *config = nm_config_get ();
-
-		priv->ignore_carrier = nm_config_data_get_ignore_carrier (nm_config_get_data (config), self);
-		g_signal_connect (G_OBJECT (config),
-		                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
-		                  G_CALLBACK (config_changed_update_ignore_carrier),
-		                  self);
-
-		check_carrier (self);
-		_LOGD (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;
-	}
-
-	/* Enslave ourselves */
-	master = nm_platform_link_get_master (NM_PLATFORM_GET, 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,
@@ -9206,6 +10586,8 @@ constructed (GObject *object)
 	}
 
 	G_OBJECT_CLASS (nm_device_parent_class)->constructed (object);
+
+	_LOGD (LOGD_DEVICE, "constructed (%s)", G_OBJECT_TYPE_NAME (self));
 }
 
 static void
@@ -9215,7 +10597,7 @@ dispose (GObject *object)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatform *platform;
 
-	_LOGD (LOGD_DEVICE, "dispose(): %s", G_OBJECT_TYPE_NAME (self));
+	_LOGD (LOGD_DEVICE, "disposing");
 
 	g_signal_handlers_disconnect_by_func (nm_config_get (), config_changed_update_ignore_carrier, self);
 
@@ -9233,15 +10615,10 @@ dispose (GObject *object)
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
-	if (priv->recheck_assume_id) {
-		g_source_remove (priv->recheck_assume_id);
-		priv->recheck_assume_id = 0;
-	}
+	nm_clear_g_source (&priv->recheck_assume_id);
+	nm_clear_g_source (&priv->recheck_available.call_id);
 
-	if (priv->recheck_available.call_id) {
-		g_source_remove (priv->recheck_available.call_id);
-		priv->recheck_available.call_id = 0;
-	}
+	nm_clear_g_source (&priv->check_delete_unrealized_id);
 
 	link_disconnect_action_cancel (self);
 
@@ -9265,7 +10642,22 @@ dispose (GObject *object)
 	nm_clear_g_source (&priv->device_link_changed_id);
 	nm_clear_g_source (&priv->device_ip_link_changed_id);
 
+	if (priv->lldp_listener) {
+		g_signal_handlers_disconnect_by_func (priv->lldp_listener,
+		                                      G_CALLBACK (lldp_neighbors_changed),
+		                                      self);
+		nm_lldp_listener_stop (priv->lldp_listener);
+		g_clear_object (&priv->lldp_listener);
+	}
+
 	G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
+
+	if (nm_clear_g_source (&priv->queued_state.id)) {
+		/* FIXME: we'd expect the queud_state to be alredy cleared and this statement
+		 * not being necessary. Add this check here to hopefully investigate crash
+		 * rh#1270247. */
+		g_return_if_reached ();
+	}
 }
 
 static void
@@ -9280,9 +10672,9 @@ finalize (GObject *object)
 	g_free (priv->perm_hw_addr);
 	g_free (priv->initial_hw_addr);
 	g_slist_free_full (priv->pending_actions, g_free);
+	g_slist_free_full (priv->dad6_failed_addrs, g_free);
 	g_clear_pointer (&priv->physical_port_id, g_free);
 	g_free (priv->udi);
-	g_free (priv->path);
 	g_free (priv->iface);
 	g_free (priv->ip_iface);
 	g_free (priv->driver);
@@ -9304,25 +10696,11 @@ set_property (GObject *object, guint prop_id,
 {
 	NMDevice *self = NM_DEVICE (object);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMPlatformLink *platform_device;
 	const char *hw_addr, *p;
 	guint count;
+	gboolean val_bool;
 
 	switch (prop_id) {
-	case PROP_PLATFORM_DEVICE:
-		platform_device = g_value_get_pointer (value);
-		if (platform_device) {
-			g_free (priv->udi);
-			priv->udi = g_strdup (nm_platform_link_get_udi (NM_PLATFORM_GET, platform_device->ifindex));
-			g_free (priv->iface);
-			priv->iface = g_strdup (platform_device->name);
-			priv->ifindex = platform_device->ifindex;
-			priv->up = NM_FLAGS_HAS (platform_device->flags, IFF_UP);
-			g_free (priv->driver);
-			priv->driver = g_strdup (platform_device->driver);
-			priv->platform_link_initialized = platform_device->initialized;
-		}
-		break;
 	case PROP_UDI:
 		if (g_value_get_string (value)) {
 			g_free (priv->udi);
@@ -9330,13 +10708,9 @@ set_property (GObject *object, guint prop_id,
 		}
 		break;
 	case PROP_IFACE:
-		if (g_value_get_string (value)) {
-			g_free (priv->iface);
-			priv->iface = g_value_dup_string (value);
-			priv->ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, priv->iface);
-			if (priv->ifindex > 0)
-				priv->up = nm_platform_link_is_up (NM_PLATFORM_GET, priv->ifindex);
-		}
+		/* construct only */
+		g_return_if_fail (!priv->iface);
+		priv->iface = g_value_dup_string (value);
 		break;
 	case PROP_DRIVER:
 		if (g_value_get_string (value)) {
@@ -9358,16 +10732,32 @@ set_property (GObject *object, guint prop_id,
 	case PROP_IP4_ADDRESS:
 		priv->ip4_address = g_value_get_uint (value);
 		break;
+	case PROP_MANAGED:
+		val_bool = g_value_get_boolean (value);
+		priv->managed_touched_by_user = TRUE;
+		nm_device_set_unmanaged_flags (self,
+		                               NM_UNMANAGED_USER | (val_bool ? NM_UNMANAGED_DEFAULT : NM_UNMANAGED_NONE),
+		                               !val_bool,
+		                               NM_DEVICE_STATE_REASON_USER_REQUESTED);
+		break;
 	case PROP_AUTOCONNECT:
 		nm_device_set_autoconnect (self, g_value_get_boolean (value));
 		break;
 	case PROP_FIRMWARE_MISSING:
 		priv->firmware_missing = g_value_get_boolean (value);
 		break;
+	case PROP_NM_PLUGIN_MISSING:
+		priv->nm_plugin_missing = g_value_get_boolean (value);
+		break;
 	case PROP_DEVICE_TYPE:
 		g_return_if_fail (priv->type == NM_DEVICE_TYPE_UNKNOWN);
 		priv->type = g_value_get_uint (value);
 		break;
+	case PROP_LINK_TYPE:
+		/* construct only */
+		g_return_if_fail (priv->link_type == NM_LINK_TYPE_NONE);
+		priv->link_type = g_value_get_uint (value);
+		break;
 	case PROP_TYPE_DESC:
 		g_free (priv->type_desc);
 		priv->type_desc = g_value_dup_string (value);
@@ -9411,18 +10801,16 @@ set_property (GObject *object, guint prop_id,
 	}
 }
 
-#define DBUS_TYPE_STATE_REASON_STRUCT (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID))
-
 static void
 get_property (GObject *object, guint prop_id,
-			  GValue *value, GParamSpec *pspec)
+              GValue *value, GParamSpec *pspec)
 {
 	NMDevice *self = NM_DEVICE (object);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *ac_path = NULL;
 	GPtrArray *array;
 	GHashTableIter iter;
 	NMConnection *connection;
+	GVariantBuilder array_builder;
 
 	switch (prop_id) {
 	case PROP_UDI:
@@ -9462,44 +10850,33 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->mtu);
 		break;
 	case PROP_IP4_CONFIG:
-		if (ip_config_valid (priv->state) && priv->ip4_config)
-			g_value_set_boxed (value, nm_ip4_config_get_dbus_path (priv->ip4_config));
-		else
-			g_value_set_boxed (value, "/");
+		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip4_config : NULL);
 		break;
 	case PROP_DHCP4_CONFIG:
-		if (ip_config_valid (priv->state) && priv->dhcp4_config)
-			g_value_set_boxed (value, nm_dhcp4_config_get_dbus_path (priv->dhcp4_config));
-		else
-			g_value_set_boxed (value, "/");
+		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp4_config : NULL);
 		break;
 	case PROP_IP6_CONFIG:
-		if (ip_config_valid (priv->state) && priv->ip6_config)
-			g_value_set_boxed (value, nm_ip6_config_get_dbus_path (priv->ip6_config));
-		else
-			g_value_set_boxed (value, "/");
+		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip6_config : NULL);
 		break;
 	case PROP_DHCP6_CONFIG:
-		if (ip_config_valid (priv->state) && priv->dhcp6_config)
-			g_value_set_boxed (value, nm_dhcp6_config_get_dbus_path (priv->dhcp6_config));
-		else
-			g_value_set_boxed (value, "/");
+		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp6_config : NULL);
 		break;
 	case PROP_STATE:
 		g_value_set_uint (value, priv->state);
 		break;
 	case PROP_STATE_REASON:
-		g_value_take_boxed (value, dbus_g_type_specialized_construct (DBUS_TYPE_STATE_REASON_STRUCT));
-		dbus_g_type_struct_set (value, 0, priv->state, 1, priv->state_reason, G_MAXUINT);
+		g_value_take_variant (value,
+		                      g_variant_new ("(uu)", priv->state, priv->state_reason));
 		break;
 	case PROP_ACTIVE_CONNECTION:
-		if (priv->act_request)
-			ac_path = nm_active_connection_get_path (NM_ACTIVE_CONNECTION (priv->act_request));
-		g_value_set_boxed (value, ac_path ? ac_path : "/");
+		nm_utils_g_value_set_object_path (value, priv->act_request);
 		break;
 	case PROP_DEVICE_TYPE:
 		g_value_set_uint (value, priv->type);
 		break;
+	case PROP_LINK_TYPE:
+		g_value_set_uint (value, priv->link_type);
+		break;
 	case PROP_MANAGED:
 		g_value_set_boolean (value, nm_device_get_managed (self));
 		break;
@@ -9509,6 +10886,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_FIRMWARE_MISSING:
 		g_value_set_boolean (value, priv->firmware_missing);
 		break;
+	case PROP_NM_PLUGIN_MISSING:
+		g_value_set_boolean (value, priv->nm_plugin_missing);
+		break;
 	case PROP_TYPE_DESC:
 		g_value_set_string (value, priv->type_desc);
 		break;
@@ -9520,7 +10900,8 @@ get_property (GObject *object, guint prop_id,
 		g_hash_table_iter_init (&iter, priv->available_connections);
 		while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL))
 			g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection)));
-		g_value_take_boxed (value, array);
+		g_ptr_array_add (array, NULL);
+		g_value_take_boxed (value, (char **) g_ptr_array_free (array, FALSE));
 		break;
 	case PROP_PHYSICAL_PORT_ID:
 		g_value_set_string (value, priv->physical_port_id);
@@ -9529,7 +10910,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_boolean (value, priv->is_master);
 		break;
 	case PROP_MASTER:
-		g_value_set_object (value, priv->master);
+		g_value_set_object (value, nm_device_get_master (self));
 		break;
 	case PROP_HW_ADDRESS:
 		g_value_set_string (value, priv->hw_addr);
@@ -9540,6 +10921,37 @@ get_property (GObject *object, guint prop_id,
 	case PROP_METERED:
 		g_value_set_uint (value, priv->metered);
 		break;
+	case PROP_LLDP_NEIGHBORS:
+		if (priv->lldp_listener)
+			g_value_set_variant (value, nm_lldp_listener_get_neighbors (priv->lldp_listener));
+		else {
+			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
+			g_value_take_variant (value, g_variant_builder_end (&array_builder));
+		}
+		break;
+	case PROP_REAL:
+		g_value_set_boolean (value, nm_device_is_real (self));
+		break;
+	case PROP_SLAVES: {
+		GSList *slave_iter;
+		char **slave_list;
+		guint i;
+
+		slave_list = g_new (char *, g_slist_length (priv->slaves) + 1);
+		for (slave_iter = priv->slaves, i = 0; slave_iter; slave_iter = slave_iter->next) {
+			SlaveInfo *info = slave_iter->data;
+			const char *path;
+
+			if (!NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
+				continue;
+			path = nm_exported_object_get_path ((NMExportedObject *) info->slave);
+			if (path)
+				slave_list[i++] = g_strdup (path);
+		}
+		slave_list[i] = NULL;
+		g_value_take_boxed (value, slave_list);
+		break;
+	}
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -9550,9 +10962,12 @@ static void
 nm_device_class_init (NMDeviceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (klass);
 
 	g_type_class_add_private (object_class, sizeof (NMDevicePrivate));
 
+	exported_object_class->export_path = NM_DBUS_PATH "/Devices/%u";
+
 	/* Virtual methods */
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
@@ -9578,6 +10993,8 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->check_connection_compatible = check_connection_compatible;
 	klass->check_connection_available = check_connection_available;
 	klass->can_unmanaged_external_down = can_unmanaged_external_down;
+	klass->realize_start_notify = realize_start_notify;
+	klass->unrealize_notify = unrealize_notify;
 	klass->is_up = is_up;
 	klass->bring_up = bring_up;
 	klass->take_down = take_down;
@@ -9586,12 +11003,6 @@ nm_device_class_init (NMDeviceClass *klass)
 
 	/* Properties */
 	g_object_class_install_property
-		(object_class, PROP_PLATFORM_DEVICE,
-		 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, "", "",
 		                      NULL,
@@ -9663,31 +11074,31 @@ nm_device_class_init (NMDeviceClass *klass)
 
 	g_object_class_install_property
 		(object_class, PROP_IP4_CONFIG,
-		 g_param_spec_boxed (NM_DEVICE_IP4_CONFIG, "", "",
-		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READWRITE |
-		                     G_PARAM_STATIC_STRINGS));
+		 g_param_spec_string (NM_DEVICE_IP4_CONFIG, "", "",
+		                      NULL,
+		                      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, "", "",
-		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READWRITE |
-		                     G_PARAM_STATIC_STRINGS));
+		 g_param_spec_string (NM_DEVICE_DHCP4_CONFIG, "", "",
+		                      NULL,
+		                      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, "", "",
-		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READWRITE |
-		                     G_PARAM_STATIC_STRINGS));
+		 g_param_spec_string (NM_DEVICE_IP6_CONFIG, "", "",
+		                      NULL,
+		                      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, "", "",
-		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READWRITE |
-		                     G_PARAM_STATIC_STRINGS));
+		 g_param_spec_string (NM_DEVICE_DHCP6_CONFIG, "", "",
+		                      NULL,
+		                      G_PARAM_READWRITE |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_STATE,
@@ -9698,17 +11109,18 @@ nm_device_class_init (NMDeviceClass *klass)
 
 	g_object_class_install_property
 		(object_class, PROP_STATE_REASON,
-		 g_param_spec_boxed (NM_DEVICE_STATE_REASON, "", "",
-		                     DBUS_TYPE_STATE_REASON_STRUCT,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS));
+		 g_param_spec_variant (NM_DEVICE_STATE_REASON, "", "",
+		                       G_VARIANT_TYPE ("(uu)"),
+		                       NULL,
+		                       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, "", "",
-		                     DBUS_TYPE_G_OBJECT_PATH,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS));
+		 g_param_spec_string (NM_DEVICE_ACTIVE_CONNECTION, "", "",
+		                      NULL,
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
 		(object_class, PROP_DEVICE_TYPE,
@@ -9718,10 +11130,17 @@ nm_device_class_init (NMDeviceClass *klass)
 		                    G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
+		(object_class, PROP_LINK_TYPE,
+		 g_param_spec_uint (NM_DEVICE_LINK_TYPE, "", "",
+		                    0, G_MAXUINT32, NM_LINK_TYPE_NONE,
+		                    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, "", "",
 		                       FALSE,
-		                       G_PARAM_READABLE |
+		                       G_PARAM_READWRITE |
 		                       G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
@@ -9739,6 +11158,13 @@ nm_device_class_init (NMDeviceClass *klass)
 		                       G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
+		(object_class, PROP_NM_PLUGIN_MISSING,
+		 g_param_spec_boolean (NM_DEVICE_NM_PLUGIN_MISSING, "", "",
+		                       FALSE,
+		                       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, "", "",
 		                      NULL,
@@ -9764,7 +11190,7 @@ nm_device_class_init (NMDeviceClass *klass)
 	g_object_class_install_property
 		(object_class, PROP_AVAILABLE_CONNECTIONS,
 		 g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS, "", "",
-		                     DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH,
+		                     G_TYPE_STRV,
 		                     G_PARAM_READABLE |
 		                     G_PARAM_STATIC_STRINGS));
 
@@ -9808,7 +11234,7 @@ nm_device_class_init (NMDeviceClass *klass)
 	 *
 	 * Whether the connection is metered.
 	 *
-	 * Since: 1.0.6
+	 * Since: 1.2
 	 **/
 	g_object_class_install_property
 		(object_class, PROP_METERED,
@@ -9817,9 +11243,31 @@ nm_device_class_init (NMDeviceClass *klass)
 		                    G_PARAM_READABLE |
 		                    G_PARAM_STATIC_STRINGS));
 
+	g_object_class_install_property
+		(object_class, PROP_LLDP_NEIGHBORS,
+		 g_param_spec_variant (NM_DEVICE_LLDP_NEIGHBORS, "", "",
+		                       G_VARIANT_TYPE ("aa{sv}"),
+		                       NULL,
+		                       G_PARAM_READABLE |
+		                       G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
+		(object_class, PROP_REAL,
+		 g_param_spec_boolean (NM_DEVICE_REAL, "", "",
+		                       FALSE,
+		                       G_PARAM_READABLE |
+		                       G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
+	    (object_class, PROP_SLAVES,
+	     g_param_spec_boxed (NM_DEVICE_SLAVES, "", "",
+	                         G_TYPE_STRV,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS));
+
 	/* Signals */
 	signals[STATE_CHANGED] =
-		g_signal_new ("state-changed",
+		g_signal_new (NM_DEVICE_STATE_CHANGED,
 		              G_OBJECT_CLASS_TYPE (object_class),
 		              G_SIGNAL_RUN_LAST,
 		              G_STRUCT_OFFSET (NMDeviceClass, state_changed),
@@ -9840,8 +11288,8 @@ nm_device_class_init (NMDeviceClass *klass)
 		              G_OBJECT_CLASS_TYPE (object_class),
 		              G_SIGNAL_RUN_FIRST,
 		              0, NULL, NULL, NULL,
-		              /* dbus-glib context, connection, permission, allow_interaction, callback, user_data */
-		              G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER, G_TYPE_POINTER);
+		              /* context, connection, permission, allow_interaction, callback, user_data */
+		              G_TYPE_NONE, 6, G_TYPE_DBUS_METHOD_INVOCATION, NM_TYPE_CONNECTION, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER, G_TYPE_POINTER);
 
 	signals[IP4_CONFIG_CHANGED] =
 		g_signal_new (NM_DEVICE_IP4_CONFIG_CHANGED,
@@ -9878,17 +11326,10 @@ nm_device_class_init (NMDeviceClass *klass)
 		              0, NULL, NULL, NULL,
 		              G_TYPE_NONE, 0);
 
-	signals[LINK_INITIALIZED] =
-		g_signal_new (NM_DEVICE_LINK_INITIALIZED,
-		              G_OBJECT_CLASS_TYPE (object_class),
-		              G_SIGNAL_RUN_FIRST,
-		              0, NULL, NULL, NULL,
-		              G_TYPE_NONE, 0);
-
-	nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
-	                                        G_TYPE_FROM_CLASS (klass),
-	                                        &dbus_glib_nm_device_object_info);
-
-	dbus_g_error_domain_register (NM_DEVICE_ERROR, NULL, NM_TYPE_DEVICE_ERROR);
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_SKELETON,
+	                                        "Reapply", impl_device_reapply,
+	                                        "Disconnect", impl_device_disconnect,
+	                                        "Delete", impl_device_delete,
+	                                        NULL);
 }
-