summary refs log tree commit diff
path: root/src/devices/nm-device.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-04-23 18:00:21 +0200
committerMichael Biebl <biebl@debian.org>2018-04-23 18:00:21 +0200
commitf60117b41d5433be1b4a96d82cd11d0c3dce9b63 (patch)
tree2dd55c4ab0fdcbe9cddb26adb4a554b1a45c73c8 /src/devices/nm-device.c
parent7e9ff09fcb2366b383b7ebbec80d2f6fff117290 (diff)
New upstream version 1.11.3 upstream/1.11.3
Diffstat (limited to 'src/devices/nm-device.c')
-rw-r--r--src/devices/nm-device.c3950
1 files changed, 2301 insertions, 1649 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index b8828d14..815de29a 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -34,6 +34,7 @@
 #include <arpa/inet.h>
 #include <fcntl.h>
 #include <linux/if_addr.h>
+#include <linux/if_arp.h>
 #include <linux/rtnetlink.h>
 #include <linux/pkt_sched.h>
 
@@ -48,6 +49,7 @@
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
 #include "dhcp/nm-dhcp-manager.h"
+#include "dhcp/nm-dhcp-utils.h"
 #include "nm-act-request.h"
 #include "nm-proxy-config.h"
 #include "nm-ip4-config.h"
@@ -64,13 +66,13 @@
 #include "nm-netns.h"
 #include "nm-dispatcher.h"
 #include "nm-config.h"
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
 #include "dns/nm-dns-manager.h"
+#include "nm-acd-manager.h"
 #include "nm-core-internal.h"
 #include "systemd/nm-sd.h"
 #include "nm-lldp-listener.h"
 #include "nm-audit-manager.h"
-#include "nm-arping-manager.h"
 #include "nm-connectivity.h"
 #include "nm-dbus-interface.h"
 #include "nm-device-vlan.h"
@@ -78,14 +80,10 @@
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF (NMDevice);
 
-#include "introspection/org.freedesktop.NetworkManager.Device.h"
-#include "introspection/org.freedesktop.NetworkManager.Device.Statistics.h"
-
 /*****************************************************************************/
 
-#define DHCP_RESTART_TIMEOUT   120
-#define DHCP_NUM_TRIES_MAX     3
 #define DEFAULT_AUTOCONNECT    TRUE
+#define DHCP_GRACE_PERIOD_SEC  480
 
 #define CARRIER_WAIT_TIME_MS 6000
 #define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000
@@ -131,13 +129,13 @@ typedef struct {
 	int ifindex;
 } DeleteOnDeactivateData;
 
-typedef void (*ArpingCallback) (NMDevice *, NMIP4Config **, gboolean);
+typedef void (*AcdCallback) (NMDevice *, NMIP4Config **, gboolean);
 
 typedef struct {
-	ArpingCallback callback;
+	AcdCallback callback;
 	NMDevice *device;
 	NMIP4Config **configs;
-} ArpingData;
+} AcdData;
 
 typedef enum {
 	HW_ADDR_TYPE_UNSET = 0,
@@ -153,6 +151,24 @@ typedef enum {
 	FIREWALL_STATE_WAIT_IP_CONFIG,
 } FirewallState;
 
+typedef struct {
+	NMIPConfig *orig;      /* the original configuration applied to the device */
+	NMIPConfig *current;   /* configuration after external changes.  NULL means
+	                          that the original configuration didn't change. */
+} AppliedConfig;
+
+struct _NMDeviceConnectivityHandle {
+	CList concheck_lst;
+	NMDevice *self;
+	NMDeviceConnectivityCallback callback;
+	gpointer user_data;
+	NMConnectivityCheckHandle *c_handle;
+	guint64 seq;
+	bool is_periodic:1;
+	bool is_periodic_bump:1;
+	bool is_periodic_bump_on_complete:1;
+};
+
 /*****************************************************************************/
 
 enum {
@@ -166,6 +182,7 @@ enum {
 	REMOVED,
 	RECHECK_AUTO_ACTIVATE,
 	RECHECK_ASSUME,
+	CONNECTIVITY_CHANGED,
 	LAST_SIGNAL,
 };
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -230,12 +247,18 @@ typedef struct _NMDevicePrivate {
 		NMDeviceStateReason reason;
 	} queued_state;
 
-	guint queued_ip4_config_id;
-	guint queued_ip6_config_id;
+	union {
+		struct {
+			guint queued_ip_config_id_6;
+			guint queued_ip_config_id_4;
+		};
+		guint queued_ip_config_id_x[2];
+	};
+
 	GSList *pending_actions;
 	GSList *dad6_failed_addrs;
 
-	NMDevice *parent_device;
+	NMDBusTrackObjPath parent_device;
 
 	char *        udi;
 	char *        iface;   /* may change, could be renamed by user */
@@ -259,11 +282,13 @@ typedef struct _NMDevicePrivate {
 	bool queued_ip4_config_pending:1;
 	bool queued_ip6_config_pending:1;
 
+	bool update_ip_config_completed_v4:1;
+	bool update_ip_config_completed_v6:1;
+
 	char *        ip_iface;
 	int           ip_ifindex;
 	NMDeviceType  type;
 	char *        type_desc;
-	char *        type_description;
 	NMLinkType    link_type;
 	NMDeviceCapabilities capabilities;
 	char *        driver;
@@ -298,9 +323,7 @@ typedef struct _NMDevicePrivate {
 
 	NMActRequest *  queued_act_request;
 	bool            queued_act_request_is_waiting_for_carrier:1;
-	bool            act_request_public:1;
-	NMActRequest   *act_request;
-	gulong          act_request_id;
+	NMDBusTrackObjPath act_request;
 	ActivationHandleData act_handle4; /* for layer2 and IPv4. */
 	ActivationHandleData act_handle6;
 	guint           recheck_assume_id;
@@ -357,6 +380,12 @@ typedef struct _NMDevicePrivate {
 
 	NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags:4;
 
+	bool            is_enslaved:1;
+	bool            master_ready_handled:1;
+
+	bool            ipv6ll_handle:1; /* TRUE if NM handles the device's IPv6LL address */
+	bool            ipv6ll_has:1;
+
 	/* Generic DHCP stuff */
 	char *          dhcp_anycast_address;
 
@@ -367,17 +396,56 @@ typedef struct _NMDevicePrivate {
 	NMPacrunnerManager *pacrunner_manager;
 	NMPacrunnerCallId *pacrunner_call_id;
 
-	/* IP4 configuration info */
-	NMIP4Config *   ip4_config;     /* Combined config from VPN, settings, and device */
+	/* IP configuration info. Combined config from VPN, settings, and device */
+	union {
+		struct {
+			NMIP6Config *ip_config_6;
+			NMIP4Config *ip_config_4;
+		};
+		NMIPConfig *ip_config_x[2];
+	};
+
 	union {
 		const IpState   ip4_state;
 		IpState         ip4_state_;
 	};
-	NMIP4Config *   con_ip4_config; /* config from the setting */
-	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 */
+	AppliedConfig   dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
+
+	/* config from the setting */
+	union {
+		struct {
+			NMIP6Config *con_ip_config_6;
+			NMIP4Config *con_ip_config_4;
+		};
+		NMIPConfig *con_ip_config_x[2];
+	};
+
+	/* Stuff added outside NM */
+	union {
+		struct {
+			NMIP6Config *ext_ip_config_6;
+			NMIP4Config *ext_ip_config_4;
+		};
+		NMIPConfig *ext_ip_config_x[2];
+	};
+
+	/* VPNs which use this device */
+	union {
+		struct {
+			GSList *vpn_configs_6;
+			GSList *vpn_configs_4;
+		};
+		GSList *vpn_configs_x[2];
+	};
+
+	/* WWAN configuration */
+	union {
+		struct {
+			AppliedConfig  wwan_ip_config_6;
+			AppliedConfig  wwan_ip_config_4;
+		};
+		AppliedConfig wwan_ip_config_x[2];
+	};
 
 	bool v4_has_shadowed_routes;
 	const char *ip4_rp_filter;
@@ -387,10 +455,9 @@ typedef struct _NMDevicePrivate {
 		NMDhcpClient *  client;
 		gulong          state_sigid;
 		NMDhcp4Config * config;
-		guint           restart_id;
-		guint           num_tries_left;
 		char *          pac_url;
 		bool            was_active;
+		guint           grace_id;
 	} dhcp4;
 
 	struct {
@@ -398,8 +465,8 @@ typedef struct _NMDevicePrivate {
 		guint timeout;
 		guint watch;
 		GPid pid;
-		const char *binary;
-		const char *address;
+		char *binary;
+		char *address;
 		guint deadline;
 	} gw_ping;
 
@@ -420,22 +487,17 @@ typedef struct _NMDevicePrivate {
 	/* IPv4 DAD stuff */
 	struct {
 		GSList *          dad_list;
-		NMArpingManager * announcing;
-	} arping;
+		NMAcdManager * announcing;
+	} acd;
 
-	/* IP6 configuration info */
-	NMIP6Config *  ip6_config;
 	union {
 		const IpState   ip6_state;
 		IpState         ip6_state_;
 	};
-	NMIP6Config *  con_ip6_config; /* config from the setting */
-	NMIP6Config *  wwan_ip6_config;
-	NMIP6Config *  ext_ip6_config; /* Stuff added outside NM */
+	AppliedConfig  ac_ip6_config;  /* config from IPv6 autoconfiguration */
 	NMIP6Config *  ext_ip6_config_captured; /* Configuration captured from platform. */
-	GSList *       vpn6_configs;   /* VPNs which use this device */
-	bool           nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
 	NMIP6Config *  dad6_ip6_config;
+	struct in6_addr ipv6ll_addr;
 
 	GHashTable *   rt6_temporary_not_available;
 
@@ -443,8 +505,6 @@ typedef struct _NMDevicePrivate {
 	gulong         ndisc_changed_id;
 	gulong         ndisc_timeout_id;
 	NMSettingIP6ConfigPrivacy ndisc_use_tempaddr;
-	/* IP6 config from autoconf */
-	NMIP6Config *  ac_ip6_config;
 
 	guint          linklocal6_timeout_id;
 	guint8         linklocal6_dad_counter;
@@ -458,21 +518,18 @@ typedef struct _NMDevicePrivate {
 		gulong           prefix_sigid;
 		NMDhcp6Config *  config;
 		/* IP6 config from DHCP */
-		NMIP6Config *    ip6_config;
+		AppliedConfig    ip6_config;
 		/* Event ID of the current IP6 config from DHCP */
 		char *           event_id;
-		guint            restart_id;
-		guint            num_tries_left;
 		guint            needed_prefixes;
 		bool             was_active;
+		guint            grace_id;
 	} dhcp6;
 
 	gboolean needs_ip6_subnet;
 
 	/* master interface for bridge/bond/team slave */
 	NMDevice *      master;
-	bool            is_enslaved;
-	bool            master_ready_handled;
 	gulong          master_ready_id;
 
 	/* slave management */
@@ -485,9 +542,26 @@ typedef struct _NMDevicePrivate {
 	NMNetns *netns;
 
 	NMLldpListener *lldp_listener;
+
+	NMConnectivity *concheck_mgr;
+
+	/* if periodic checks are enabled, this is the source id for the next check. */
+	guint concheck_p_cur_id;
+
+	/* the currently configured max periodic interval. */
+	guint concheck_p_max_interval;
+
+	/* the current interval. If we are probing, the interval might be lower
+	 * then the configured max interval. */
+	guint concheck_p_cur_interval;
+
+	/* the timestamp, when we last scheduled the timer concheck_p_cur_id with current interval
+	 * concheck_p_cur_interval. */
+	gint64 concheck_p_cur_basetime_ns;
+
 	NMConnectivityState connectivity_state;
-	guint concheck_periodic_id;
-	guint64 concheck_seq;
+
+	CList concheck_lst_head;
 
 	guint check_delete_unrealized_id;
 
@@ -500,35 +574,35 @@ typedef struct _NMDevicePrivate {
 
 } NMDevicePrivate;
 
-G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
 
 #define NM_DEVICE_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDevice, NM_IS_DEVICE)
 
 /*****************************************************************************/
 
+static const NMDBusInterfaceInfoExtended interface_info_device;
+static const GDBusSignalInfo signal_info_state_changed;
+
 static void nm_device_set_proxy_config (NMDevice *self, const char *pac_url);
 
-static gboolean update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolean intersect_configs);
+static gboolean update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_configs);
 
-static gboolean nm_device_set_ip4_config (NMDevice *self,
-                                          NMIP4Config *config,
-                                          gboolean commit,
-                                          GPtrArray *ip4_dev_route_blacklist);
-static gboolean ip4_config_merge_and_apply (NMDevice *self,
-                                            gboolean commit);
+static gboolean nm_device_set_ip_config (NMDevice *self,
+                                         int addr_family,
+                                         NMIPConfig *config,
+                                         gboolean commit,
+                                         GPtrArray *ip4_dev_route_blacklist);
 
-static gboolean nm_device_set_ip6_config (NMDevice *self,
-                                          NMIP6Config *config,
-                                          gboolean commit);
-static gboolean ip6_config_merge_and_apply (NMDevice *self,
-                                            gboolean commit);
+static gboolean ip_config_merge_and_apply (NMDevice *self,
+                                           int addr_family,
+                                           gboolean commit);
 
 static gboolean nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure);
 static void nm_device_slave_notify_enslave (NMDevice *self, gboolean success);
 static void nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason);
 
-static gboolean addrconf6_start_with_link_ready (NMDevice *self);
-static NMActStageReturn linklocal6_start (NMDevice *self);
+static void addrconf6_start_with_link_ready (NMDevice *self);
+static gboolean linklocal6_start (NMDevice *self);
 
 static void _carrier_wait_check_queued_act_request (NMDevice *self);
 static gint64 _get_carrier_wait_ms (NMDevice *self);
@@ -556,9 +630,10 @@ static void realize_start_setup (NMDevice *self,
                                  NMUnmanFlagOp unmanaged_user_explicit);
 static void _set_mtu (NMDevice *self, guint32 mtu);
 static void _commit_mtu (NMDevice *self, const NMIP4Config *config);
-static void dhcp_schedule_restart (NMDevice *self, int addr_family, const char *reason);
 static void _cancel_activation (NMDevice *self);
 
+static void concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean is_periodic);
+
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (queued_state_to_string, NMDeviceState,
@@ -683,6 +758,16 @@ nm_device_get_platform (NMDevice *self)
 	return nm_netns_get_platform (nm_device_get_netns (self));
 }
 
+static NMConnectivity *
+concheck_get_mgr (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (!priv->concheck_mgr))
+		priv->concheck_mgr = g_object_ref (nm_connectivity_get ());
+	return priv->concheck_mgr;
+}
+
 static NMIP4Config *
 _ip4_config_new (NMDevice *self)
 {
@@ -697,6 +782,105 @@ _ip6_config_new (NMDevice *self)
 	                          nm_device_get_ip_ifindex (self));
 }
 
+static NMIPConfig *
+_ip_config_new (NMDevice *self, int addr_family)
+{
+	nm_assert_addr_family (addr_family);
+
+	return addr_family == AF_INET
+	       ? (gpointer) _ip4_config_new (self)
+	       : (gpointer) _ip6_config_new (self);
+}
+
+static void
+applied_config_clear (AppliedConfig *config)
+{
+	g_clear_object (&config->current);
+	g_clear_object (&config->orig);
+}
+
+static void
+applied_config_init (AppliedConfig *config, gpointer ip_config)
+{
+	nm_g_object_ref (ip_config);
+	applied_config_clear (config);
+	config->orig = ip_config;
+}
+
+static void
+applied_config_init_new (AppliedConfig *config, NMDevice *self, int addr_family)
+{
+	gs_unref_object NMIPConfig *c = _ip_config_new (self, addr_family);
+
+	applied_config_init (config, c);
+}
+
+static NMIPConfig *
+applied_config_get_current (AppliedConfig *config)
+{
+	return config->current ?: config->orig;
+}
+
+static void
+applied_config_add_address (AppliedConfig *config, const NMPlatformIPAddress *address)
+{
+	if (config->orig)
+		nm_ip_config_add_address (config->orig, address);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_add_address (config->current, address);
+}
+
+static void
+applied_config_add_nameserver (AppliedConfig *config, const NMIPAddr *ns)
+{
+	if (config->orig)
+		nm_ip_config_add_nameserver (config->orig, ns);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_add_nameserver (config->current, ns);
+}
+
+static void
+applied_config_add_search (AppliedConfig *config, const char *new)
+{
+	if (config->orig)
+		nm_ip_config_add_search (config->orig, new);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_add_search (config->current, new);
+}
+
+static void
+applied_config_reset_searches (AppliedConfig *config)
+{
+	if (config->orig)
+		nm_ip_config_reset_searches (config->orig);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_reset_searches (config->current);
+}
+
+static void
+applied_config_reset_nameservers (AppliedConfig *config)
+{
+	if (config->orig)
+		nm_ip_config_reset_nameservers (config->orig);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_reset_nameservers (config->current);
+}
+
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_sys_iface_state_to_str, NMDeviceSysIfaceState,
@@ -826,29 +1010,18 @@ nm_device_assume_state_reset (NMDevice *self)
 /*****************************************************************************/
 
 static void
-init_ip4_config_dns_priority (NMDevice *self, NMIP4Config *config)
+init_ip_config_dns_priority (NMDevice *self, NMIPConfig *config)
 {
 	gs_free char *value = NULL;
 	gint priority;
 
 	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "ipv4.dns-priority",
+	                                               (nm_ip_config_get_addr_family (config) == AF_INET)
+	                                                 ? "ipv4.dns-priority"
+	                                                 : "ipv6.dns-priority",
 	                                               self);
 	priority = _nm_utils_ascii_str_to_int64 (value, 10, G_MININT, G_MAXINT, 0);
-	nm_ip4_config_set_dns_priority (config, priority ?: NM_DNS_PRIORITY_DEFAULT_NORMAL);
-}
-
-static void
-init_ip6_config_dns_priority (NMDevice *self, NMIP6Config *config)
-{
-	gs_free char *value = NULL;
-	gint priority;
-
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "ipv6.dns-priority",
-	                                               self);
-	priority = _nm_utils_ascii_str_to_int64 (value, 10, G_MININT, G_MAXINT, 0);
-	nm_ip6_config_set_dns_priority (config, priority ?: NM_DNS_PRIORITY_DEFAULT_NORMAL);
+	nm_ip_config_set_dns_priority (config, priority ?: NM_DNS_PRIORITY_DEFAULT_NORMAL);
 }
 
 /*****************************************************************************/
@@ -879,19 +1052,41 @@ nm_device_ipv4_sysctl_set (NMDevice *self, const char *property, const char *val
 }
 
 static guint32
-nm_device_ipv4_sysctl_get_uint32 (NMDevice *self, const char *property, guint32 fallback)
+nm_device_ipv4_sysctl_get_effective_uint32 (NMDevice *self, const char *property, guint32 fallback)
 {
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+	gint64 v, v_all;
 
 	if (!nm_device_get_ip_ifindex (self))
 		return fallback;
 
-	return nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
-	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, nm_device_get_ip_iface (self), property)),
-	                                           10,
-	                                           0,
-	                                           G_MAXUINT32,
-	                                           fallback);
+	/* for this kind of sysctl (e.g. "rp_filter"), kernel effectively uses the
+	 * MAX of the per-device value and the "all" value.
+	 *
+	 * Also do that, by reading both sysctls and return the maximum. */
+
+	v = nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
+	                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET,
+	                                                                                                  buf,
+	                                                                                                  nm_device_get_ip_iface (self),
+	                                                                                                  property)),
+	                                        10,
+	                                        0,
+	                                        G_MAXUINT32,
+	                                        -1);
+
+	v_all = nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
+	                                            NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET,
+	                                                                                                      buf,
+	                                                                                                      "all",
+	                                                                                                      property)),
+	                                            10,
+	                                            0,
+	                                            G_MAXUINT32,
+	                                            -1);
+
+	v = NM_MAX (v, v_all);
+	return v > -1 ? (guint32) v : fallback;
 }
 
 gboolean
@@ -1035,7 +1230,7 @@ _set_ip_state (NMDevice *self, int addr_family, IpState new_state)
 
 	p =   (addr_family == AF_INET)
 	    ? &priv->ip4_state_
-        : &priv->ip6_state_;
+	    : &priv->ip6_state_;
 
 	if (*p != new_state) {
 		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
@@ -1076,28 +1271,26 @@ nm_device_get_iface (NMDevice *self)
 }
 
 gboolean
-nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed)
+nm_device_take_over_link (NMDevice *self, int ifindex, char **old_name)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const NMPlatformLink *plink;
 	NMPlatform *platform;
 	gboolean up, success = TRUE;
-	int ifindex;
+	gs_free char *name = NULL;
 
 	g_return_val_if_fail (priv->ifindex <= 0, FALSE);
-	g_return_val_if_fail (ifname, FALSE);
 
-	NM_SET_OUT (renamed, FALSE);
+	NM_SET_OUT (old_name, NULL);
 
 	platform = nm_device_get_platform (self);
-	plink = nm_platform_link_get_by_ifname (platform, ifname);
+	plink = nm_platform_link_get (platform, ifindex);
 	if (!plink)
 		return FALSE;
 
-	ifindex = plink->ifindex;
-
-	if (!nm_streq (ifname, nm_device_get_iface (self))) {
+	if (!nm_streq (plink->name, nm_device_get_iface (self))) {
 		up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
+		name = g_strdup (plink->name);
 
 		/* Rename the link to the device ifname */
 		if (up)
@@ -1106,7 +1299,8 @@ nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed)
 		if (up)
 			nm_platform_link_set_up (platform, ifindex, NULL);
 
-		NM_SET_OUT (renamed, success);
+		if (success)
+			NM_SET_OUT (old_name, g_steal_pointer (&name));
 	}
 
 	if (success) {
@@ -1178,71 +1372,102 @@ nm_device_get_ip_ifindex (const NMDevice *self)
 	return priv->ip_iface ? priv->ip_ifindex : priv->ifindex;
 }
 
-/**
- * nm_device_set_ip_iface:
- * @self: the #NMDevice
- * @iface: the new IP interface name
- *
- * Updates the IP interface name and possibly the ifindex.
- *
- * Returns: %TRUE if the anything (name or ifindex) changed, %FALSE if nothing
- * changed.
- */
-gboolean
-nm_device_set_ip_iface (NMDevice *self, const char *iface)
+static void
+_set_ip_ifindex (NMDevice *self,
+                 int ifindex,
+                 const char *ifname)
 {
-	NMDevicePrivate *priv;
-	int ifindex;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMPlatform *platform;
+	gboolean eq_name;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	/* normalize arguments */
+	if (ifindex <= 0) {
+		ifindex = 0;
+		ifname = NULL;
+	}
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	if (nm_streq0 (iface, priv->ip_iface)) {
-		if (!iface)
-			return FALSE;
-		ifindex = nm_platform_if_nametoindex (nm_device_get_platform (self), iface);
-		if (   ifindex <= 0
-		    || priv->ip_ifindex == ifindex)
-			return FALSE;
+	eq_name = nm_streq0 (priv->ip_iface, ifname);
 
-		priv->ip_ifindex = ifindex;
-		_LOGD (LOGD_DEVICE, "ip-ifname: update ifindex for ifname '%s': %d", iface, priv->ip_ifindex);
-	} else {
+	if (   eq_name
+	    && priv->ip_ifindex == ifindex)
+		return;
+
+	_LOGD (LOGD_DEVICE, "ip-ifindex: update ip-interface to %s%s%s, ifindex %d",
+	       NM_PRINT_FMT_QUOTE_STRING (ifname),
+	       ifindex);
+
+	priv->ip_ifindex = ifindex;
+	if (!eq_name) {
 		g_free (priv->ip_iface);
-		priv->ip_iface = g_strdup (iface);
-
-		if (iface) {
-			/* The @iface name is not in sync with the platform cache.
-			 * So, there is no point asking the platform cache to resolve
-			 * the ifindex. Instead, we can only hope that the interface
-			 * with this name still exists and we resolve the ifindex
-			 * anew.
-			 */
-			priv->ip_ifindex = nm_platform_if_nametoindex (nm_device_get_platform (self), iface);
-			if (priv->ip_ifindex > 0)
-				_LOGD (LOGD_DEVICE, "ip-ifname: set ifname '%s', ifindex %d", iface, priv->ip_ifindex);
-			else
-				_LOGW (LOGD_DEVICE, "ip-ifname: set ifname '%s', unknown ifindex", iface);
-		} else {
-			priv->ip_ifindex = 0;
-			_LOGD (LOGD_DEVICE, "ip-ifname: clear ifname");
-		}
+		priv->ip_iface = g_strdup (ifname);
+		_notify (self, PROP_IP_IFACE);
 	}
 
 	if (priv->ip_ifindex > 0) {
-		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
+		platform = nm_device_get_platform (self);
+
+		nm_platform_process_events_ensure_link (platform,
+		                                        priv->ip_ifindex,
+		                                        priv->ip_iface);
+
+		if (nm_platform_check_kernel_support (platform,
 		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
-			nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ip_ifindex, TRUE);
+			nm_platform_link_set_user_ipv6ll_enabled (platform, priv->ip_ifindex, TRUE);
 
-		if (!nm_platform_link_is_up (nm_device_get_platform (self), priv->ip_ifindex))
-			nm_platform_link_set_up (nm_device_get_platform (self), priv->ip_ifindex, NULL);
+		if (!nm_platform_link_is_up (platform, priv->ip_ifindex))
+			nm_platform_link_set_up (platform, priv->ip_ifindex, NULL);
 	}
 
 	/* We don't care about any saved values from the old iface */
 	g_hash_table_remove_all (priv->ip6_saved_properties);
+}
 
-	_notify (self, PROP_IP_IFACE);
-	return TRUE;
+gboolean
+nm_device_set_ip_ifindex (NMDevice *self, int ifindex)
+{
+	char ifname_buf[IFNAMSIZ];
+	const char *ifname = NULL;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (nm_device_is_activating (self), FALSE);
+
+	if (ifindex > 0) {
+		ifname = nm_platform_if_indextoname (nm_device_get_platform (self), ifindex, ifname_buf);
+		if (!ifname)
+			_LOGW (LOGD_DEVICE, "ip-ifindex: ifindex %d not found", ifindex);
+	}
+
+	_set_ip_ifindex (self, ifindex, ifname);
+	return ifindex > 0;
+}
+
+/**
+ * nm_device_set_ip_iface:
+ * @self: the #NMDevice
+ * @ifname: the new IP interface name
+ *
+ * Updates the IP interface name and possibly the ifindex.
+ *
+ * Returns: %TRUE if an interface with name @ifname exists,
+ *   and %FALSE, if @ifname is %NULL or no such interface exists.
+ */
+gboolean
+nm_device_set_ip_iface (NMDevice *self, const char *ifname)
+{
+	int ifindex = 0;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (nm_device_is_activating (self), FALSE);
+
+	if (ifname) {
+		ifindex = nm_platform_if_nametoindex (nm_device_get_platform (self), ifname);
+		if (ifindex <= 0)
+			_LOGW (LOGD_DEVICE, "ip-ifindex: ifname %s not found", ifname);
+	}
+
+	_set_ip_ifindex (self, ifindex, ifname);
+	return ifindex > 0;
 }
 
 static gboolean
@@ -1289,12 +1514,9 @@ nm_device_parent_get_ifindex (NMDevice *self)
 NMDevice *
 nm_device_parent_get_device (NMDevice *self)
 {
-	NMDevicePrivate *priv;
-
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	return priv->parent_device;
+	return NM_DEVICE_GET_PRIVATE (self)->parent_device.obj;
 }
 
 static void
@@ -1316,7 +1538,7 @@ _parent_set_ifindex (NMDevice *self,
 	NMDevice *parent_device;
 	gboolean changed = FALSE;
 	int old_ifindex;
-	NMDevice *old_device;
+	gs_unref_object NMDevice *old_device = NULL;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
@@ -1326,16 +1548,15 @@ _parent_set_ifindex (NMDevice *self,
 		parent_ifindex = 0;
 
 	old_ifindex = priv->parent_ifindex;
-	old_device = priv->parent_device;
 
 	if (priv->parent_ifindex == parent_ifindex) {
 		if (parent_ifindex > 0) {
 			if (   !force_check
-			    && priv->parent_device
-			    && nm_device_get_ifindex (priv->parent_device) == parent_ifindex)
+			    && priv->parent_device.obj
+			    && nm_device_get_ifindex (priv->parent_device.obj) == parent_ifindex)
 				return FALSE;
 		} else {
-			if (!priv->parent_device)
+			if (!priv->parent_device.obj)
 				return FALSE;
 		}
 	} else {
@@ -1350,24 +1571,23 @@ _parent_set_ifindex (NMDevice *self,
 	} else
 		parent_device = NULL;
 
-	if (parent_device != priv->parent_device) {
-		priv->parent_device = parent_device;
+	if (parent_device != priv->parent_device.obj) {
+		old_device = nm_g_object_ref (priv->parent_device.obj);
+		nm_dbus_track_obj_path_set (&priv->parent_device, parent_device, TRUE);
 		changed = TRUE;
 	}
 
 	if (changed) {
 		if (priv->parent_ifindex <= 0)
 			_LOGD (LOGD_DEVICE, "parent: clear");
-		else if (!priv->parent_device)
+		else if (!priv->parent_device.obj)
 			_LOGD (LOGD_DEVICE, "parent: ifindex %d, no device", priv->parent_ifindex);
 		else {
 			_LOGD (LOGD_DEVICE, "parent: ifindex %d, device %p, %s", priv->parent_ifindex,
-			       priv->parent_device, nm_device_get_iface (priv->parent_device));
+			       priv->parent_device.obj, nm_device_get_iface (priv->parent_device.obj));
 		}
 
-		NM_DEVICE_GET_CLASS (self)->parent_changed_notify (self, old_ifindex, old_device, priv->parent_ifindex, priv->parent_device);
-
-		_notify (self, PROP_PARENT);
+		NM_DEVICE_GET_CLASS (self)->parent_changed_notify (self, old_ifindex, old_device, priv->parent_ifindex, priv->parent_device.obj);
 	}
 	return changed;
 }
@@ -1392,7 +1612,7 @@ nm_device_parent_notify_changed (NMDevice *self,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->parent_ifindex > 0) {
-		if (   priv->parent_device == change_candidate
+		if (   priv->parent_device.obj == change_candidate
 		    || priv->parent_ifindex == nm_device_get_ifindex (change_candidate))
 			return _parent_set_ifindex (self, priv->parent_ifindex, device_removed);
 	}
@@ -1698,16 +1918,13 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 static gboolean
 default_route_metric_penalty_detect (NMDevice *self)
 {
-#if WITH_CONCHECK
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* currently we don't differentiate between IPv4 and IPv6 when detecting
 	 * connectivity. */
 	if (   priv->connectivity_state != NM_CONNECTIVITY_FULL
-		&& nm_connectivity_check_enabled (nm_connectivity_get ())) {
+	    && nm_connectivity_check_enabled (concheck_get_mgr (self)))
 		return TRUE;
-	}
-#endif
 
 	return FALSE;
 }
@@ -1775,6 +1992,34 @@ out:
 	return nm_utils_ip_route_metric_normalize (addr_family, route_metric);
 }
 
+static NMSettingConnectionMdns
+_get_mdns (NMDevice *self)
+{
+	NMConnection *connection;
+	NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_SETTING_CONNECTION_MDNS_DEFAULT);
+
+	connection = nm_device_get_applied_connection (self);
+	if (connection)
+		mdns = nm_setting_connection_get_mdns (nm_connection_get_setting_connection (connection));
+
+	if (mdns == NM_SETTING_CONNECTION_MDNS_DEFAULT) {
+		gs_free char *value = NULL;
+
+		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                               "connection.mdns",
+		                                               self);
+		mdns = _nm_utils_ascii_str_to_int64 (value,
+		                                     10,
+		                                     NM_SETTING_CONNECTION_MDNS_NO,
+		                                     NM_SETTING_CONNECTION_MDNS_YES,
+		                                     NM_SETTING_CONNECTION_MDNS_DEFAULT);
+	}
+
+	return mdns;
+}
+
 guint32
 nm_device_get_route_table (NMDevice *self,
                            int addr_family,
@@ -1851,12 +2096,12 @@ nm_device_get_best_default_route (NMDevice *self,
 
 	switch (addr_family) {
 	case AF_INET:
-		return priv->ip4_config ? nm_ip4_config_best_default_route_get (priv->ip4_config) : NULL;
+		return priv->ip_config_4 ? nm_ip4_config_best_default_route_get (priv->ip_config_4) : NULL;
 	case AF_INET6:
-		return priv->ip6_config ? nm_ip6_config_best_default_route_get (priv->ip6_config) : NULL;
+		return priv->ip_config_6 ? nm_ip6_config_best_default_route_get (priv->ip_config_6) : NULL;
 	case AF_UNSPEC:
-		return    (priv->ip4_config ? nm_ip4_config_best_default_route_get (priv->ip4_config) : NULL)
-		       ?: (priv->ip6_config ? nm_ip6_config_best_default_route_get (priv->ip6_config) : NULL);
+		return    (priv->ip_config_4 ? nm_ip4_config_best_default_route_get (priv->ip_config_4) : NULL)
+		       ?: (priv->ip_config_6 ? nm_ip6_config_best_default_route_get (priv->ip_config_6) : NULL);
 	default:
 		g_return_val_if_reached (NULL);
 	}
@@ -1884,18 +2129,23 @@ nm_device_get_type_description (NMDevice *self)
 static const char *
 get_type_description (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceClass *klass;
 
-	if (!priv->type_description) {
+	nm_assert (NM_IS_DEVICE (self));
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (G_UNLIKELY (!klass->default_type_description)) {
 		const char *typename;
+		gs_free char *s = NULL;
 
 		typename = G_OBJECT_TYPE_NAME (self);
 		if (g_str_has_prefix (typename, "NMDevice"))
 			typename += 8;
-		priv->type_description = g_ascii_strdown (typename, -1);
+		s = g_ascii_strdown (typename, -1);
+		klass->default_type_description = g_intern_string (s);
 	}
 
-	return priv->type_description;
+	return klass->default_type_description;
 }
 
 gboolean
@@ -1909,7 +2159,7 @@ nm_device_get_act_request (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->act_request;
+	return NM_DEVICE_GET_PRIVATE (self)->act_request.obj;
 }
 
 NMSettingsConnection *
@@ -1917,7 +2167,7 @@ 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;
+	return priv->act_request.obj ? nm_act_request_get_settings_connection (priv->act_request.obj) : NULL;
 }
 
 NMConnection *
@@ -1929,7 +2179,7 @@ nm_device_get_applied_connection (NMDevice *self)
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	return priv->act_request ? nm_act_request_get_applied_connection (priv->act_request) : NULL;
+	return priv->act_request.obj ? nm_act_request_get_applied_connection (priv->act_request.obj) : NULL;
 }
 
 gboolean
@@ -1937,10 +2187,10 @@ nm_device_has_unmodified_applied_connection (NMDevice *self, NMSettingCompareFla
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (!priv->act_request)
+	if (!priv->act_request.obj)
 		return FALSE;
 
-	return nm_active_connection_has_unmodified_applied_connection ((NMActiveConnection *) priv->act_request, compare_flags);
+	return nm_active_connection_has_unmodified_applied_connection ((NMActiveConnection *) priv->act_request.obj, compare_flags);
 }
 
 NMSetting *
@@ -1968,173 +2218,557 @@ nm_device_get_physical_port_id (NMDevice *self)
 
 /*****************************************************************************/
 
+typedef enum {
+	CONCHECK_SCHEDULE_UPDATE_INTERVAL,
+	CONCHECK_SCHEDULE_CHECK_EXTERNAL,
+	CONCHECK_SCHEDULE_CHECK_PERIODIC,
+	CONCHECK_SCHEDULE_RETURNED_MIN,
+	CONCHECK_SCHEDULE_RETURNED_BUMP,
+	CONCHECK_SCHEDULE_RETURNED_MAX,
+} ConcheckScheduleMode;
+
+static NMDeviceConnectivityHandle *concheck_start (NMDevice *self,
+                                                   NMDeviceConnectivityCallback callback,
+                                                   gpointer user_data,
+                                                   gboolean is_periodic);
+
+static void concheck_periodic_schedule_set (NMDevice *self,
+                                            ConcheckScheduleMode mode);
+
+static gboolean
+concheck_periodic_timeout_cb (gpointer user_data)
+{
+	NMDevice *self = user_data;
+
+	_LOGt (LOGD_CONCHECK, "connectivity: periodic timeout");
+	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_PERIODIC);
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+concheck_is_possible (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (   !nm_device_is_real (self)
+	    || NM_FLAGS_HAS (priv->unmanaged_flags, NM_UNMANAGED_LOOPBACK))
+		return FALSE;
+
+	/* we enable periodic checks for every device state (except UNKNOWN). Especially with
+	 * unmanaged devices, it is interesting to know whether we have connectivity on that device. */
+	if (priv->state == NM_DEVICE_STATE_UNKNOWN)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+concheck_periodic_schedule_do (NMDevice *self, gint64 interval_ns)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean periodic_check_disabled = FALSE;
+
+	/* we always cancel whatever was pending. */
+	if (nm_clear_g_source (&priv->concheck_p_cur_id))
+		periodic_check_disabled = TRUE;
+
+	if (priv->concheck_p_max_interval == 0) {
+		/* periodic checks are disabled */
+		goto out;
+	}
+
+	nm_assert (interval_ns >= 0);
+
+	if (!concheck_is_possible (self))
+		goto out;
+
+	_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: %sscheduled in %u milliseconds (%u seconds interval)",
+	       periodic_check_disabled ? "re-" : "",
+	       (guint) (interval_ns / NM_UTILS_NS_PER_MSEC),
+	       priv->concheck_p_cur_interval);
+
+	nm_assert (priv->concheck_p_cur_interval > 0);
+	priv->concheck_p_cur_id = g_timeout_add (interval_ns / NM_UTILS_NS_PER_MSEC,
+	                                         concheck_periodic_timeout_cb,
+	                                         self);
+	return TRUE;
+out:
+	if (periodic_check_disabled)
+		_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: unscheduled");
+	return FALSE;
+}
+
+#define CONCHECK_P_PROBE_INTERVAL 1
+
 static void
-update_connectivity_state (NMDevice *self, NMConnectivityState state)
+concheck_periodic_schedule_set (NMDevice *self,
+                                ConcheckScheduleMode mode)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gint64 new_expiry, exp_expiry, cur_expiry, tdiff;
+	gint64 now_ns = 0;
 
-	/* If the connectivity check is disabled, make an optimistic guess. */
-	if (state == NM_CONNECTIVITY_UNKNOWN) {
-		if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
-			if (nm_device_get_best_default_route (self, AF_UNSPEC))
-				state = NM_CONNECTIVITY_FULL;
-			else
-				state = NM_CONNECTIVITY_LIMITED;
-		} else {
-			state = NM_CONNECTIVITY_NONE;
+	if (priv->concheck_p_max_interval == 0) {
+		/* periodic check is disabled. Nothing to do. */
+		return;
+	}
+
+	if (!priv->concheck_p_cur_id) {
+		/* we currently don't have a timeout scheduled. No need to reschedule
+		 * another one... */
+		if (mode == CONCHECK_SCHEDULE_UPDATE_INTERVAL) {
+			/* ... unless, we are initalizing. In this case, setup the current current
+			 * interval and schedule a perform a check right away.  */
+			priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_max_interval, CONCHECK_P_PROBE_INTERVAL);
+			priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+			if (concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND))
+				concheck_start (self, NULL, NULL, TRUE);
 		}
+		return;
 	}
 
-	if (priv->connectivity_state != state) {
-#if WITH_CONCHECK
-		_LOGD (LOGD_CONCHECK, "state changed from %s to %s",
-		       nm_connectivity_state_to_string (priv->connectivity_state),
-		       nm_connectivity_state_to_string (state));
-#endif
-		priv->connectivity_state = state;
-		_notify (self, PROP_CONNECTIVITY);
+	switch (mode) {
+	case CONCHECK_SCHEDULE_UPDATE_INTERVAL:
+		/* called with "UPDATE_INTERVAL" and already have a concheck_p_cur_id scheduled. */
 
-		if (   priv->state == NM_DEVICE_STATE_ACTIVATED
-		    && !nm_device_sys_iface_state_is_external (self)) {
-			if (   nm_device_get_best_default_route (self, AF_INET)
-			    && !ip4_config_merge_and_apply (self, TRUE))
-				_LOGW (LOGD_IP4, "Failed to update IPv4 route metric");
-			if (   nm_device_get_best_default_route (self, AF_INET6)
-			    && !ip6_config_merge_and_apply (self, TRUE))
-				_LOGW (LOGD_IP6, "Failed to update IPv6 route metric");
+		nm_assert (priv->concheck_p_max_interval > 0);
+		nm_assert (priv->concheck_p_cur_interval > 0);
+
+		if (priv->concheck_p_cur_interval <= priv->concheck_p_max_interval) {
+			/* we currently have a shorter interval set, than what we now have. Either,
+			 * because we are probing, or because the previous max interval was shorter.
+			 *
+			 * Either way, the current timer is set just fine. Nothing to do, we will
+			 * probe our way up. */
+			return;
 		}
+
+		cur_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_max_interval * NM_UTILS_NS_PER_SECOND);
+		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+
+		priv->concheck_p_cur_interval = priv->concheck_p_max_interval;
+		if (cur_expiry <= now_ns) {
+			/* Since the last time we scheduled a periodic check, already more than the
+			 * new max_interval passed. We need to start a check right away (and
+			 * schedule a timeout in cur-interval in the future). */
+			priv->concheck_p_cur_basetime_ns = now_ns;
+			if (concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND))
+				concheck_start (self, NULL, NULL, TRUE);
+		} else {
+			/* we are reducing the max-interval to a shorter interval that we have currently
+			 * scheduled (with cur_interval).
+			 *
+			 * However, since the last time we scheduled the check, not even the new max-interval
+			 * expired. All we need to do, is reschedule the timer to expire sooner. The cur_basetime
+			 * is unchanged. */
+			concheck_periodic_schedule_do (self, cur_expiry - now_ns);
+		}
+		return;
+
+	case CONCHECK_SCHEDULE_CHECK_EXTERNAL:
+		/* a external connectivity check delays our periodic check. We reset the counter. */
+		priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		return;
+
+	case CONCHECK_SCHEDULE_CHECK_PERIODIC:
+	{
+		gboolean any_periodic_pending;
+		NMDeviceConnectivityHandle *handle;
+		guint old_interval = priv->concheck_p_cur_interval;
+
+		any_periodic_pending = FALSE;
+		c_list_for_each_entry (handle, &priv->concheck_lst_head, concheck_lst) {
+			if (handle->is_periodic_bump) {
+				handle->is_periodic_bump = FALSE;
+				handle->is_periodic_bump_on_complete = FALSE;
+				any_periodic_pending = TRUE;
+			}
+		}
+		if (any_periodic_pending) {
+			/* we reached a timeout to schedule a new periodic request, however we still
+			 * have period requests pending that didn't complete yet. We need to bump the
+			 * interval already. */
+			priv->concheck_p_cur_interval = NM_MIN (old_interval * 2, priv->concheck_p_max_interval);
+		}
+
+		/* we just reached a timeout. The expected expiry (exp_expiry) should be
+		 * pretty close to now_ns.
+		 *
+		 * We want to reschedule the timeout at exp_expiry (aka now) + cur_interval. */
+		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		exp_expiry = priv->concheck_p_cur_basetime_ns + (old_interval * NM_UTILS_NS_PER_SECOND);
+		new_expiry = exp_expiry + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		tdiff = NM_MAX (new_expiry - now_ns, 0);
+		priv->concheck_p_cur_basetime_ns = (now_ns + tdiff) - (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		concheck_periodic_schedule_do (self, tdiff);
+		handle = concheck_start (self, NULL, NULL, TRUE);
+		if (old_interval != priv->concheck_p_cur_interval) {
+			/* we just bumped the interval already when scheduling this check.
+			 * When the handle returns, don't bump a second time.
+			 *
+			 * But if we reach the timeout again before the handle returns (this
+			 * code here) we will still bump the interval. */
+			handle->is_periodic_bump_on_complete = FALSE;
+		}
+		return;
 	}
-}
 
-typedef struct {
-	NMDevice *self;
-	NMDeviceConnectivityCallback callback;
-	gpointer user_data;
-	guint64 seq;
-} ConnectivityCheckData;
+	/* we just got an event that we lost connectivity (that is, concheck returned). We reset
+	 * the interval to min/max or increase the probe interval (bump). */
+	case CONCHECK_SCHEDULE_RETURNED_MIN:
+		priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_max_interval, CONCHECK_P_PROBE_INTERVAL);
+		break;
+	case CONCHECK_SCHEDULE_RETURNED_MAX:
+		priv->concheck_p_cur_interval = priv->concheck_p_max_interval;
+		break;
+	case CONCHECK_SCHEDULE_RETURNED_BUMP:
+		priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_cur_interval * 2, priv->concheck_p_max_interval);
+		break;
+	}
 
-static void
-concheck_done (ConnectivityCheckData *data)
+	/* we are here, because we returned from a connectivity check and adjust the current interval.
+	 *
+	 * But note that we calculate the new timeout based on the time when we scheduled the
+	 * last check, instead of counting from now. The reaons is, that we want that the times
+	 * when we schedule checks be at precise intervals, without including the time it took for
+	 * the connectivity check. */
+	new_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	tdiff = NM_MAX (new_expiry - nm_utils_get_monotonic_timestamp_ns_cached (&now_ns), 0);
+	priv->concheck_p_cur_basetime_ns = now_ns + tdiff - (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	concheck_periodic_schedule_do (self, tdiff);
+}
+
+void
+nm_device_check_connectivity_update_interval (NMDevice *self)
 {
-	NMDevice *self = data->self;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	guint new_interval;
+
+	new_interval = nm_connectivity_get_interval (concheck_get_mgr (self));
+
+	new_interval = NM_MIN (new_interval, 7 *24 * 3600);
+
+	if (new_interval != priv->concheck_p_max_interval) {
+		_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: set interval to %u seconds", new_interval);
+		priv->concheck_p_max_interval = new_interval;
+	}
+
+	if (!new_interval) {
+		/* this will cancel any potentially pending timeout. */
+		concheck_periodic_schedule_do (self, 0);
+
+		/* also update the fake connectivity state. */
+		concheck_update_state (self, NM_CONNECTIVITY_FAKE, TRUE);
+		return;
+	}
 
-	/* The unsolicited connectivity checks don't hook a callback. */
-	if (data->callback)
-		data->callback (data->self, priv->connectivity_state, data->user_data);
-	g_object_unref (data->self);
-	g_slice_free (ConnectivityCheckData, data);
+	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_UPDATE_INTERVAL);
 }
 
-#if WITH_CONCHECK
 static void
-concheck_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
+concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow_periodic_bump)
 {
-	ConnectivityCheckData *data = user_data;
-	NMDevice *self = data->self;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnectivity *connectivity = NM_CONNECTIVITY (source_object);
-	NMConnectivityState state;
-	GError *error = NULL;
 
-	state = nm_connectivity_check_finish (connectivity, result, &error);
-	if (error) {
-		_LOGW (LOGD_DEVICE, "connectivity checking on '%s' failed: %s",
-		       nm_device_get_iface (self), error->message);
-		g_error_free (error);
+	/* @state is a result of the connectivity check. We only expect a precise
+	 * number of possible values. */
+	nm_assert (NM_IN_SET (state, NM_CONNECTIVITY_LIMITED,
+	                             NM_CONNECTIVITY_PORTAL,
+	                             NM_CONNECTIVITY_FULL,
+	                             NM_CONNECTIVITY_FAKE,
+	                             NM_CONNECTIVITY_ERROR));
+
+	if (state == NM_CONNECTIVITY_ERROR) {
+		/* on error, we don't change the current connectivity state,
+		 * except making UNKNOWN to NONE. */
+		state = priv->connectivity_state;
+		if (state == NM_CONNECTIVITY_UNKNOWN)
+			state = NM_CONNECTIVITY_NONE;
+	} else if (state == NM_CONNECTIVITY_FAKE) {
+		/* If the connectivity check is disabled and we obtain a fake
+		 * result, make an optimistic guess. */
+		if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
+			/* FIXME: the fake connectivity state depends on the availablility of
+			 * a default route. However, we have no mechanism that rechecks the
+			 * value if a device route appears/disappears after the device
+			 * was activated. */
+			if (nm_device_get_best_default_route (self, AF_UNSPEC))
+				state = NM_CONNECTIVITY_FULL;
+			else
+				state = NM_CONNECTIVITY_LIMITED;
+		} else
+			state = NM_CONNECTIVITY_NONE;
+	}
+
+	if (priv->connectivity_state == state) {
+		/* we got a connectivty update, but the state didn't change. If we were probing,
+		 * we bump the probe frequency. */
+		if (allow_periodic_bump)
+			concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP);
+		return;
+	}
+	/* we need to update the probe interval before emitting signals. Emitting
+	 * a signal might call back into NMDevice and change the probe settings.
+	 * So, do that first. */
+	if (state == NM_CONNECTIVITY_FULL) {
+		/* we reached full connectivity state. Stop probing by setting the
+		 * interval to the max. */
+		concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_MAX);
+	} else if (priv->connectivity_state == NM_CONNECTIVITY_FULL) {
+		/* we are about to loose connectivity. (re)start probing by setting
+		 * the timeout interval to the min. */
+		concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_MIN);
+	} else {
+		if (allow_periodic_bump)
+			concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP);
 	}
 
-	if (data->seq == priv->concheck_seq)
-		update_connectivity_state (data->self, state);
-	concheck_done (data);
+	_LOGD (LOGD_CONCHECK, "connectivity state changed from %s to %s",
+	       nm_connectivity_state_to_string (priv->connectivity_state),
+	       nm_connectivity_state_to_string (state));
+	priv->connectivity_state = state;
+
+	_notify (self, PROP_CONNECTIVITY);
+	g_signal_emit (self, signals[CONNECTIVITY_CHANGED], 0);
+
+	if (   priv->state == NM_DEVICE_STATE_ACTIVATED
+	    && !nm_device_sys_iface_state_is_external (self)) {
+		if (   nm_device_get_best_default_route (self, AF_INET)
+		    && !ip_config_merge_and_apply (self, AF_INET, TRUE))
+			_LOGW (LOGD_IP4, "Failed to update IPv4 route metric");
+		if (   nm_device_get_best_default_route (self, AF_INET6)
+		    && !ip_config_merge_and_apply (self, AF_INET6, TRUE))
+			_LOGW (LOGD_IP6, "Failed to update IPv6 route metric");
+	}
 }
-#endif /* WITH_CONCHECK */
 
-static gboolean
-no_concheck (gpointer user_data)
+static void
+concheck_handle_complete (NMDeviceConnectivityHandle *handle,
+                          GError *error)
 {
-	ConnectivityCheckData *data = user_data;
+	/* The moment we invoke the callback, we unlink it. It signals
+	 * that @handle is handled -- as far as the callee of callback
+	 * is concerned. */
+	c_list_unlink (&handle->concheck_lst);
 
-	concheck_done (data);
-	return G_SOURCE_REMOVE;
+	if (handle->c_handle)
+		nm_connectivity_check_cancel (handle->c_handle);
+
+	if (handle->callback) {
+		handle->callback (handle->self,
+		                  handle,
+		                  NM_DEVICE_GET_PRIVATE (handle->self)->connectivity_state,
+		                  error,
+		                  handle->user_data);
+	}
+
+	g_slice_free (NMDeviceConnectivityHandle, handle);
 }
 
-void
-nm_device_check_connectivity (NMDevice *self,
-                              NMDeviceConnectivityCallback callback,
-                              gpointer user_data)
+static void
+concheck_cb (NMConnectivity *connectivity,
+             NMConnectivityCheckHandle *c_handle,
+             NMConnectivityState state,
+             GError *error,
+             gpointer user_data)
 {
-	ConnectivityCheckData *data;
-#if WITH_CONCHECK
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-#endif
+	_nm_unused gs_unref_object NMDevice *self_keep_alive = NULL;
+	NMDevice *self;
+	NMDevicePrivate *priv;
+	NMDeviceConnectivityHandle *handle;
+	NMDeviceConnectivityHandle *other_handle;
+	gboolean handle_is_alive;
+	gboolean allow_periodic_bump;
+	gboolean any_periodic_before;
+	gboolean any_periodic_after;
+	guint64 seq;
 
-	data = g_slice_new0 (ConnectivityCheckData);
-	data->self = g_object_ref (self);
-	data->callback = callback;
-	data->user_data = user_data;
+	handle = user_data;
+	nm_assert (handle->c_handle == c_handle);
+	nm_assert (NM_IS_DEVICE (handle->self));
 
-#if WITH_CONCHECK
-	if (priv->concheck_periodic_id) {
-		data->seq = ++priv->concheck_seq;
+	handle->c_handle = NULL;
+	self = handle->self;
 
-		/* Kick off a real connectivity check. */
-		nm_connectivity_check_async (nm_connectivity_get (),
-		                             nm_device_get_ip_iface (self),
-		                             concheck_cb,
-		                             data);
+	if (nm_utils_error_is_cancelled (error, FALSE)) {
+		/* the only place where we nm_connectivity_check_cancel(@c_handle), is
+		 * from inside concheck_handle_complete(). This is a recursive call,
+		 * nothing to do. */
+		_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, cancelled)",
+		       (long long unsigned) handle->seq);
 		return;
 	}
-#endif
 
-	/* Fake one. */
-	g_idle_add (no_concheck, data);
+	self_keep_alive = g_object_ref (self);
+
+	_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, state:%s%s%s%s)",
+	       (long long unsigned) handle->seq,
+	       nm_connectivity_state_to_string (state),
+	       NM_PRINT_FMT_QUOTED (error, ", error: ", error->message, "", ""));
+
+	/* we keep NMConnectivity instance alive. It cannot be disposing. */
+	nm_assert (!nm_utils_error_is_cancelled (error, TRUE));
+
+	/* keep @self alive, while we invoke callbacks. */
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert (!handle || c_list_contains (&priv->concheck_lst_head, &handle->concheck_lst));
+
+	seq = handle->seq;
+
+	/* find out, if there are any periodic checks pending (either whether they
+	 * were scheduled before or after @handle. */
+	any_periodic_before = FALSE;
+	any_periodic_after = FALSE;
+	c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
+		if (other_handle->is_periodic_bump_on_complete) {
+			if (other_handle->seq < seq)
+				any_periodic_before = TRUE;
+			else if (other_handle->seq > seq)
+				any_periodic_after = TRUE;
+		}
+	}
+	if (NM_IN_SET (state, NM_CONNECTIVITY_ERROR)) {
+		/* the request failed. We consider this periodic check only as completed if
+		 * this was a periodic check, and there are not checks pending (either
+		 * before or after this one).
+		 *
+		 * We allow_periodic_bump, if the request failed and there are
+		 * still other requests periodic pending. */
+		allow_periodic_bump =    handle->is_periodic_bump_on_complete
+		                      && !any_periodic_before
+		                      && !any_periodic_after;
+	} else {
+		/* the request succeeded. This marks the completion of a periodic check,
+		 * if this handle was periodic, or any previously scheduled one (that
+		 * we are going to complete below). */
+		allow_periodic_bump =    handle->is_periodic_bump_on_complete
+		                      || any_periodic_before;
+	}
+
+	/* first update the new state, and emit signals. */
+	concheck_update_state (self, state, allow_periodic_bump);
+
+	handle_is_alive = FALSE;
+
+	/* we might have invoked callbacks during concheck_update_state(). The caller might have
+	 * cancelled and thus destroyed @handle. We have to check whether handle is still alive,
+	 * by searching it in the list of alive handles.
+	 *
+	 * Also, we might want to complete all pending callbacks that were started before
+	 * @handle, as they are automatically obsoleted. */
+check_handles:
+	c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
+		if (other_handle->seq >= seq) {
+			/* it's not guaranteed that @handle is still in the list. It might already
+			 * be canceled while invoking callbacks for a previous other_handle.
+			 * If it is already cancelled, @handle is a dangling pointer.
+			 *
+			 * Since @seq is assigned uniquely and increasing, either @other_handle is
+			 * @handle (and thus, handle is alive), or it isn't. */
+			if (other_handle == handle)
+				handle_is_alive = TRUE;
+			break;
+		}
+
+		nm_assert (other_handle != handle);
+
+		if (!NM_IN_SET (state, NM_CONNECTIVITY_ERROR)) {
+			/* we also want to complete handles that were started before the current
+			 * @handle. Their response is out-dated. */
+			concheck_handle_complete (other_handle, NULL);
+
+			/* we invoked callbacks, other handles might be cancelled and removed from the list.
+			 * Need to iterate the list from the start. */
+			goto check_handles;
+		}
+	}
+
+	if (!handle_is_alive) {
+		/* We didn't find @handle in the list of alive handles. Thus, the handles
+		 * was cancelled while we were invoking events. Nothing to do, and don't
+		 * touch the dangling pointer. */
+		return;
+	}
+
+	concheck_handle_complete (handle, NULL);
 }
 
-NMConnectivityState
-nm_device_get_connectivity_state (NMDevice *self)
+static NMDeviceConnectivityHandle *
+concheck_start (NMDevice *self,
+                NMDeviceConnectivityCallback callback,
+                gpointer user_data,
+                gboolean is_periodic)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (self), NM_CONNECTIVITY_UNKNOWN);
+	static guint64 seq_counter = 0;
+	NMDevicePrivate *priv;
+	NMDeviceConnectivityHandle *handle;
 
-	return NM_DEVICE_GET_PRIVATE (self)->connectivity_state;
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	handle = g_slice_new0 (NMDeviceConnectivityHandle);
+	handle->seq = ++seq_counter;
+	handle->self = self;
+	handle->callback = callback;
+	handle->user_data = user_data;
+	handle->is_periodic = is_periodic;
+	handle->is_periodic_bump = is_periodic;
+	handle->is_periodic_bump_on_complete = is_periodic;
+
+	c_list_link_tail (&priv->concheck_lst_head, &handle->concheck_lst);
+
+	_LOGT (LOGD_CONCHECK, "connectivity: start check (seq:%llu%s)",
+	       (long long unsigned) handle->seq,
+	       is_periodic ? ", periodic-check" : "");
+
+	handle->c_handle = nm_connectivity_check_start (concheck_get_mgr (self),
+	                                                nm_device_get_ip_iface (self),
+	                                                concheck_cb,
+	                                                handle);
+	return handle;
 }
 
-#if WITH_CONCHECK
-static void
-concheck_periodic (NMConnectivity *connectivity, NMDevice *self)
+NMDeviceConnectivityHandle *
+nm_device_check_connectivity (NMDevice *self,
+                              NMDeviceConnectivityCallback callback,
+                              gpointer user_data)
 {
-	nm_device_check_connectivity (self, NULL, NULL);
+	NMDeviceConnectivityHandle *handle;
+
+	if (!concheck_is_possible (self))
+		return NULL;
+
+	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_EXTERNAL);
+	handle = concheck_start (self, callback, user_data, FALSE);
+	return handle;
 }
-#endif
 
-static void
-concheck_periodic_update (NMDevice *self)
+void
+nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle)
 {
-#if WITH_CONCHECK
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean check_enable;
+	gs_free_error GError *cancelled_error = NULL;
 
-	check_enable =    (priv->state == NM_DEVICE_STATE_ACTIVATED)
-	               && nm_device_get_best_default_route (self, AF_UNSPEC);
+	g_return_if_fail (handle);
+	g_return_if_fail (NM_IS_DEVICE (handle->self));
+	g_return_if_fail (!c_list_is_empty (&handle->concheck_lst));
 
-	if (check_enable && !priv->concheck_periodic_id) {
-		/* We just gained a default route. Enable periodic checking. */
-		priv->concheck_periodic_id = g_signal_connect (nm_connectivity_get (),
-		                                               NM_CONNECTIVITY_PERIODIC_CHECK,
-		                                               G_CALLBACK (concheck_periodic), self);
-		/* Also kick off a check right away. */
-		nm_device_check_connectivity (self, NULL, NULL);
-	} else if (!check_enable && priv->concheck_periodic_id) {
-		/* The default route has gone off, and so has connectivity. */
-		g_signal_handler_disconnect (nm_connectivity_get (), priv->concheck_periodic_id);
-		priv->concheck_periodic_id = 0;
-		update_connectivity_state (self, NM_CONNECTIVITY_NONE);
-	}
-#else
-	/* update_connectivity_state() figures out how to lie about
-	 * connectivity state if the actual state is not really known. */
-	update_connectivity_state (self, NM_CONNECTIVITY_UNKNOWN);
-#endif
+	/* nobody has access to periodic handles, and cannot cancel
+	 * them externally. */
+	nm_assert (!handle->is_periodic);
+
+	nm_utils_error_set_cancelled (&cancelled_error, FALSE, "NMDevice");
+	concheck_handle_complete (handle, cancelled_error);
+}
+
+NMConnectivityState
+nm_device_get_connectivity_state (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_CONNECTIVITY_UNKNOWN);
+
+	return NM_DEVICE_GET_PRIVATE (self)->connectivity_state;
 }
 
 /*****************************************************************************/
@@ -2214,7 +2848,7 @@ nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *c
 	/* Since slave devices don't have their own IP configuration,
 	 * set the MTU here.
 	 */
-	_commit_mtu (slave, NM_DEVICE_GET_PRIVATE (slave)->ip4_config);
+	_commit_mtu (slave, NM_DEVICE_GET_PRIVATE (slave)->ip_config_4);
 
 	return success;
 }
@@ -2352,8 +2986,6 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 {
 	NMDevicePrivate *priv;
 	GError *error = NULL;
-	gconstpointer addr;
-	size_t addr_length;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
@@ -2386,8 +3018,6 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 
 	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_device_get_platform (self), priv->ifindex, &addr_length);
-
 		if (!nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self), &error)) {
 			_LOGD (LOGD_DEVICE, "LLDP listener %p could not be restarted: %s",
 			       priv->lldp_listener, error->message);
@@ -2624,12 +3254,14 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 
 	now = nm_utils_get_monotonic_timestamp_s ();
 
-	head_entry = nm_ip6_config_lookup_addresses (priv->ip6_config);
+	head_entry = nm_ip6_config_lookup_addresses (priv->ip_config_6);
 	addresses = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscAddress),
 	                               head_entry ? head_entry->len : 0);
 	nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) {
 		const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS (ipconf_iter.current->obj);
 		NMNDiscAddress *ndisc_addr;
+		guint32 lifetime, preferred;
+		gint32 base;
 
 		if (IN6_IS_ADDR_LINKLOCAL (&addr->address))
 			continue;
@@ -2641,19 +3273,35 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 		if (addr->plen != 64)
 			continue;
 
+		/* resolve the timestamps relative to a new base.
+		 *
+		 * Note that for convenience, platform @addr might have timestamp and/or
+		 * lifetime unset. We don't allow that flexibility for ndisc and require
+		 * well defined timestamps. */
+		if (addr->timestamp) {
+			nm_assert (addr->timestamp < G_MAXINT32);
+			base = addr->timestamp;
+		} else
+			base = now;
+
+		lifetime = nm_utils_lifetime_get (addr->timestamp, addr->lifetime, addr->preferred,
+		                                  base, &preferred);
+		if (!lifetime)
+			continue;
+
 		g_array_set_size (addresses, addresses->len+1);
 		ndisc_addr = &g_array_index (addresses, NMNDiscAddress, addresses->len-1);
 		ndisc_addr->address = addr->address;
-		ndisc_addr->timestamp = addr->timestamp;
-		ndisc_addr->lifetime = addr->lifetime;
-		ndisc_addr->preferred = addr->preferred;
+		ndisc_addr->timestamp = base;
+		ndisc_addr->lifetime = lifetime;
+		ndisc_addr->preferred = preferred;
 	}
 
-	len = nm_ip6_config_get_num_nameservers (priv->ip6_config);
+	len = nm_ip6_config_get_num_nameservers (priv->ip_config_6);
 	dns_servers = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscDNSServer), len);
 	g_array_set_size (dns_servers, len);
 	for (i = 0; i < len; i++) {
-		const struct in6_addr *nameserver = nm_ip6_config_get_nameserver (priv->ip6_config, i);
+		const struct in6_addr *nameserver = nm_ip6_config_get_nameserver (priv->ip_config_6, i);
 		NMNDiscDNSServer *ndisc_nameserver;
 
 		ndisc_nameserver = &g_array_index (dns_servers, NMNDiscDNSServer, i);
@@ -2662,11 +3310,11 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 		ndisc_nameserver->lifetime = NM_NDISC_ROUTER_LIFETIME;
 	}
 
-	len = nm_ip6_config_get_num_searches (priv->ip6_config);
+	len = nm_ip6_config_get_num_searches (priv->ip_config_6);
 	dns_domains = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscDNSDomain), len);
 	g_array_set_size (dns_domains, len);
 	for (i = 0; i < len; i++) {
-		const char *search = nm_ip6_config_get_search (priv->ip6_config, i);
+		const char *search = nm_ip6_config_get_search (priv->ip_config_6, i);
 		NMNDiscDNSDomain *ndisc_search;
 
 		ndisc_search = &g_array_index (dns_domains, NMNDiscDNSDomain, i);
@@ -2792,11 +3440,11 @@ device_link_changed (NMDevice *self)
 		/* the link was down and just came up. That happens for example, while changing MTU.
 		 * We must restore IP configuration. */
 		if (priv->ip4_state == IP_DONE) {
-			if (!ip4_config_merge_and_apply (self, TRUE))
+			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 				_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
 		}
 		if (priv->ip6_state == IP_DONE) {
-			if (!ip6_config_merge_and_apply (self, TRUE))
+			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 				_LOGW (LOGD_IP6, "failed applying IP6 config after link comes up again");
 		}
 	}
@@ -2981,7 +3629,7 @@ ip4_rp_filter_update (NMDevice *self)
 
 	if (   priv->v4_has_shadowed_routes
 	    || nm_device_get_best_default_route (self, AF_INET)) {
-		if (nm_device_ipv4_sysctl_get_uint32 (self, "rp_filter", 0) != 1) {
+		if (nm_device_ipv4_sysctl_get_effective_uint32 (self, "rp_filter", 0) != 1) {
 			/* Don't touch the rp_filter if it's not strict. */
 			return;
 		}
@@ -3300,8 +3948,8 @@ realize_start_setup (NMDevice *self,
 	g_return_if_fail (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT));
 	g_return_if_fail (priv->ip_ifindex <= 0);
 	g_return_if_fail (priv->ip_iface == NULL);
-	g_return_if_fail (!priv->queued_ip4_config_id);
-	g_return_if_fail (!priv->queued_ip6_config_id);
+	g_return_if_fail (!priv->queued_ip_config_id_4);
+	g_return_if_fail (!priv->queued_ip_config_id_6);
 
 	_LOGD (LOGD_DEVICE, "start setup of %s, kernel ifindex %d", G_OBJECT_TYPE_NAME (self), plink ? plink->ifindex : 0);
 
@@ -3348,7 +3996,7 @@ realize_start_setup (NMDevice *self,
 
 		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
 		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
-			priv->nm_ipv6ll = nm_platform_link_get_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ifindex);
+			priv->ipv6ll_handle = nm_platform_link_get_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ifindex);
 
 		if (nm_platform_link_supports_sriov (nm_device_get_platform (self), priv->ifindex))
 			capabilities |= NM_DEVICE_CAP_SRIOV;
@@ -3447,6 +4095,9 @@ nm_device_realize_finish (NMDevice *self, const NMPlatformLink *plink)
 	if (plink)
 		device_recheck_slave_status (self, plink);
 
+	priv->update_ip_config_completed_v4 = FALSE;
+	priv->update_ip_config_completed_v6 = FALSE;
+
 	priv->real = TRUE;
 	_notify (self, PROP_REAL);
 
@@ -3672,7 +4323,7 @@ nm_device_notify_component_added (NMDevice *self, GObject *component)
  * because that ethernet interface is controlled by the WWAN device and cannot
  * be used independently of the WWAN device.
  *
- * Returns: %TRUE if @self or it's components owns the interface name,
+ * Returns: %TRUE if @self or its components own the interface name,
  * %FALSE if not
  */
 gboolean
@@ -3962,7 +4613,7 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 
 	/* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the
 	 * master to enslave us. */
-	if (   nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request))
+	if (   nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request.obj))
 	    && !priv->is_enslaved)
 		return;
 
@@ -4150,8 +4801,8 @@ nm_device_removed (NMDevice *self, gboolean unconfigure_ip_config)
 	if (!unconfigure_ip_config)
 		return;
 
-	nm_device_set_ip4_config (self, NULL, FALSE, NULL);
-	nm_device_set_ip6_config (self, NULL, FALSE);
+	nm_device_set_ip_config (self, AF_INET, NULL, FALSE, NULL);
+	nm_device_set_ip_config (self, AF_INET6, NULL, FALSE, NULL);
 }
 
 static gboolean
@@ -4403,9 +5054,9 @@ device_has_config (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* Check for IP configuration. */
-	if (priv->ip4_config && nm_ip4_config_get_num_addresses (priv->ip4_config))
+	if (priv->ip_config_4 && nm_ip4_config_get_num_addresses (priv->ip_config_4))
 		return TRUE;
-	if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config))
+	if (priv->ip_config_6 && nm_ip6_config_get_num_addresses (priv->ip_config_6))
 		return TRUE;
 
 	/* The existence of a software device is good enough. */
@@ -4530,10 +5181,10 @@ nm_device_generate_connection (NMDevice *self,
 		}
 	} else {
 		/* Only regular and master devices get IP configuration; slaves do not */
-		s_ip4 = nm_ip4_config_create_setting (priv->ip4_config);
+		s_ip4 = nm_ip4_config_create_setting (priv->ip_config_4);
 		nm_connection_add_setting (connection, s_ip4);
 
-		s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
+		s_ip6 = nm_ip6_config_create_setting (priv->ip_config_6);
 		nm_connection_add_setting (connection, s_ip6);
 
 		nm_connection_add_setting (connection, nm_setting_proxy_new ());
@@ -4590,34 +5241,45 @@ nm_device_generate_connection (NMDevice *self,
 	return g_steal_pointer (&connection);
 }
 
+/**
+ * nm_device_complete_connection:
+ *
+ * Complete the connection. This is solely used for AddAndActivate where the user
+ * may pass in an incomplete connection and a device, and the device tries to
+ * make sense of it and complete it for activation. Otherwise, this is not
+ * used.
+ *
+ * Returns: success or failure.
+ */
 gboolean
 nm_device_complete_connection (NMDevice *self,
                                NMConnection *connection,
                                const char *specific_object,
-                               const GSList *existing_connections,
+                               NMConnection *const*existing_connections,
                                GError **error)
 {
-	gboolean success = FALSE;
+	NMDeviceClass *klass;
 
-	g_return_val_if_fail (self != NULL, FALSE);
-	g_return_val_if_fail (connection != NULL, FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+	klass = NM_DEVICE_GET_CLASS (self);
 
-	if (!NM_DEVICE_GET_CLASS (self)->complete_connection) {
+	if (!klass->complete_connection) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
 		             "Device class %s had no complete_connection method",
 		             G_OBJECT_TYPE_NAME (self));
 		return FALSE;
 	}
 
-	success = NM_DEVICE_GET_CLASS (self)->complete_connection (self,
-	                                                           connection,
-	                                                           specific_object,
-	                                                           existing_connections,
-	                                                           error);
-	if (success)
-		success = nm_connection_verify (connection, error);
+	if (!klass->complete_connection (self,
+	                                 connection,
+	                                 specific_object,
+	                                 existing_connections,
+	                                 error))
+		return FALSE;
 
-	return success;
+	return nm_connection_verify (connection, error);
 }
 
 gboolean
@@ -4726,15 +5388,12 @@ nm_device_check_connection_compatible (NMDevice *self, NMConnection *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 (!nm_device_is_master (self))
 		return FALSE;
 
@@ -5147,8 +5806,9 @@ activate_stage1_device_prepare (NMDevice *self)
 	_set_ip_state (self, AF_INET6, IP_NONE);
 
 	/* Notify the new ActiveConnection along with the state change */
-	priv->act_request_public = TRUE;
-	_notify (self, PROP_ACTIVE_CONNECTION);
+	nm_dbus_track_obj_path_set (&priv->act_request,
+	                            priv->act_request.obj,
+	                            TRUE);
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
 
@@ -5184,7 +5844,7 @@ nm_device_activate_schedule_stage1_device_prepare (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	activation_source_schedule (self, activate_stage1_device_prepare, AF_INET);
 }
@@ -5202,8 +5862,6 @@ lldp_init (NMDevice *self, gboolean restart)
 
 	if (priv->ifindex > 0 && lldp_rx_enabled (self)) {
 		gs_free_error GError *error = NULL;
-		gconstpointer addr;
-		size_t addr_length;
 
 		if (priv->lldp_listener) {
 			if (restart && nm_lldp_listener_is_running (priv->lldp_listener))
@@ -5217,8 +5875,6 @@ lldp_init (NMDevice *self, gboolean restart)
 		}
 
 		if (!nm_lldp_listener_is_running (priv->lldp_listener)) {
-			addr = nm_platform_link_get_address (nm_device_get_platform (self), priv->ifindex, &addr_length);
-
 			if (nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self), &error))
 				_LOGD (LOGD_DEVICE, "LLDP listener %p started", priv->lldp_listener);
 			else {
@@ -5365,7 +6021,7 @@ activate_stage2_device_config (NMDevice *self)
 
 		if (slave_state == NM_DEVICE_STATE_IP_CONFIG)
 			nm_device_master_enslave_slave (self, info->slave, nm_device_get_applied_connection (info->slave));
-		else if (   priv->act_request
+		else if (   priv->act_request.obj
 		         && nm_device_sys_iface_state_is_external (self)
 		         && slave_state <= NM_DEVICE_STATE_DISCONNECTED)
 			nm_device_queue_recheck_assume (info->slave);
@@ -5390,10 +6046,10 @@ nm_device_activate_schedule_stage2_device_config (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	if (!priv->master_ready_handled) {
-		NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request);
+		NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request.obj);
 		NMActiveConnection *master;
 
 		master = nm_active_connection_get_master (active);
@@ -5431,13 +6087,9 @@ nm_device_ip_method_failed (NMDevice *self,
                             int addr_family,
                             NMDeviceStateReason reason)
 {
-	NMDevicePrivate *priv;
-
 	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
 	_set_ip_state (self, addr_family, IP_FAIL);
 
 	if (get_ip_config_may_fail (self, addr_family))
@@ -5478,16 +6130,16 @@ get_ipv4_dad_timeout (NMDevice *self)
 }
 
 static void
-arping_data_destroy (gpointer ptr, GClosure *closure)
+acd_data_destroy (gpointer ptr, GClosure *closure)
 {
-	ArpingData *data = ptr;
+	AcdData *data = ptr;
 	int i;
 
 	if (data) {
 		for (i = 0; data->configs && data->configs[i]; i++)
 			g_object_unref (data->configs[i]);
 		g_free (data->configs);
-		g_slice_free (ArpingData, data);
+		g_slice_free (AcdData, data);
 	}
 }
 
@@ -5507,7 +6159,7 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 }
 
 static void
-arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *data)
+acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
 {
 	NMDevice *self;
 	NMDevicePrivate *priv;
@@ -5522,7 +6174,7 @@ arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *da
 
 	for (i = 0; data->configs && data->configs[i]; i++) {
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) {
-			result = nm_arping_manager_check_address (arping_manager, address->address);
+			result = nm_acd_manager_check_address (acd_manager, address->address);
 			success &= result;
 
 			_NMLOG (result ? LOGL_DEBUG : LOGL_WARN,
@@ -5535,8 +6187,8 @@ arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *da
 
 	data->callback (self, data->configs, success);
 
-	priv->arping.dad_list = g_slist_remove (priv->arping.dad_list, arping_manager);
-	nm_arping_manager_destroy (arping_manager);
+	priv->acd.dad_list = g_slist_remove (priv->acd.dad_list, acd_manager);
+	nm_acd_manager_destroy (acd_manager);
 }
 
 /**
@@ -5550,18 +6202,17 @@ arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *da
  * be started. @configs will be unreferenced after @cb has been called.
  */
 static void
-ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
+ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMArpingManager *arping_manager;
+	NMAcdManager *acd_manager;
 	const NMPlatformIP4Address *address;
 	NMDedupMultiIter ipconf_iter;
-	ArpingData *data;
+	AcdData *data;
 	guint timeout;
 	gboolean ret, addr_found;
-	const guint8 *hw_addr;
-	size_t hw_addr_len = 0;
-	GError *error = NULL;
+	const guint8 *hwaddr_arr;
+	size_t length;
 	guint i;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
@@ -5576,14 +6227,14 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
 	}
 
 	timeout = get_ipv4_dad_timeout (self);
-	hw_addr = nm_platform_link_get_address (nm_device_get_platform (self),
-	                                        nm_device_get_ip_ifindex (self),
-	                                        &hw_addr_len);
+	hwaddr_arr = nm_platform_link_get_address (nm_device_get_platform (self),
+	                                           nm_device_get_ip_ifindex (self),
+	                                           &length);
 
 	if (   !timeout
-	    || !hw_addr
-	    || !hw_addr_len
+	    || !hwaddr_arr
 	    || !addr_found
+	    || length != ETH_ALEN
 	    || nm_device_sys_iface_state_is_external_or_assume (self)) {
 
 		/* DAD not needed, signal success */
@@ -5596,36 +6247,36 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
 		return;
 	}
 
-	/* don't take additional references of @arping_manager that outlive @self.
+	/* don't take additional references of @acd_manager that outlive @self.
 	 * Otherwise, the callback can be invoked on a dangling pointer as we don't
 	 * disconnect the handler. */
-	arping_manager = nm_arping_manager_new (nm_device_get_ip_ifindex (self));
-	priv->arping.dad_list = g_slist_append (priv->arping.dad_list, arping_manager);
+	acd_manager = nm_acd_manager_new (nm_device_get_ip_ifindex (self), hwaddr_arr, length);
+	priv->acd.dad_list = g_slist_append (priv->acd.dad_list, acd_manager);
 
-	data = g_slice_new0 (ArpingData);
+	data = g_slice_new0 (AcdData);
 	data->configs = configs;
 	data->callback = cb;
 	data->device = self;
 
 	for (i = 0; configs[i]; i++) {
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address)
-			nm_arping_manager_add_address (arping_manager, address->address);
+			nm_acd_manager_add_address (acd_manager, address->address);
 	}
 
-	g_signal_connect_data (arping_manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
-	                       G_CALLBACK (arping_manager_probe_terminated), data,
-	                       arping_data_destroy, 0);
+	g_signal_connect_data (acd_manager, NM_ACD_MANAGER_PROBE_TERMINATED,
+	                       G_CALLBACK (acd_manager_probe_terminated), data,
+	                       acd_data_destroy, 0);
 
-	ret = nm_arping_manager_start_probe (arping_manager, timeout, &error);
+	ret = nm_acd_manager_start_probe (acd_manager, timeout);
 
 	if (!ret) {
-		_LOGW (LOGD_DEVICE, "arping probe failed: %s", error->message);
+		_LOGW (LOGD_DEVICE, "acd probe failed");
 
 		/* DAD could not be started, signal success */
 		cb (self, configs, TRUE);
 
-		priv->arping.dad_list = g_slist_remove (priv->arping.dad_list, arping_manager);
-		nm_arping_manager_destroy (arping_manager);
+		priv->acd.dad_list = g_slist_remove (priv->acd.dad_list, acd_manager);
+		nm_acd_manager_destroy (acd_manager);
 	}
 }
 
@@ -5687,10 +6338,10 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 	NMIP4Config *config;
 	int r;
 
-	if (priv->act_request == NULL)
+	if (priv->act_request.obj == NULL)
 		return;
 
-	connection = nm_act_request_get_applied_connection (priv->act_request);
+	connection = nm_act_request_get_applied_connection (priv->act_request.obj);
 	g_assert (connection);
 
 	/* Ignore if the connection isn't an AutoIP connection */
@@ -5724,9 +6375,8 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 			nm_clear_g_source (&priv->ipv4ll_timeout);
 			nm_device_activate_schedule_ip4_config_result (self, config);
 		} else if (priv->ip4_state == IP_DONE) {
-			g_clear_object (&priv->dev_ip4_config);
-			priv->dev_ip4_config = g_object_ref (config);
-			if (!ip4_config_merge_and_apply (self, TRUE)) {
+			applied_config_init (&priv->dev_ip4_config, config);
+			if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 				_LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
 				nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
 			}
@@ -5822,55 +6472,42 @@ ipv4ll_start (NMDevice *self)
 /*****************************************************************************/
 
 static void
-ensure_con_ip4_config (NMDevice *self)
+ensure_con_ip_config (NMDevice *self, int addr_family)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+	NMIPConfig *con_ip_config;
 
-	if (priv->con_ip4_config)
+	if (priv->con_ip_config_x[IS_IPv4])
 		return;
 
 	connection = nm_device_get_applied_connection (self);
 	if (!connection)
 		return;
 
-	priv->con_ip4_config = _ip4_config_new (self);
-	nm_ip4_config_merge_setting (priv->con_ip4_config,
-	                             nm_connection_get_setting_ip4_config (connection),
-	                             nm_device_get_route_table (self, AF_INET, TRUE),
-	                             nm_device_get_route_metric (self, AF_INET));
+	con_ip_config = _ip_config_new (self, addr_family);
 
-	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
-		/* For assumed connections ignore all addresses and routes. */
-		nm_ip4_config_reset_addresses (priv->con_ip4_config);
-		nm_ip4_config_reset_routes (priv->con_ip4_config);
+	if (IS_IPv4) {
+		nm_ip4_config_merge_setting (NM_IP4_CONFIG (con_ip_config),
+		                             nm_connection_get_setting_ip4_config (connection),
+		                             _get_mdns (self),
+		                             nm_device_get_route_table (self, addr_family, TRUE),
+		                             nm_device_get_route_metric (self, addr_family));
+	} else {
+		nm_ip6_config_merge_setting (NM_IP6_CONFIG (con_ip_config),
+		                             nm_connection_get_setting_ip6_config (connection),
+		                             nm_device_get_route_table (self, addr_family, TRUE),
+		                             nm_device_get_route_metric (self, addr_family));
 	}
-}
-
-static void
-ensure_con_ip6_config (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-
-	if (priv->con_ip6_config)
-		return;
-
-	connection = nm_device_get_applied_connection (self);
-	if (!connection)
-		return;
-
-	priv->con_ip6_config = _ip6_config_new (self);
-	nm_ip6_config_merge_setting (priv->con_ip6_config,
-	                             nm_connection_get_setting_ip6_config (connection),
-	                             nm_device_get_route_table (self, AF_INET6, TRUE),
-	                             nm_device_get_route_metric (self, AF_INET6));
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
 		/* For assumed connections ignore all addresses and routes. */
-		nm_ip6_config_reset_addresses (priv->con_ip6_config);
-		nm_ip6_config_reset_routes (priv->con_ip6_config);
+		nm_ip_config_reset_addresses (con_ip_config);
+		nm_ip_config_reset_routes (con_ip_config);
 	}
+
+	priv->con_ip_config_x[IS_IPv4] = con_ip_config;
 }
 
 /*****************************************************************************/
@@ -5881,7 +6518,7 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->dhcp4.restart_id);
+	nm_clear_g_source (&priv->dhcp4.grace_id);
 	g_clear_pointer (&priv->dhcp4.pac_url, g_free);
 
 	if (priv->dhcp4.client) {
@@ -5898,105 +6535,222 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	}
 
 	if (priv->dhcp4.config) {
-		nm_exported_object_clear_and_unexport (&priv->dhcp4.config);
+		nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
 		_notify (self, PROP_DHCP4_CONFIG);
 	}
 }
 
 static gboolean
-ip4_config_merge_and_apply (NMDevice *self,
-                            gboolean commit)
+ip_config_merge_and_apply (NMDevice *self,
+                           int addr_family,
+                           gboolean commit)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
 	gboolean success;
-	NMIP4Config *composite;
+	gs_unref_object NMIPConfig *composite = NULL;
+	NMIPConfig *config;
+	gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
+	NMConnection *connection;
 	gboolean ignore_auto_routes = FALSE;
 	gboolean ignore_auto_dns = FALSE;
 	gboolean ignore_default_routes = FALSE;
 	GSList *iter;
-	gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
+	const char *ip6_addr_gen_token = NULL;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	if (nm_device_sys_iface_state_is_external (self))
-		commit = 0;
+		commit = FALSE;
 
-	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	connection = nm_device_get_applied_connection (self);
+
+	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	if (connection) {
-		NMSettingIPConfig *s_ip4 = nm_connection_get_setting_ip4_config (connection);
+		NMSettingIPConfig *s_ip =   IS_IPv4
+		                          ? nm_connection_get_setting_ip4_config (connection)
+		                          : nm_connection_get_setting_ip6_config (connection);
 
-		if (s_ip4) {
-			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip4);
-			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip4);
+		if (s_ip) {
+			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip);
+			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip);
 
 			/* if the connection has an explicit gateway, we also ignore
 			 * the default routes from other sources. */
-			ignore_default_routes =    nm_setting_ip_config_get_never_default (s_ip4)
-			                        || nm_setting_ip_config_get_gateway (s_ip4);
+			ignore_default_routes =    nm_setting_ip_config_get_never_default (s_ip)
+			                        || nm_setting_ip_config_get_gateway (s_ip);
+
+			if (!IS_IPv4) {
+				NMSettingIP6Config *s_ip6 = NM_SETTING_IP6_CONFIG (s_ip);
+
+				if (nm_setting_ip6_config_get_addr_gen_mode (s_ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64)
+					ip6_addr_gen_token = nm_setting_ip6_config_get_token (s_ip6);
+			}
 		}
 	}
 
-	composite = _ip4_config_new (self);
-	init_ip4_config_dns_priority (self, composite);
+	composite = _ip_config_new (self, addr_family);
+
+	if (!IS_IPv4) {
+		nm_ip6_config_set_privacy (NM_IP6_CONFIG (composite),
+		                           priv->ndisc
+		                             ? priv->ndisc_use_tempaddr
+		                             : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+	}
+
+	init_ip_config_dns_priority (self, composite);
 
 	if (commit) {
-		if (priv->queued_ip4_config_id)
-			update_ext_ip_config (self, AF_INET, FALSE, FALSE);
-		ensure_con_ip4_config (self);
+		if (priv->queued_ip_config_id_x[IS_IPv4])
+			update_ext_ip_config (self, addr_family, FALSE);
+		ensure_con_ip_config (self, addr_family);
 	}
 
-	if (commit)
-		priv->default_route_metric_penalty_ip4_has = default_route_metric_penalty_detect (self);
+	if (!IS_IPv4) {
+		if (   commit
+		    && priv->ipv6ll_has) {
+			const NMPlatformIP6Address ll_a = {
+				.address = priv->ipv6ll_addr,
+				.plen = 64,
+				.addr_source = NM_IP_CONFIG_SOURCE_IP6LL,
+			};
+			const NMPlatformIP6Route ll_r = {
+				.network.s6_addr16[0] = htons (0xfe80u),
+				.plen = 64,
+				.metric = nm_device_get_route_metric (self, addr_family),
+				.rt_source = NM_IP_CONFIG_SOURCE_IP6LL,
+			};
 
-	if (priv->dev_ip4_config) {
-		nm_ip4_config_merge (composite, priv->dev_ip4_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET));
+			nm_assert (IN6_IS_ADDR_LINKLOCAL (&priv->ipv6ll_addr));
+
+			nm_ip6_config_add_address (NM_IP6_CONFIG (composite), &ll_a);
+			nm_ip6_config_add_route (NM_IP6_CONFIG (composite), &ll_r, NULL);
+		}
 	}
 
-	for (iter = priv->vpn4_configs; iter; iter = iter->next)
-		nm_ip4_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+	if (commit) {
+		gboolean v;
+
+		v = default_route_metric_penalty_detect (self);
+		if (IS_IPv4)
+			priv->default_route_metric_penalty_ip4_has = v;
+		else
+			priv->default_route_metric_penalty_ip6_has = v;
+	}
 
-	if (priv->ext_ip4_config)
-		nm_ip4_config_merge (composite, priv->ext_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+	/* Merge all the IP configs into the composite config */
+
+	if (IS_IPv4) {
+		config = applied_config_get_current (&priv->dev_ip4_config);
+		if (config) {
+			nm_ip4_config_merge (NM_IP4_CONFIG (composite), NM_IP4_CONFIG (config),
+			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+			                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+			                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+			                     default_route_metric_penalty_get (self, addr_family));
+		}
+	}
+
+	if (!IS_IPv4) {
+		config = applied_config_get_current (&priv->ac_ip6_config);
+		if (config) {
+			nm_ip6_config_merge (NM_IP6_CONFIG (composite), NM_IP6_CONFIG (config),
+			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+			                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+			                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+			                     default_route_metric_penalty_get (self, addr_family));
+		}
+	}
+
+	if (!IS_IPv4) {
+		config = applied_config_get_current (&priv->dhcp6.ip6_config);
+		if (config) {
+			nm_ip6_config_merge (NM_IP6_CONFIG (composite), NM_IP6_CONFIG (config),
+			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+			                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+			                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+			                     default_route_metric_penalty_get (self, addr_family));
+		}
+	}
+
+	for (iter = priv->vpn_configs_x[IS_IPv4]; iter; iter = iter->next)
+		nm_ip_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+
+	if (priv->ext_ip_config_x[IS_IPv4])
+		nm_ip_config_merge (composite, priv->ext_ip_config_x[IS_IPv4], NM_IP_CONFIG_MERGE_DEFAULT, 0);
 
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
 	 */
-	if (priv->wwan_ip4_config) {
-		nm_ip4_config_merge (composite, priv->wwan_ip4_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET));
+	config = applied_config_get_current (&priv->wwan_ip_config_x[IS_IPv4]);
+	if (config) {
+		nm_ip_config_merge (composite, config,
+		                      (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+		                    | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+		                    | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+		                    default_route_metric_penalty_get (self, addr_family));
+	}
+
+	if (!IS_IPv4) {
+		if (priv->rt6_temporary_not_available) {
+			const NMPObject *o;
+			GHashTableIter hiter;
+
+			g_hash_table_iter_init (&hiter, priv->rt6_temporary_not_available);
+			while (g_hash_table_iter_next (&hiter, (gpointer *) &o, NULL)) {
+				nm_ip6_config_add_route (NM_IP6_CONFIG (composite),
+				                         NMP_OBJECT_CAST_IP6_ROUTE (o),
+				                         NULL);
+			}
+		}
 	}
 
 	/* Merge user overrides into the composite config. For assumed connections,
-	 * con_ip4_config is empty. */
-	if (priv->con_ip4_config) {
-		nm_ip4_config_merge (composite, priv->con_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT,
-		                     default_route_metric_penalty_get (self, AF_INET));
+	 * con_ip_config_x is empty. */
+	if (priv->con_ip_config_x[IS_IPv4]) {
+		nm_ip_config_merge (composite, priv->con_ip_config_x[IS_IPv4], NM_IP_CONFIG_MERGE_DEFAULT,
+		                    default_route_metric_penalty_get (self, addr_family));
 	}
 
 	if (commit) {
-		nm_ip4_config_add_dependent_routes (composite,
-		                                    nm_device_get_route_table (self, AF_INET, TRUE),
-		                                    nm_device_get_route_metric (self, AF_INET),
-		                                    &ip4_dev_route_blacklist);
+		if (IS_IPv4) {
+			nm_ip4_config_add_dependent_routes (NM_IP4_CONFIG (composite),
+			                                    nm_device_get_route_table (self, addr_family, TRUE),
+			                                    nm_device_get_route_metric (self, addr_family),
+			                                    &ip4_dev_route_blacklist);
+		} else {
+			nm_ip6_config_add_dependent_routes (NM_IP6_CONFIG (composite),
+			                                    nm_device_get_route_table (self, addr_family, TRUE),
+			                                    nm_device_get_route_metric (self, addr_family));
+		}
 	}
 
-	if (commit) {
-		if (NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit)
-			NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, composite);
+	if (IS_IPv4) {
+		if (commit) {
+			if (NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit)
+				NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, NM_IP4_CONFIG (composite));
+		}
 	}
 
-	success = nm_device_set_ip4_config (self, composite, commit, ip4_dev_route_blacklist);
-	g_object_unref (composite);
+	if (!IS_IPv4) {
+		if (commit) {
+			NMUtilsIPv6IfaceId iid;
+
+			if (   ip6_addr_gen_token
+			    && nm_utils_ipv6_interface_identifier_get_from_token (&iid, ip6_addr_gen_token)) {
+				nm_platform_link_set_ipv6_token (nm_device_get_platform (self),
+				                                 nm_device_get_ip_ifindex (self),
+				                                 iid);
+			}
+		}
+	}
+
+	success = nm_device_set_ip_config (self, addr_family, composite, commit, ip4_dev_route_blacklist);
+	if (commit) {
+		if (IS_IPv4)
+			priv->v4_commit_first_time = FALSE;
+		else
+			priv->v6_commit_first_time = FALSE;
+	}
 
-	if (commit)
-		priv->v4_commit_first_time = FALSE;
 	return success;
 }
 
@@ -6007,10 +6761,9 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 
 	g_return_val_if_fail (config, FALSE);
 
-	g_clear_object (&priv->dev_ip4_config);
-	priv->dev_ip4_config = g_object_ref (config);
+	applied_config_init (&priv->dev_ip4_config, config);
 
-	if (!ip4_config_merge_and_apply (self, TRUE)) {
+	if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
 		return FALSE;
 	}
@@ -6026,20 +6779,17 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 }
 
 static gboolean
-dhcp4_restart_cb (gpointer user_data)
+dhcp4_grace_period_expired (gpointer user_data)
 {
 	NMDevice *self = user_data;
-	NMDevicePrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	priv->dhcp4.restart_id = 0;
+	_LOGI (LOGD_DHCP4, "DHCPv4: grace period expired");
 
-	if (dhcp4_start (self) == NM_ACT_STAGE_RETURN_FAILURE)
-		dhcp_schedule_restart (self, AF_INET, NULL);
+	nm_device_ip_method_failed (self, AF_INET,
+	                            NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+	/* If the device didn't fail, the DHCP client will continue */
 
-	return FALSE;
+	return G_SOURCE_REMOVE;
 }
 
 static void
@@ -6047,44 +6797,48 @@ dhcp4_fail (NMDevice *self, gboolean timeout)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_LOGD (LOGD_DHCP4, "DHCPv4 failed: timeout %d, num tries left %u",
-	       timeout, priv->dhcp4.num_tries_left);
+	_LOGD (LOGD_DHCP4, "DHCPv4 failed%s", timeout ? " (timeout)" : "");
 
-	dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-
-	/* Don't fail if there are static addresses configured on
-	 * the device, instead retry after some time.
+	/* Keep client running if there are static addresses configured
+	 * on the interface.
 	 */
 	if (   priv->ip4_state == IP_DONE
-	    && priv->con_ip4_config
-	    && nm_ip4_config_get_num_addresses (priv->con_ip4_config) > 0) {
-		dhcp_schedule_restart (self, AF_INET, "device has IP addresses");
+	    && priv->con_ip_config_4
+	    && nm_ip4_config_get_num_addresses (priv->con_ip_config_4) > 0)
+		goto clear_config;
+
+	/* Fail the method in case of timeout or failure during initial
+	 * configuration.
+	 */
+	if (   !priv->dhcp4.was_active
+	    && (timeout || priv->ip4_state == IP_CONF)) {
+		dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
+		nm_device_activate_schedule_ip4_config_timeout (self);
 		return;
 	}
 
-	if (   priv->dhcp4.num_tries_left == DHCP_NUM_TRIES_MAX
-	    && (timeout || (priv->ip4_state == IP_CONF))
-	    && !priv->dhcp4.was_active)
-		nm_device_activate_schedule_ip4_config_timeout (self);
-	else if (   priv->dhcp4.num_tries_left < DHCP_NUM_TRIES_MAX
-	         || priv->ip4_state == IP_DONE
-	         || priv->dhcp4.was_active) {
-		/* Don't fail immediately when the lease expires but try to
-		 * restart DHCP for a predefined number of times.
-		 */
-		if (priv->dhcp4.num_tries_left) {
-			priv->dhcp4.num_tries_left--;
-			dhcp_schedule_restart (self, AF_INET, "lease expired");
-		} else {
-			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-			/* We failed the ipv4 method but schedule again the retries if the ipv6 method is
-			 * configured, keeping the connection up.
-			 */
-			if (nm_device_get_state (self) != NM_DEVICE_STATE_FAILED)
-				dhcp_schedule_restart (self, AF_INET, "renewal failed");
-		}
-	} else
-		g_warn_if_reached ();
+	/* In any other case (expired lease, assumed connection, etc.),
+	 * start a grace period in which we keep the client running,
+	 * hoping that it will regain a lease.
+	 */
+	if (!priv->dhcp4.grace_id) {
+		priv->dhcp4.grace_id = g_timeout_add_seconds (DHCP_GRACE_PERIOD_SEC,
+		                                              dhcp4_grace_period_expired,
+		                                              self);
+		_LOGI (LOGD_DHCP4,
+		       "DHCPv4: %u seconds grace period started",
+		       DHCP_GRACE_PERIOD_SEC);
+		goto clear_config;
+	}
+	return;
+
+clear_config:
+	/* The previous configuration is no longer valid */
+	if (priv->dhcp4.config) {
+		nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
+		priv->dhcp4.config = nm_dhcp4_config_new ();
+		_notify (self, PROP_DHCP4_CONFIG);
+	}
 }
 
 static void
@@ -6124,6 +6878,8 @@ dhcp4_state_changed (NMDhcpClient *client,
 			break;
 		}
 
+		nm_clear_g_source (&priv->dhcp4.grace_id);
+
 		/* After some failures, we have been able to renew the lease:
 		 * update the ip state
 		 */
@@ -6136,7 +6892,6 @@ dhcp4_state_changed (NMDhcpClient *client,
 
 		nm_dhcp4_config_set_options (priv->dhcp4.config, options);
 		_notify (self, PROP_DHCP4_CONFIG);
-		priv->dhcp4.num_tries_left = DHCP_NUM_TRIES_MAX;
 
 		if (priv->ip4_state == IP_CONF) {
 			connection = nm_device_get_applied_connection (self);
@@ -6145,6 +6900,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 			manual = _ip4_config_new (self);
 			nm_ip4_config_merge_setting (manual,
 			                             nm_connection_get_setting_ip4_config (connection),
+			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
 			                             nm_device_get_route_table (self, AF_INET, TRUE),
 			                             nm_device_get_route_metric (self, AF_INET));
 
@@ -6220,14 +6976,100 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 	return timeout ?: NM_DHCP_TIMEOUT_DEFAULT;
 }
 
+static GBytes *
+dhcp4_get_client_id (NMDevice *self, NMConnection *connection)
+{
+	NMSettingIPConfig *s_ip4;
+	const char *client_id;
+	gs_free char *client_id_default = NULL;
+	guint8 *client_id_buf;
+	gboolean is_mac;
+
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	client_id = nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4));
+
+	if (!client_id) {
+		client_id_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                                           "ipv4.dhcp-client-id", self);
+		if (client_id_default && client_id_default[0])
+			client_id = client_id_default;
+	}
+
+	if (!client_id)
+		return NULL;
+
+	if (   (is_mac = nm_streq (client_id, "mac"))
+	    || nm_streq (client_id, "perm-mac")) {
+		const char *hwaddr;
+		char addr_buf[NM_UTILS_HWADDR_LEN_MAX];
+		gsize addr_len;
+		guint8 addr_type;
+
+		hwaddr = is_mac
+		         ? nm_device_get_hw_address (self)
+		         : nm_device_get_permanent_hw_address (self);
+		if (!hwaddr)
+			return NULL;
+
+		if (!_nm_utils_hwaddr_aton (hwaddr, addr_buf, sizeof (addr_buf), &addr_len))
+			g_return_val_if_reached (NULL);
+
+		switch (addr_len) {
+		case ETH_ALEN:
+			addr_type = ARPHRD_ETHER;
+			break;
+		default:
+			/* unsupported type. */
+			return NULL;
+		}
+
+		client_id_buf = g_malloc (addr_len + 1);
+		client_id_buf[0] = addr_type;
+		memcpy (&client_id_buf[1], addr_buf, addr_len);
+		return g_bytes_new_take (client_id_buf, addr_len + 1);
+	}
+
+	if (nm_streq (client_id, "stable")) {
+		NMUtilsStableType stable_type;
+		const char *stable_id;
+		GChecksum *sum;
+		guint8 buf[20];
+		gsize buf_size;
+		guint32 salted_header;
+
+		stable_id = _get_stable_id (self, connection, &stable_type);
+		if (!stable_id)
+			g_return_val_if_reached (NULL);
+
+		salted_header = htonl (2011610591 + stable_type);
+
+		sum = g_checksum_new (G_CHECKSUM_SHA1);
+
+		g_checksum_update (sum, (const guchar *) &salted_header, sizeof (salted_header));
+		g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id));
+
+		buf_size = sizeof (buf);
+		g_checksum_get_digest (sum, buf, &buf_size);
+		nm_assert (buf_size == sizeof (buf));
+
+		g_checksum_free (sum);
+
+		client_id_buf = g_malloc (1 + 15);
+		client_id_buf[0] = 0;
+		memcpy (&client_id_buf[1], buf, 15);
+		return g_bytes_new_take (client_id_buf, 1 + 15);
+	}
+
+	return nm_dhcp_utils_client_id_string_to_bytes (client_id);
+}
+
 static NMActStageReturn
 dhcp4_start (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingIPConfig *s_ip4;
-	const guint8 *hw_addr;
-	size_t hw_addr_len = 0;
-	GByteArray *tmp = NULL;
+	gs_unref_bytes GBytes *hwaddr = NULL;
+	gs_unref_bytes GBytes *client_id = NULL;
 	NMConnection *connection;
 
 	connection = nm_device_get_applied_connection (self);
@@ -6236,36 +7078,31 @@ dhcp4_start (NMDevice *self)
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
 	/* Clear old exported DHCP options */
-	nm_exported_object_clear_and_unexport (&priv->dhcp4.config);
+	nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
 	priv->dhcp4.config = nm_dhcp4_config_new ();
 
-	hw_addr = nm_platform_link_get_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), &hw_addr_len);
-	if (hw_addr_len) {
-		tmp = g_byte_array_sized_new (hw_addr_len);
-		g_byte_array_append (tmp, hw_addr, hw_addr_len);
-	}
+	hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self),
+	                                                nm_device_get_ip_ifindex (self));
+
+	client_id = dhcp4_get_client_id (self, connection);
 
-	/* Begin DHCP on the interface */
 	g_warn_if_fail (priv->dhcp4.client == NULL);
 	priv->dhcp4.client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
 	                                                nm_netns_get_multi_idx (nm_device_get_netns (self)),
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
-	                                                tmp,
+	                                                hwaddr,
 	                                                nm_connection_get_uuid (connection),
 	                                                nm_device_get_route_table (self, AF_INET, TRUE),
 	                                                nm_device_get_route_metric (self, AF_INET),
 	                                                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)),
+	                                                client_id,
 	                                                get_dhcp_timeout (self, AF_INET),
 	                                                priv->dhcp_anycast_address,
 	                                                NULL);
 
-	if (tmp)
-		g_byte_array_free (tmp, TRUE);
-
 	if (!priv->dhcp4.client)
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
@@ -6338,7 +7175,7 @@ shared4_new_config (NMDevice *self, NMConnection *connection)
 		guint32 count = 0;
 
 		if (G_UNLIKELY (!shared_ips))
-			shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
+			shared_ips = g_hash_table_new (nm_direct_hash, NULL);
 		else {
 			while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
 				count += ntohl (0x100);
@@ -6507,7 +7344,6 @@ act_stage3_ip4_config_start (NMDevice *self,
 	}
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	priv->dhcp4.num_tries_left = DHCP_NUM_TRIES_MAX;
 
 	/* Start IPv4 addressing based on the method requested */
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
@@ -6524,6 +7360,7 @@ act_stage3_ip4_config_start (NMDevice *self,
 		config = _ip4_config_new (self);
 		nm_ip4_config_merge_setting (config,
 		                             nm_connection_get_setting_ip4_config (connection),
+		                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
 		                             nm_device_get_route_table (self, AF_INET, TRUE),
 		                             nm_device_get_route_metric (self, AF_INET));
 
@@ -6560,9 +7397,9 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
-	g_clear_object (&priv->dhcp6.ip6_config);
+	applied_config_clear (&priv->dhcp6.ip6_config);
 	g_clear_pointer (&priv->dhcp6.event_id, g_free);
-	nm_clear_g_source (&priv->dhcp6.restart_id);
+	nm_clear_g_source (&priv->dhcp6.grace_id);
 
 	if (priv->dhcp6.client) {
 		nm_clear_g_signal_handler (priv->dhcp6.client, &priv->dhcp6.state_sigid);
@@ -6578,148 +7415,18 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP6, FALSE);
 
 	if (priv->dhcp6.config) {
-		nm_exported_object_clear_and_unexport (&priv->dhcp6.config);
+		nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
 		_notify (self, PROP_DHCP6_CONFIG);
 	}
 }
 
 static gboolean
-ip6_config_merge_and_apply (NMDevice *self,
-                            gboolean commit)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-	gboolean success;
-	NMIP6Config *composite;
-	gboolean ignore_auto_routes = FALSE;
-	gboolean ignore_auto_dns = FALSE;
-	gboolean ignore_default_routes = FALSE;
-	const char *token = NULL;
-	GSList *iter;
-
-	if (nm_device_sys_iface_state_is_external (self))
-		commit = 0;
-
-	/* Apply ignore-auto-routes and ignore-auto-dns settings */
-	connection = nm_device_get_applied_connection (self);
-	if (connection) {
-		NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection);
-
-		if (s_ip6) {
-			NMSettingIP6Config *ip6 = NM_SETTING_IP6_CONFIG (s_ip6);
-
-			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip6);
-			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip6);
-
-			/* if the connection has an explicit gateway, we also ignore
-			 * the default routes from other sources. */
-			ignore_default_routes =    nm_setting_ip_config_get_never_default (s_ip6)
-			                        || nm_setting_ip_config_get_gateway (s_ip6);
-
-			if (nm_setting_ip6_config_get_addr_gen_mode (ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64)
-				token = nm_setting_ip6_config_get_token (ip6);
-		}
-	}
-
-	composite = _ip6_config_new (self);
-	nm_ip6_config_set_privacy (composite,
-	                           priv->ndisc ?
-	                           priv->ndisc_use_tempaddr :
-	                           NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
-	init_ip6_config_dns_priority (self, composite);
-
-	if (commit) {
-		if (priv->queued_ip6_config_id)
-			update_ext_ip_config (self, AF_INET6, FALSE, FALSE);
-		ensure_con_ip6_config (self);
-	}
-
-	if (commit)
-		priv->default_route_metric_penalty_ip6_has = default_route_metric_penalty_detect (self);
-
-	/* Merge all the IP configs into the composite config */
-	if (priv->ac_ip6_config) {
-		nm_ip6_config_merge (composite, priv->ac_ip6_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET6));
-	}
-	if (priv->dhcp6.ip6_config) {
-		nm_ip6_config_merge (composite, priv->dhcp6.ip6_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET6));
-	}
-
-	for (iter = priv->vpn6_configs; iter; iter = iter->next)
-		nm_ip6_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
-
-	if (priv->ext_ip6_config)
-		nm_ip6_config_merge (composite, priv->ext_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
-
-	/* Merge WWAN config *last* to ensure modem-given settings overwrite
-	 * any external stuff set by pppd or other scripts.
-	 */
-	if (priv->wwan_ip6_config) {
-		nm_ip6_config_merge (composite, priv->wwan_ip6_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET6));
-	}
-
-	if (priv->rt6_temporary_not_available) {
-		const NMPObject *o;
-		GHashTableIter hiter;
-
-		g_hash_table_iter_init (&hiter, priv->rt6_temporary_not_available);
-		while (g_hash_table_iter_next (&hiter, (gpointer *) &o, NULL)) {
-			nm_ip6_config_add_route (composite,
-			                         NMP_OBJECT_CAST_IP6_ROUTE (o),
-			                         NULL);
-		}
-	}
-
-	/* Merge user overrides into the composite config. For assumed connections,
-	 * con_ip6_config is empty. */
-	if (priv->con_ip6_config) {
-		nm_ip6_config_merge (composite, priv->con_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT,
-		                     default_route_metric_penalty_get (self, AF_INET6));
-	}
-
-	if (commit) {
-		nm_ip6_config_add_dependent_routes (composite,
-		                                    nm_device_get_route_table (self, AF_INET6, TRUE),
-		                                    nm_device_get_route_metric (self, AF_INET6));
-	}
-
-	/* Allow setting MTU etc */
-	if (commit) {
-		NMUtilsIPv6IfaceId iid;
-
-		if (token && nm_utils_ipv6_interface_identifier_get_from_token (&iid, token)) {
-			nm_platform_link_set_ipv6_token (nm_device_get_platform (self),
-			                                 nm_device_get_ip_ifindex (self),
-			                                 iid);
-		}
-	}
-
-	success = nm_device_set_ip6_config (self, composite, commit);
-	g_object_unref (composite);
-	if (commit)
-		priv->v6_commit_first_time = FALSE;
-	return success;
-}
-
-static gboolean
 dhcp6_lease_change (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingsConnection *settings_connection;
 
-	if (priv->dhcp6.ip6_config == NULL) {
+	if (!applied_config_get_current (&priv->dhcp6.ip6_config)) {
 		_LOGW (LOGD_DHCP6, "failed to get DHCPv6 config for rebind");
 		return FALSE;
 	}
@@ -6730,7 +7437,7 @@ dhcp6_lease_change (NMDevice *self)
 	g_assert (settings_connection);
 
 	/* Apply the updated config */
-	if (!ip6_config_merge_and_apply (self, TRUE)) {
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE)) {
 		_LOGW (LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event");
 		return FALSE;
 	}
@@ -6746,53 +7453,17 @@ dhcp6_lease_change (NMDevice *self)
 }
 
 static gboolean
-dhcp6_restart_cb (gpointer user_data)
+dhcp6_grace_period_expired (gpointer user_data)
 {
 	NMDevice *self = user_data;
-	NMDevicePrivate *priv;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	priv->dhcp6.restart_id = 0;
-
-	if (!dhcp6_start (self, FALSE))
-		dhcp_schedule_restart (self, AF_INET6, NULL);
-
-	return FALSE;
-}
-
-static void
-dhcp_schedule_restart (NMDevice *self,
-                       int addr_family,
-                       const char *reason)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint tries_left;
-	char tries_str[255];
-
-	nm_assert_addr_family (addr_family);
-
-	tries_left =   (addr_family == AF_INET)
-	             ? priv->dhcp4.num_tries_left
-	             : priv->dhcp6.num_tries_left;
+	_LOGI (LOGD_DHCP6, "DHCPv6: grace period expired");
 
-	_LOGI ((addr_family == AF_INET) ? LOGD_DHCP4 : LOGD_DHCP6,
-	       "scheduling DHCPv%c restart in %u seconds%s%s%s%s",
-	       nm_utils_addr_family_to_char (addr_family),
-	       DHCP_RESTART_TIMEOUT,
-	       (tries_left != DHCP_NUM_TRIES_MAX)
-	         ? nm_sprintf_buf (tries_str, ", %u tries left", tries_left + 1)
-	         : "",
-	       NM_PRINT_FMT_QUOTED (reason, " (reason: ", reason, ")", ""));
+	nm_device_ip_method_failed (self, AF_INET6,
+	                            NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+	/* If the device didn't fail, the DHCP client will continue */
 
-	if (addr_family == AF_INET) {
-		priv->dhcp4.restart_id = g_timeout_add_seconds (DHCP_RESTART_TIMEOUT,
-		                                                dhcp4_restart_cb, self);
-	} else {
-		priv->dhcp6.restart_id = g_timeout_add_seconds (DHCP_RESTART_TIMEOUT,
-		                                                dhcp6_restart_cb, self);
-	}
+	return G_SOURCE_REMOVE;
 }
 
 static void
@@ -6801,51 +7472,57 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean is_dhcp_managed;
 
-	_LOGD (LOGD_DHCP6, "DHCPv6 failed: timeout %d, num tries left %u",
-           timeout, priv->dhcp6.num_tries_left);
+	_LOGD (LOGD_DHCP6, "DHCPv6 failed%s", timeout ? " (timeout)" : "");
 
 	is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
-	dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 
-	if (is_dhcp_managed || priv->dhcp6.num_tries_left < DHCP_NUM_TRIES_MAX) {
-		/* Don't fail if there are static addresses configured on
-		 * the device, instead retry after some time.
+	if (is_dhcp_managed) {
+		/* Keep client running if there are static addresses configured
+		 * on the interface.
 		 */
 		if (   priv->ip6_state == IP_DONE
-		    && priv->con_ip6_config
-		    && nm_ip6_config_get_num_addresses (priv->con_ip6_config)) {
-			dhcp_schedule_restart (self, AF_INET6, "device has IP addresses");
+		    && priv->con_ip_config_6
+		    && nm_ip6_config_get_num_addresses (priv->con_ip_config_6))
+			goto clear_config;
+
+		/* Fail the method in case of timeout or failure during initial
+		 * configuration.
+		 */
+		if (   !priv->dhcp6.was_active
+		    && (timeout || priv->ip6_state == IP_CONF)) {
+			dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
+			nm_device_activate_schedule_ip6_config_timeout (self);
 			return;
 		}
 
-		if (   priv->dhcp6.num_tries_left == DHCP_NUM_TRIES_MAX
-		    && (timeout || (priv->ip6_state == IP_CONF))
-		    && !priv->dhcp6.was_active)
-			nm_device_activate_schedule_ip6_config_timeout (self);
-		else if (   priv->dhcp6.num_tries_left < DHCP_NUM_TRIES_MAX
-		         || priv->ip6_state == IP_DONE
-		         || priv->dhcp6.was_active) {
-			/* Don't fail immediately when the lease expires but try to
-			 * restart DHCP for a predefined number of times.
-			 */
-			if (priv->dhcp6.num_tries_left) {
-				priv->dhcp6.num_tries_left--;
-				dhcp_schedule_restart (self, AF_INET6, "lease expired");
-			} else {
-				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-				/* We failed the ipv6 method but schedule again the retries if the ipv4 method is
-				 * configured, keeping the connection up.
-				 */
-				if (nm_device_get_state (self) != NM_DEVICE_STATE_FAILED)
-					dhcp_schedule_restart (self, AF_INET6, "renewal failed");
-			}
-		} else
-			g_warn_if_reached ();
+		/* In any other case (expired lease, assumed connection, etc.),
+		 * start a grace period in which we keep the client running,
+		 * hoping that it will regain a lease.
+		 */
+		if (!priv->dhcp6.grace_id) {
+			priv->dhcp6.grace_id = g_timeout_add_seconds (DHCP_GRACE_PERIOD_SEC,
+			                                              dhcp6_grace_period_expired,
+			                                              self);
+			_LOGI (LOGD_DHCP6,
+			       "DHCPv6: %u seconds grace period started",
+			       DHCP_GRACE_PERIOD_SEC);
+			goto clear_config;
+		}
 	} else {
 		/* not a hard failure; just live with the RA info */
+		dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 		if (priv->ip6_state == IP_CONF)
 			nm_device_activate_schedule_ip6_config_result (self);
 	}
+	return;
+
+clear_config:
+	/* The previous configuration is no longer valid */
+	if (priv->dhcp6.config) {
+		nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
+		priv->dhcp6.config = nm_dhcp6_config_new ();
+		_notify (self, PROP_DHCP6_CONFIG);
+	}
 }
 
 static void
@@ -6881,6 +7558,7 @@ dhcp6_state_changed (NMDhcpClient *client,
 
 	switch (state) {
 	case NM_DHCP_STATE_BOUND:
+		nm_clear_g_source (&priv->dhcp6.grace_id);
 		/* If the server sends multiple IPv6 addresses, we receive a state
 		 * changed event for each of them. Use the event ID to merge IPv6
 		 * addresses from the same transaction into a single configuration.
@@ -6893,16 +7571,16 @@ dhcp6_state_changed (NMDhcpClient *client,
 			const NMPlatformIP6Address *a;
 
 			nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6_config, &a)
-				nm_ip6_config_add_address (priv->dhcp6.ip6_config, a);
+				applied_config_add_address (&priv->dhcp6.ip6_config, NM_PLATFORM_IP_ADDRESS_CAST (a));
 		} else {
-			g_clear_object (&priv->dhcp6.ip6_config);
 			g_clear_pointer (&priv->dhcp6.event_id, g_free);
 			if (ip6_config) {
-				priv->dhcp6.ip6_config = g_object_ref (ip6_config);
+				applied_config_init (&priv->dhcp6.ip6_config, ip6_config);
 				priv->dhcp6.event_id = g_strdup (event_id);
 				nm_dhcp6_config_set_options (priv->dhcp6.config, options);
 				_notify (self, PROP_DHCP6_CONFIG);
-			}
+			} else
+				applied_config_clear (&priv->dhcp6.ip6_config);
 		}
 
 		/* After long time we have been able to renew the lease:
@@ -6911,10 +7589,8 @@ dhcp6_state_changed (NMDhcpClient *client,
 		if (priv->ip6_state == IP_FAIL)
 			_set_ip_state (self, AF_INET6, IP_CONF);
 
-		priv->dhcp6.num_tries_left = DHCP_NUM_TRIES_MAX;
-
 		if (priv->ip6_state == IP_CONF) {
-			if (priv->dhcp6.ip6_config == NULL) {
+			if (!applied_config_get_current (&priv->dhcp6.ip6_config)) {
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED);
 				break;
 			}
@@ -6965,34 +7641,32 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingIPConfig *s_ip6;
-	GByteArray *tmp = NULL;
-	const guint8 *hw_addr;
-	size_t hw_addr_len = 0;
+	gs_unref_bytes GBytes *hwaddr = NULL;
 	const NMPlatformIP6Address *ll_addr = NULL;
 
 	g_assert (connection);
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
 
-	if (priv->ext_ip6_config_captured)
-		ll_addr = nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE);
+	if (priv->ext_ip6_config_captured) {
+		ll_addr = nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
+		                                              NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+		                                            | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL);
+	}
 
 	if (!ll_addr) {
 		_LOGW (LOGD_DHCP6, "can't start DHCPv6: no link-local address");
 		return FALSE;
 	}
 
-	hw_addr = nm_platform_link_get_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), &hw_addr_len);
-	if (hw_addr_len) {
-		tmp = g_byte_array_sized_new (hw_addr_len);
-		g_byte_array_append (tmp, hw_addr, hw_addr_len);
-	}
+	hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self),
+	                                                nm_device_get_ip_ifindex (self));
 
 	priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
 	                                                nm_device_get_multi_index (self),
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
-	                                                tmp,
+	                                                hwaddr,
 	                                                &ll_addr->address,
 	                                                nm_connection_get_uuid (connection),
 	                                                nm_device_get_route_table (self, AF_INET6, TRUE),
@@ -7004,8 +7678,6 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	                                                (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
 	                                                nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6)),
 	                                                priv->dhcp6.needed_prefixes);
-	if (tmp)
-		g_byte_array_free (tmp, TRUE);
 
 	if (priv->dhcp6.client) {
 		priv->dhcp6.state_sigid = g_signal_connect (priv->dhcp6.client,
@@ -7031,11 +7703,11 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll)
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip6;
 
-	nm_exported_object_clear_and_unexport (&priv->dhcp6.config);
+	nm_dbus_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);
+	nm_assert (!applied_config_get_current (&priv->dhcp6.ip6_config));
+	applied_config_clear (&priv->dhcp6.ip6_config);
 	g_clear_pointer (&priv->dhcp6.event_id, g_free);
 
 	connection = nm_device_get_applied_connection (self);
@@ -7046,17 +7718,12 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll)
 		nm_device_add_pending_action (self, NM_PENDING_ACTION_DHCP6, TRUE);
 
 	if (wait_for_ll) {
-		NMActStageReturn ret;
-
 		/* ensure link local is ready... */
-		ret = linklocal6_start (self);
-		if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
-			/* success; wait for the LL address to show up */
+		if (!linklocal6_start (self)) {
+			/* wait for the LL address to show up */
 			return TRUE;
 		}
-
-		/* success; already have the LL address; kick off DHCP */
-		g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
+		/* already have the LL address; kick off DHCP */
 	}
 
 	if (!dhcp6_start_with_link_ready (self, connection))
@@ -7118,19 +7785,19 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatformIP6Address address = *subnet;
 
-	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = _ip6_config_new (self);
+	if (!applied_config_get_current (&priv->ac_ip6_config))
+		applied_config_init_new (&priv->ac_ip6_config, self, AF_INET6);
 
 	/* Assign a ::1 address in the subnet for us. */
 	address.address.s6_addr32[3] |= htonl (1);
-	nm_ip6_config_add_address (priv->ac_ip6_config, &address);
+	applied_config_add_address (&priv->ac_ip6_config, NM_PLATFORM_IP_ADDRESS_CAST (&address));
 
 	_LOGD (LOGD_IP6, "ipv6-pd: using %s address (preferred for %u seconds)",
 	       nm_utils_inet6_ntop (&address.address, NULL),
 	       subnet->preferred);
 
 	/* This also updates the ndisc if there are actual changes. */
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "ipv6-pd: failed applying IP6 config for connection sharing");
 }
 
@@ -7145,11 +7812,11 @@ nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device)
 	NMIP6Config *from_config = NULL;
 	guint i, len;
 
-	if (priv->ac_ip6_config) {
-		nm_ip6_config_reset_nameservers (priv->ac_ip6_config);
-		nm_ip6_config_reset_searches (priv->ac_ip6_config);
+	if (applied_config_get_current (&priv->ac_ip6_config)) {
+		applied_config_reset_nameservers (&priv->ac_ip6_config);
+		applied_config_reset_searches (&priv->ac_ip6_config);
 	} else
-		priv->ac_ip6_config = _ip6_config_new (self);
+		applied_config_init_new (&priv->ac_ip6_config, self, AF_INET6);
 
 	if (from_device)
 		from_config = nm_device_get_ip6_config (from_device);
@@ -7158,34 +7825,28 @@ nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device)
 
 	len = nm_ip6_config_get_num_nameservers (from_config);
 	for (i = 0; i < len; i++) {
-		nm_ip6_config_add_nameserver (priv->ac_ip6_config,
-		                              nm_ip6_config_get_nameserver (from_config, i));
+		applied_config_add_nameserver (&priv->ac_ip6_config,
+		                               (const NMIPAddr *) nm_ip6_config_get_nameserver (from_config, i));
 	}
 
 	len = nm_ip6_config_get_num_searches (from_config);
 	for (i = 0; i < len; i++) {
-		nm_ip6_config_add_search (priv->ac_ip6_config,
-		                          nm_ip6_config_get_search (from_config, i));
+		applied_config_add_search (&priv->ac_ip6_config,
+		                           nm_ip6_config_get_search (from_config, i));
 	}
 
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "ipv6-pd: failed applying DNS config for connection sharing");
 }
 
 /*****************************************************************************/
 
 static void
-linklocal6_cleanup (NMDevice *self)
+linklocal6_failed (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	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);
 }
 
@@ -7200,17 +7861,26 @@ linklocal6_timeout_cb (gpointer user_data)
 }
 
 static void
-linklocal6_complete (NMDevice *self)
+linklocal6_check_complete (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	const char *method;
 
-	g_assert (priv->linklocal6_timeout_id);
-	g_assert (priv->ext_ip6_config_captured);
-	g_assert (nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE));
+	if (!priv->linklocal6_timeout_id) {
+		/* we are not waiting for linklocal to complete. Nothing to do. */
+		return;
+	}
+
+	if (   !priv->ext_ip6_config_captured
+	    || !nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
+	                                            NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+	                                          | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) {
+		/* we don't have a non-tentative link local address yet. Wait longer. */
+		return;
+	}
 
-	linklocal6_cleanup (self);
+	nm_clear_g_source (&priv->linklocal6_timeout_id);
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -7220,12 +7890,9 @@ linklocal6_complete (NMDevice *self)
 	_LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses successful, continue with method %s", method);
 
 	if (   strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	    || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) {
-		if (!addrconf6_start_with_link_ready (self)) {
-			/* Time out IPv6 instead of failing the entire activation */
-			nm_device_activate_schedule_ip6_config_timeout (self);
-		}
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
+	    || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0)
+		addrconf6_start_with_link_ready (self);
+	else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
 		if (!dhcp6_start_with_link_ready (self, connection)) {
 			/* Time out IPv6 instead of failing the entire activation */
 			nm_device_activate_schedule_ip6_config_timeout (self);
@@ -7240,28 +7907,27 @@ static void
 check_and_add_ipv6ll_addr (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	struct in6_addr lladdr;
 	NMConnection *connection;
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
+	const char *addr_type;
 
-	if (priv->nm_ipv6ll == FALSE)
+	if (!priv->ipv6ll_handle)
 		return;
 
-	if (priv->ext_ip6_config_captured) {
-		NMDedupMultiIter ipconf_iter;
-		const NMPlatformIP6Address *addr;
-
-		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, priv->ext_ip6_config_captured, &addr) {
-			if (   IN6_IS_ADDR_LINKLOCAL (&addr->address)
-			    && !(addr->n_ifa_flags & IFA_F_DADFAILED)) {
-				/* Already have an LL address, nothing to do */
-				return;
-			}
-		}
+	if (   priv->ext_ip6_config_captured
+	    && nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
+	                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+	                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
+	                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) {
+		/* Already have an LL address, nothing to do */
+		return;
 	}
 
+	priv->ipv6ll_has = FALSE;
+	memset (&priv->ipv6ll_addr, 0, sizeof (priv->ipv6ll_addr));
+
 	memset (&lladdr, 0, sizeof (lladdr));
 	lladdr.s6_addr16[0] = htons (0xfe80);
 
@@ -7286,7 +7952,7 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 			linklocal6_failed (self);
 			return;
 		}
-		_LOGD (LOGD_IP6, "linklocal6: using IPv6 stable-privacy addressing");
+		addr_type = "stable-privacy";
 	} else {
 		NMUtilsIPv6IfaceId iid;
 
@@ -7302,37 +7968,30 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 			_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_identifier (&lladdr, iid);
+		addr_type = "EUI-64";
 	}
 
-	_LOGD (LOGD_IP6, "linklocal6: adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL));
-	if (!nm_platform_ip6_address_add (nm_device_get_platform (self),
-	                                  ip_ifindex,
-	                                  lladdr,
-	                                  64,
-	                                  in6addr_any,
-	                                  NM_PLATFORM_LIFETIME_PERMANENT,
-	                                  NM_PLATFORM_LIFETIME_PERMANENT,
-	                                  0)) {
-		_LOGW (LOGD_IP6, "failed to add IPv6 link-local address %s",
-		       nm_utils_inet6_ntop (&lladdr, NULL));
-	}
+	_LOGD (LOGD_IP6, "linklocal6: generated %s IPv6LL address %s", addr_type, nm_utils_inet6_ntop (&lladdr, NULL));
+	priv->ipv6ll_has = TRUE;
+	priv->ipv6ll_addr = lladdr;
+	ip_config_merge_and_apply (self, AF_INET6, TRUE);
 }
 
-static NMActStageReturn
+static gboolean
 linklocal6_start (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	const char *method;
 
-	linklocal6_cleanup (self);
+	nm_clear_g_source (&priv->linklocal6_timeout_id);
 
 	if (   priv->ext_ip6_config_captured
-	    && nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE))
-		return NM_ACT_STAGE_RETURN_SUCCESS;
+	    && nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
+	                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+	                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
+		return TRUE;
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -7348,8 +8007,7 @@ linklocal6_start (NMDevice *self)
 	 * (rh #1101809)
 	 */
 	priv->linklocal6_timeout_id = g_timeout_add_seconds (15, linklocal6_timeout_cb, self);
-
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return FALSE;
 }
 
 /*****************************************************************************/
@@ -7414,7 +8072,7 @@ _set_mtu (NMDevice *self, guint32 mtu)
 
 	if (priv->master) {
 		/* changing the MTU of a slave, might require the master to reset
-		 * it's MTU. Note that the master usually cannot set a MTU larger
+		 * its MTU. Note that the master usually cannot set a MTU larger
 		 * then the slave's. Hence, when the slave increases the MTU,
 		 * master might want to retry setting the MTU. */
 		nm_device_commit_mtu (priv->master);
@@ -7591,7 +8249,7 @@ nm_device_commit_mtu (NMDevice *self)
 	if (   state >= NM_DEVICE_STATE_CONFIG
 	    && state < NM_DEVICE_STATE_DEACTIVATING) {
 		_LOGT (LOGD_DEVICE, "mtu: commit-mtu...");
-		_commit_mtu (self, NM_DEVICE_GET_PRIVATE (self)->ip4_config);
+		_commit_mtu (self, NM_DEVICE_GET_PRIVATE (self)->ip_config_4);
 	} else
 		_LOGT (LOGD_DEVICE, "mtu: commit-mtu... skip due to state %s", nm_device_state_to_str (state));
 }
@@ -7603,10 +8261,10 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	guint i;
 
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
-	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = _ip6_config_new (self);
+	if (!applied_config_get_current (&priv->ac_ip6_config))
+		applied_config_init_new (&priv->ac_ip6_config, self, AF_INET6);
 
 	if (changed & NM_NDISC_CONFIG_ADDRESSES) {
 		guint8 plen;
@@ -7627,16 +8285,23 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		} else
 			plen = 128;
 
-		nm_ip6_config_reset_addresses_ndisc (priv->ac_ip6_config,
+		nm_ip6_config_reset_addresses_ndisc ((NMIP6Config *) priv->ac_ip6_config.orig,
 		                                     rdata->addresses,
 		                                     rdata->addresses_n,
 		                                     plen,
 		                                     ifa_flags);
+		if (priv->ac_ip6_config.current) {
+			nm_ip6_config_reset_addresses_ndisc ((NMIP6Config *) priv->ac_ip6_config.current,
+			                                     rdata->addresses,
+			                                     rdata->addresses_n,
+			                                     plen,
+			                                     ifa_flags);
+		}
 	}
 
 	if (NM_FLAGS_ANY (changed,   NM_NDISC_CONFIG_ROUTES
 	                           | NM_NDISC_CONFIG_GATEWAYS)) {
-		nm_ip6_config_reset_routes_ndisc (priv->ac_ip6_config,
+		nm_ip6_config_reset_routes_ndisc ((NMIP6Config *) priv->ac_ip6_config.orig,
 		                                  rdata->gateways,
 		                                  rdata->gateways_n,
 		                                  rdata->routes,
@@ -7645,22 +8310,34 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		                                  nm_device_get_route_metric (self, AF_INET6),
 		                                  nm_platform_check_kernel_support (nm_device_get_platform (self),
 		                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
+		if (priv->ac_ip6_config.current) {
+			nm_ip6_config_reset_routes_ndisc ((NMIP6Config *) priv->ac_ip6_config.current,
+			                                  rdata->gateways,
+			                                  rdata->gateways_n,
+			                                  rdata->routes,
+			                                  rdata->routes_n,
+			                                  nm_device_get_route_table (self, AF_INET6, TRUE),
+			                                  nm_device_get_route_metric (self, AF_INET6),
+			                                  nm_platform_check_kernel_support (nm_device_get_platform (self),
+			                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
+		}
+
 	}
 
 	if (changed & NM_NDISC_CONFIG_DNS_SERVERS) {
 		/* Rebuild DNS server list from neighbor discovery cache. */
-		nm_ip6_config_reset_nameservers (priv->ac_ip6_config);
+		applied_config_reset_nameservers (&priv->ac_ip6_config);
 
 		for (i = 0; i < rdata->dns_servers_n; i++)
-			nm_ip6_config_add_nameserver (priv->ac_ip6_config, &rdata->dns_servers[i].address);
+			applied_config_add_nameserver (&priv->ac_ip6_config, (const NMIPAddr *) &rdata->dns_servers[i].address);
 	}
 
 	if (changed & NM_NDISC_CONFIG_DNS_DOMAINS) {
 		/* Rebuild domain list from neighbor discovery cache. */
-		nm_ip6_config_reset_searches (priv->ac_ip6_config);
+		applied_config_reset_searches (&priv->ac_ip6_config);
 
 		for (i = 0; i < rdata->dns_domains_n; i++)
-			nm_ip6_config_add_search (priv->ac_ip6_config, rdata->dns_domains[i].domain);
+			applied_config_add_search (&priv->ac_ip6_config, rdata->dns_domains[i].domain);
 	}
 
 	if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) {
@@ -7710,16 +8387,21 @@ ndisc_ra_timeout (NMNDisc *ndisc, NMDevice *self)
 		 * ever receive one, then time out IPv6.  But if there is other
 		 * IPv6 configuration, like manual IPv6 addresses or external IPv6
 		 * config, consider that sufficient for IPv6 success.
+		 *
+		 * FIXME: it doesn't seem correct to determine this based on which
+		 *        addresses we find inside priv->ip_config_6.
 		 */
-		if (   priv->ip6_config
-		    && nm_ip6_config_get_address_first_nontentative (priv->ip6_config, FALSE))
+		if (   priv->ip_config_6
+		    && nm_ip6_config_find_first_address (priv->ip_config_6,
+		                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
+		                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))
 			nm_device_activate_schedule_ip6_config_result (self);
 		else
 			nm_device_activate_schedule_ip6_config_timeout (self);
 	}
 }
 
-static gboolean
+static void
 addrconf6_start_with_link_ready (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -7737,12 +8419,10 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	}
 
 	/* Apply any manual configuration before starting RA */
-	if (!ip6_config_merge_and_apply (self, TRUE)) {
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "failed to apply manual IPv6 configuration");
-		g_clear_object (&priv->con_ip6_config);
-	}
 
-	/* XXX: These sysctls would probably be better set by the lndp ndisc itself. */
+	/* FIXME: These sysctls would probably be better set by the lndp ndisc itself. */
 	switch (nm_ndisc_get_node_type (priv->ndisc)) {
 	case NM_NDISC_NODE_TYPE_HOST:
 		/* Accepting prefixes from discovered routers. */
@@ -7773,7 +8453,7 @@ addrconf6_start_with_link_ready (NMDevice *self)
 
 	ndisc_set_router_config (priv->ndisc, self);
 	nm_ndisc_start (priv->ndisc);
-	return TRUE;
+	return;
 }
 
 static NMNDiscNodeType
@@ -7796,7 +8476,6 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
-	NMActStageReturn ret;
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
 	NMUtilsStableType stable_type;
@@ -7805,11 +8484,8 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
-	g_warn_if_fail (priv->ac_ip6_config == NULL);
-	if (priv->ac_ip6_config) {
-		g_object_unref (priv->ac_ip6_config);
-		priv->ac_ip6_config = NULL;
-	}
+	nm_assert (!applied_config_get_current (&priv->ac_ip6_config));
+	applied_config_clear (&priv->ac_ip6_config);
 
 	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
@@ -7846,15 +8522,14 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 		nm_device_add_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, TRUE);
 
 	/* ensure link local is ready... */
-	ret = linklocal6_start (self);
-	if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
-		/* success; wait for the LL address to show up */
+	if (!linklocal6_start (self)) {
+		/* wait for the LL address to show up */
 		return TRUE;
 	}
 
-	/* success; already have the LL address; kick off neighbor discovery */
-	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
-	return addrconf6_start_with_link_ready (self);
+	/* already have the LL address; kick off neighbor discovery */
+	addrconf6_start_with_link_ready (self);
+	return TRUE;
 }
 
 static void
@@ -7867,7 +8542,7 @@ addrconf6_cleanup (NMDevice *self)
 
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE);
 
-	g_clear_object (&priv->ac_ip6_config);
+	applied_config_clear (&priv->ac_ip6_config);
 	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 	g_clear_object (&priv->ndisc);
@@ -7921,7 +8596,8 @@ restore_ip6_properties (NMDevice *self)
 	g_hash_table_iter_init (&iter, priv->ip6_saved_properties);
 	while (g_hash_table_iter_next (&iter, &key, &value)) {
 		/* Don't touch "disable_ipv6" if we're doing userland IPv6LL */
-		if (priv->nm_ipv6ll && strcmp (key, "disable_ipv6") == 0)
+		if (   priv->ipv6ll_handle
+		    && nm_streq (key, "disable_ipv6"))
 			continue;
 		nm_device_ipv6_sysctl_set (self, key, value);
 	}
@@ -7931,7 +8607,7 @@ static inline void
 set_disable_ipv6 (NMDevice *self, const char *value)
 {
 	/* We only touch disable_ipv6 when NM is not managing the IPv6LL address */
-	if (NM_DEVICE_GET_PRIVATE (self)->nm_ipv6ll == FALSE)
+	if (!NM_DEVICE_GET_PRIVATE (self)->ipv6ll_handle)
 		nm_device_ipv6_sysctl_set (self, "disable_ipv6", value);
 }
 
@@ -7946,7 +8622,7 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 	                                       NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
 		return;
 
-	priv->nm_ipv6ll = enable;
+	priv->ipv6ll_handle = enable;
 	if (ifindex > 0) {
 		NMPlatformError plerr;
 		const char *detail = enable ? "enable" : "disable";
@@ -8090,21 +8766,19 @@ act_stage3_ip6_config_start (NMDevice *self,
 	}
 
 	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
-	priv->dhcp6.num_tries_left = DHCP_NUM_TRIES_MAX;
-
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
 		if (   !priv->master
 		    && !nm_device_sys_iface_state_is_external (self)) {
-			gboolean old_nm_ipv6ll = priv->nm_ipv6ll;
+			gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
 
 			/* When activating an IPv6 'ignore' connection we need to revert back
 			 * to kernel IPv6LL, but the kernel won't actually assign an address
 			 * to the interface until disable_ipv6 is bounced.
 			 */
 			set_nm_ipv6ll (self, FALSE);
-			if (old_nm_ipv6ll == TRUE)
+			if (ipv6ll_handle_old)
 				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
 			restore_ip6_properties (self);
 		}
@@ -8115,7 +8789,7 @@ act_stage3_ip6_config_start (NMDevice *self,
 	 * expose any ipv6 sysctls or allow presence of any addresses on the interface,
 	 * including LL, which * would make it impossible to autoconfigure MTU to a
 	 * correct value. */
-	_commit_mtu (self, priv->ip4_config);
+	_commit_mtu (self, priv->ip_config_4);
 
 	/* Any method past this point requires an IPv6LL address. Use NM-controlled
 	 * IPv6LL if this is not an assumed connection, since assumed connections
@@ -8136,7 +8810,6 @@ act_stage3_ip6_config_start (NMDevice *self,
 	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
 	                                                       nm_device_get_platform (self),
 	                                                       nm_device_get_ip_ifindex (self),
-	                                                       FALSE,
 	                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 
 	ip6_privacy = _ip6_privacy_get (self);
@@ -8149,7 +8822,9 @@ act_stage3_ip6_config_start (NMDevice *self,
 		} else
 			ret = NM_ACT_STAGE_RETURN_POSTPONE;
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) {
-		ret = linklocal6_start (self);
+		ret = linklocal6_start (self)
+		      ? NM_ACT_STAGE_RETURN_SUCCESS
+		      : NM_ACT_STAGE_RETURN_POSTPONE;
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
 		priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
 		if (!dhcp6_start (self, TRUE)) {
@@ -8259,8 +8934,8 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 		/* Here we get a static IPv6 config, like for Shared where it's
 		 * autogenerated or from modems where it comes from ModemManager.
 		 */
-		g_warn_if_fail (priv->ac_ip6_config == NULL);
-		priv->ac_ip6_config = ip6_config;
+		nm_assert (!applied_config_get_current (&priv->ac_ip6_config));
+		applied_config_init (&priv->ac_ip6_config, ip6_config);
 		nm_device_activate_schedule_ip6_config_result (self);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
 		_set_ip_state (self, AF_INET6, IP_DONE);
@@ -8398,7 +9073,7 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	/* Add the interface to the specified firewall zone */
 	if (priv->fw_state == FIREWALL_STATE_UNMANAGED) {
@@ -8470,7 +9145,7 @@ nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	activation_source_schedule (self, activate_stage4_ip4_config_timeout, AF_INET);
 }
@@ -8526,7 +9201,7 @@ nm_device_activate_schedule_ip6_config_timeout (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	activation_source_schedule (self, activate_stage4_ip6_config_timeout, AF_INET6);
 }
@@ -8644,9 +9319,9 @@ arp_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->arping.announcing) {
-		nm_arping_manager_destroy (priv->arping.announcing);
-		priv->arping.announcing = NULL;
+	if (priv->acd.announcing) {
+		nm_acd_manager_destroy (priv->acd.announcing);
+		priv->acd.announcing = NULL;
 	}
 }
 
@@ -8666,7 +9341,7 @@ arp_announce (NMDevice *self)
 	                                        nm_device_get_ip_ifindex (self),
 	                                        &hw_addr_len);
 
-	if (!hw_addr_len || !hw_addr)
+	if (!hw_addr || hw_addr_len != ETH_ALEN)
 		return;
 
 	/* We only care about manually-configured addresses; DHCP- and autoip-configured
@@ -8682,19 +9357,19 @@ arp_announce (NMDevice *self)
 	if (num == 0)
 		return;
 
-	priv->arping.announcing = nm_arping_manager_new (nm_device_get_ip_ifindex (self));
+	priv->acd.announcing = nm_acd_manager_new (nm_device_get_ip_ifindex (self), hw_addr, hw_addr_len);
 
 	for (i = 0; i < num; i++) {
 		NMIPAddress *ip = nm_setting_ip_config_get_address (s_ip4, i);
 		in_addr_t addr;
 
 		if (inet_pton (AF_INET, nm_ip_address_get_address (ip), &addr) == 1)
-			nm_arping_manager_add_address (priv->arping.announcing, addr);
+			nm_acd_manager_add_address (priv->acd.announcing, addr);
 		else
 			g_warn_if_reached ();
 	}
 
-	nm_arping_manager_announce_addresses (priv->arping.announcing);
+	nm_acd_manager_announce_addresses (priv->acd.announcing);
 }
 
 static void
@@ -8719,8 +9394,7 @@ activate_stage5_ip4_config_result (NMDevice *self)
 			_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 	}
 
-	/* NULL to use the existing priv->dev_ip4_config */
-	if (!ip4_config_merge_and_apply (self, TRUE)) {
+	if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 		_LOGD (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Commit) failed");
 		nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 		return;
@@ -8732,7 +9406,7 @@ activate_stage5_ip4_config_result (NMDevice *self)
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
 		gs_free_error GError *error = NULL;
 
-		if (!start_sharing (self, priv->ip4_config, &error)) {
+		if (!start_sharing (self, priv->ip_config_4, &error)) {
 			_LOGW (LOGD_SHARING, "Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed: %s", error->message);
 			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
 			return;
@@ -8768,10 +9442,7 @@ nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *conf
 	g_return_if_fail (NM_IS_DEVICE (self));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	g_clear_object (&priv->dev_ip4_config);
-	if (config)
-		priv->dev_ip4_config = g_object_ref (config);
-
+	applied_config_init (&priv->dev_ip4_config, config);
 	activation_source_schedule (self, activate_stage5_ip4_config_result, AF_INET);
 }
 
@@ -8796,6 +9467,32 @@ nm_device_activate_ip4_state_done (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_DONE;
 }
 
+static void
+dad6_add_pending_address (NMDevice *self,
+                          NMPlatform *platform,
+                          int ifindex,
+                          const struct in6_addr *address,
+                          NMIP6Config **dad6_config)
+{
+	const NMPlatformIP6Address *pl_addr;
+
+	pl_addr = nm_platform_ip6_address_get (platform,
+	                                       ifindex,
+	                                       *address);
+	if (   pl_addr
+	    && NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_TENTATIVE)
+	    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_DADFAILED)
+	    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
+		_LOGt (LOGD_DEVICE, "IPv6 DAD: pending address %s",
+		       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
+
+		if (!*dad6_config)
+			*dad6_config = _ip6_config_new (self);
+
+		nm_ip6_config_add_address (*dad6_config, pl_addr);
+	}
+}
+
 /*
  * Returns a NMIP6Config containing NM-configured addresses which
  * have the tentative flag, or NULL if none is present.
@@ -8804,42 +9501,43 @@ static NMIP6Config *
 dad6_get_pending_addresses (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMIP6Config *confs[] = { priv->ac_ip6_config,
-	                         priv->dhcp6.ip6_config,
-	                         priv->con_ip6_config,
-	                         priv->wwan_ip6_config };
-	const NMPlatformIP6Address *addr, *pl_addr;
+	NMIP6Config *confs[] = { (NMIP6Config *) applied_config_get_current (&priv->ac_ip6_config),
+	                         (NMIP6Config *) applied_config_get_current (&priv->dhcp6.ip6_config),
+	                         priv->con_ip_config_6,
+	                         (NMIP6Config *) applied_config_get_current (&priv->wwan_ip_config_6) };
+	const NMPlatformIP6Address *addr;
 	NMIP6Config *dad6_config = NULL;
 	NMDedupMultiIter ipconf_iter;
 	guint i;
 	int ifindex;
+	NMPlatform *platform;
 
 	ifindex = nm_device_get_ip_ifindex (self);
 	g_return_val_if_fail (ifindex > 0, NULL);
 
+	platform = nm_device_get_platform (self);
+
+	if (priv->ipv6ll_has) {
+		dad6_add_pending_address (self,
+		                          platform,
+		                          ifindex,
+		                          &priv->ipv6ll_addr,
+		                          &dad6_config);
+	}
+
 	/* We are interested only in addresses that we have explicitly configured,
 	 * not in externally added ones.
 	 */
 	for (i = 0; i < G_N_ELEMENTS (confs); i++) {
-		if (confs[i]) {
-
-			nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, confs[i], &addr) {
-				pl_addr = nm_platform_ip6_address_get (nm_device_get_platform (self),
-				                                       ifindex,
-				                                       addr->address);
-				if (   pl_addr
-				    && NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_TENTATIVE)
-				    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_DADFAILED)
-				    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
-					_LOGt (LOGD_DEVICE, "IPv6 DAD: pending address %s",
-					       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
-
-					if (!dad6_config)
-						dad6_config = _ip6_config_new (self);
-
-					nm_ip6_config_add_address (dad6_config, pl_addr);
-				}
-			}
+		if (!confs[i])
+			continue;
+
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, confs[i], &addr) {
+			dad6_add_pending_address (self,
+			                          platform,
+			                          ifindex,
+			                          &addr->address,
+			                          &dad6_config);
 		}
 	}
 
@@ -8871,10 +9569,10 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 			_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 	}
 
-	if (ip6_config_merge_and_apply (self, TRUE)) {
+	if (ip_config_merge_and_apply (self, AF_INET6, TRUE)) {
 		if (   priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
 		    && priv->ip6_state == IP_CONF) {
-			if (priv->dhcp6.ip6_config) {
+			if (applied_config_get_current (&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.
 				 */
@@ -8961,43 +9659,26 @@ nm_device_activate_ip6_state_done (NMDevice *self)
 /*****************************************************************************/
 
 static void
-act_request_set_cb (NMActRequest *act_request,
-                    GParamSpec *pspec,
-                    NMDevice *self)
-{
-	_notify (self, PROP_ACTIVE_CONNECTION);
-}
-
-static void
 act_request_set (NMDevice *self, NMActRequest *act_request)
 {
 	NMDevicePrivate *priv;
-	gs_unref_object NMActRequest *old_act_requst = NULL;
 
 	nm_assert (NM_IS_DEVICE (self));
 	nm_assert (!act_request || NM_IS_ACT_REQUEST (act_request));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (   !priv->act_request_public
-	    && priv->act_request == act_request)
+	if (   !priv->act_request.visible
+	    && priv->act_request.obj == act_request)
 		return;
 
 	/* always clear the public flag. The few callers that set a new @act_request
 	 * don't want that the property is public yet.  */
-	priv->act_request_public = FALSE;
-
-	nm_clear_g_signal_handler (priv->act_request, &priv->act_request_id);
-
-	old_act_requst = priv->act_request;
-	priv->act_request = nm_g_object_ref (act_request);
+	nm_dbus_track_obj_path_set (&priv->act_request,
+	                            act_request,
+	                            FALSE);
 
 	if (act_request) {
-		priv->act_request_id = g_signal_connect (act_request,
-		                                         "notify::"NM_EXPORTED_OBJECT_PATH,
-		                                         G_CALLBACK (act_request_set_cb),
-		                                         self);
-
 		switch (nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (act_request))) {
 		case NM_ACTIVATION_TYPE_EXTERNAL:
 			break;
@@ -9014,8 +9695,6 @@ act_request_set (NMDevice *self, NMActRequest *act_request)
 			break;
 		}
 	}
-
-	_notify (self, PROP_ACTIVE_CONNECTION);
 }
 
 static void
@@ -9041,9 +9720,9 @@ _update_ip4_address (NMDevice *self)
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	if (   priv->ip4_config
+	if (   priv->ip_config_4
 	    && ip_config_valid (priv->state)
-	    && (address = nm_ip4_config_get_first_address (priv->ip4_config))) {
+	    && (address = nm_ip4_config_get_first_address (priv->ip_config_4))) {
 		if (address->address != priv->ip4_address) {
 			priv->ip4_address = address->address;
 			_notify (self, PROP_IP4_ADDRESS);
@@ -9137,37 +9816,31 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
 }
 
 static void
-_cleanup_ip4_pre (NMDevice *self, CleanupType cleanup_type)
+_cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	_set_ip_state (self, AF_INET, IP_NONE);
-
-	if (nm_clear_g_source (&priv->queued_ip4_config_id))
-		_LOGD (LOGD_DEVICE, "clearing queued IP4 config change");
-	priv->queued_ip4_config_pending = FALSE;
-
-	dhcp4_cleanup (self, cleanup_type, FALSE);
-	arp_cleanup (self);
-	dnsmasq_cleanup (self);
-	ipv4ll_cleanup (self);
-}
+	_set_ip_state (self, addr_family, IP_NONE);
 
-static void
-_cleanup_ip6_pre (NMDevice *self, CleanupType cleanup_type)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	_set_ip_state (self, AF_INET6, IP_NONE);
-
-	if (nm_clear_g_source (&priv->queued_ip6_config_id))
-		_LOGD (LOGD_DEVICE, "clearing queued IP6 config change");
-	priv->queued_ip6_config_pending = FALSE;
+	if (nm_clear_g_source (&priv->queued_ip_config_id_x[IS_IPv4])) {
+		_LOGD (LOGD_DEVICE, "clearing queued IP%c config change",
+		       nm_utils_addr_family_to_char (addr_family));
+	}
 
-	g_clear_object (&priv->dad6_ip6_config);
-	dhcp6_cleanup (self, cleanup_type, FALSE);
-	linklocal6_cleanup (self);
-	addrconf6_cleanup (self);
+	if (IS_IPv4) {
+		priv->queued_ip4_config_pending = FALSE;
+		dhcp4_cleanup (self, cleanup_type, FALSE);
+		arp_cleanup (self);
+		dnsmasq_cleanup (self);
+		ipv4ll_cleanup (self);
+	} else {
+		priv->queued_ip6_config_pending = FALSE;
+		g_clear_object (&priv->dad6_ip6_config);
+		dhcp6_cleanup (self, cleanup_type, FALSE);
+		nm_clear_g_source (&priv->linklocal6_timeout_id);
+		addrconf6_cleanup (self);
+	}
 }
 
 gboolean
@@ -9186,7 +9859,7 @@ _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
 		gs_unref_hashtable GHashTable *check_dups = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL);
 
 		for (i = 0; argv[i]; i++) {
-			if (!nm_g_hash_table_add (check_dups, (char *) argv[i]))
+			if (!g_hash_table_add (check_dups, (char *) argv[i]))
 				nm_assert (FALSE);
 		}
 		nm_assert (g_hash_table_size (check_dups) > 0);
@@ -9237,8 +9910,7 @@ _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
 void
 nm_device_reactivate_ip4_config (NMDevice *self,
                                  NMSettingIPConfig *s_ip4_old,
-                                 NMSettingIPConfig *s_ip4_new,
-                                 gboolean force_restart)
+                                 NMSettingIPConfig *s_ip4_new)
 {
 	NMDevicePrivate *priv;
 	const char *method_old, *method_new;
@@ -9247,31 +9919,31 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->ip4_state != IP_NONE) {
-		g_clear_object (&priv->con_ip4_config);
-		g_clear_object (&priv->ext_ip4_config);
-		priv->con_ip4_config = _ip4_config_new (self);
-		nm_ip4_config_merge_setting (priv->con_ip4_config,
+		g_clear_object (&priv->con_ip_config_4);
+		g_clear_object (&priv->ext_ip_config_4);
+		g_clear_object (&priv->dev_ip4_config.current);
+		g_clear_object (&priv->wwan_ip_config_4.current);
+		priv->con_ip_config_4 = _ip4_config_new (self);
+		nm_ip4_config_merge_setting (priv->con_ip_config_4,
 		                             s_ip4_new,
+		                             _get_mdns (self),
 		                             nm_device_get_route_table (self, AF_INET, TRUE),
 		                             nm_device_get_route_metric (self, AF_INET));
 
-		if (!force_restart) {
-			method_old = s_ip4_old
-			             ? nm_setting_ip_config_get_method (s_ip4_old)
-			             : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-			method_new = s_ip4_new
-			             ? nm_setting_ip_config_get_method (s_ip4_new)
-			             : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-			force_restart = !nm_streq0 (method_old, method_new);
-		}
+		method_old = s_ip4_old
+		             ? nm_setting_ip_config_get_method (s_ip4_old)
+		             : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+		method_new = s_ip4_new
+		             ? nm_setting_ip_config_get_method (s_ip4_new)
+		             : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
 
-		if (force_restart) {
-			_cleanup_ip4_pre (self, CLEANUP_TYPE_DECONFIGURE);
+		if (!nm_streq0 (method_old, method_new)) {
+			_cleanup_ip_pre (self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
 			_set_ip_state (self, AF_INET, IP_WAIT);
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
 		} else {
-			if (!ip4_config_merge_and_apply (self, TRUE))
+			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 				_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
 		}
 	}
@@ -9280,8 +9952,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 void
 nm_device_reactivate_ip6_config (NMDevice *self,
                                  NMSettingIPConfig *s_ip6_old,
-                                 NMSettingIPConfig *s_ip6_new,
-                                 gboolean force_restart)
+                                 NMSettingIPConfig *s_ip6_new)
 {
 	NMDevicePrivate *priv;
 	const char *method_old, *method_new;
@@ -9290,31 +9961,34 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->ip6_state != IP_NONE) {
-		g_clear_object (&priv->con_ip6_config);
-		g_clear_object (&priv->ext_ip6_config);
-		priv->con_ip6_config = _ip6_config_new (self);
-		nm_ip6_config_merge_setting (priv->con_ip6_config,
+		g_clear_object (&priv->con_ip_config_6);
+		g_clear_object (&priv->ext_ip_config_6);
+		g_clear_object (&priv->ac_ip6_config.current);
+		g_clear_object (&priv->dhcp6.ip6_config.current);
+		g_clear_object (&priv->wwan_ip_config_6.current);
+		if (   priv->ipv6ll_handle
+		    && !IN6_IS_ADDR_UNSPECIFIED (&priv->ipv6ll_addr))
+			priv->ipv6ll_has = TRUE;
+		priv->con_ip_config_6 = _ip6_config_new (self);
+		nm_ip6_config_merge_setting (priv->con_ip_config_6,
 		                             s_ip6_new,
 		                             nm_device_get_route_table (self, AF_INET6, TRUE),
 		                             nm_device_get_route_metric (self, AF_INET6));
 
-		if (!force_restart) {
-			method_old = s_ip6_old
-			             ? nm_setting_ip_config_get_method (s_ip6_old)
-			             : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-			method_new = s_ip6_new
-			             ? nm_setting_ip_config_get_method (s_ip6_new)
-			             : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-			force_restart = !nm_streq0 (method_old, method_new);
-		}
+		method_old = s_ip6_old
+		             ? nm_setting_ip_config_get_method (s_ip6_old)
+		             : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
+		method_new = s_ip6_new
+		             ? nm_setting_ip_config_get_method (s_ip6_new)
+		             : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
 
-		if (force_restart) {
-			_cleanup_ip6_pre (self, CLEANUP_TYPE_DECONFIGURE);
+		if (!nm_streq0 (method_old, method_new)) {
+			_cleanup_ip_pre (self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
 			_set_ip_state (self, AF_INET6, IP_WAIT);
 			if (!nm_device_activate_stage3_ip6_start (self))
 				_LOGW (LOGD_IP6, "Failed to apply IPv6 configuration");
 		} else {
-			if (!ip6_config_merge_and_apply (self, TRUE))
+			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 				_LOGW (LOGD_IP4, "Failed to reapply IPv6 configuration");
 		}
 	}
@@ -9359,7 +10033,7 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 		 * allowed to differ.
 		 *
 		 * This includes UUID, there is no principal problem with reapplying a
-		 * connection and changing it's UUID. In fact, disallowing it makes it
+		 * connection and changing its UUID. In fact, disallowing it makes it
 		 * cumbersome for the user to reapply any connection but the original
 		 * settings-connection. */
 		return nm_device_hash_check_invalid_keys (diffs,
@@ -9485,7 +10159,7 @@ check_and_reapply_connection (NMDevice *self,
 	}
 
 	if (   version_id != 0
-	    && version_id != nm_active_connection_version_id_get ((NMActiveConnection *) priv->act_request)) {
+	    && version_id != nm_active_connection_version_id_get ((NMActiveConnection *) priv->act_request.obj)) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_VERSION_ID_MISMATCH,
@@ -9498,10 +10172,10 @@ check_and_reapply_connection (NMDevice *self,
 	 *************************************************************************/
 
 	if (diffs)
-		nm_active_connection_version_id_bump ((NMActiveConnection *) priv->act_request);
+		nm_active_connection_version_id_bump ((NMActiveConnection *) priv->act_request.obj);
 
 	_LOGD (LOGD_DEVICE, "reapply (version-id %llu%s)",
-	       (unsigned long long) nm_active_connection_version_id_get (((NMActiveConnection *) priv->act_request)),
+	       (unsigned long long) nm_active_connection_version_id_get (((NMActiveConnection *) priv->act_request.obj)),
 	       diffs ? "" : " (unmodified)");
 
 	if (diffs) {
@@ -9557,8 +10231,8 @@ check_and_reapply_connection (NMDevice *self,
 	s_ip6_old = nm_connection_get_setting_ip6_config (con_old);
 	s_ip6_new = nm_connection_get_setting_ip6_config (con_new);
 
-	nm_device_reactivate_ip4_config (self, s_ip4_old, s_ip4_new, TRUE);
-	nm_device_reactivate_ip6_config (self, s_ip6_old, s_ip6_new, TRUE);
+	nm_device_reactivate_ip4_config (self, s_ip4_old, s_ip4_new);
+	nm_device_reactivate_ip6_config (self, s_ip6_old, s_ip6_new);
 
 	reactivate_proxy_config (self);
 
@@ -9627,25 +10301,33 @@ reapply_cb (NMDevice *self,
 }
 
 static void
-impl_device_reapply (NMDevice *self,
-                     GDBusMethodInvocation *context,
-                     GVariant *settings,
-                     guint64 version_id,
-                     guint32 flags)
+impl_device_reapply (NMDBusObject *obj,
+                     const NMDBusInterfaceInfoExtended *interface_info,
+                     const NMDBusMethodInfoExtended *method_info,
+                     GDBusConnection *dbus_connection,
+                     const char *sender,
+                     GDBusMethodInvocation *invocation,
+                     GVariant *parameters)
 {
+	NMDevice *self = NM_DEVICE (obj);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingsConnection *settings_connection;
 	NMConnection *connection = NULL;
 	GError *error = NULL;
 	ReapplyData *reapply_data;
+	gs_unref_variant GVariant *settings = NULL;
+	guint64 version_id;
+	guint32 flags;
+
+	g_variant_get (parameters, "(@a{sa{sv}}tu)", &settings, &version_id, &flags);
 
 	/* No flags supported as of now. */
 	if (flags != 0) {
 		error = g_error_new_literal (NM_DEVICE_ERROR,
 		                             NM_DEVICE_ERROR_FAILED,
 		                             "Invalid flags specified");
-		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, context, error->message);
-		g_dbus_method_invocation_take_error (context, error);
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, invocation, error->message);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
@@ -9653,8 +10335,8 @@ impl_device_reapply (NMDevice *self,
 		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, NULL, context, error->message);
-		g_dbus_method_invocation_take_error (context, error);
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, invocation, error->message);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
@@ -9669,8 +10351,8 @@ impl_device_reapply (NMDevice *self,
 		                                                  &error);
 		if (!connection) {
 			g_prefix_error (&error, "The settings specified are invalid: ");
-			nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, context, error->message);
-			g_dbus_method_invocation_take_error (context, error);
+			nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, invocation, error->message);
+			g_dbus_method_invocation_take_error (invocation, error);
 			return;
 		}
 		nm_connection_clear_secrets (connection);
@@ -9683,9 +10365,8 @@ impl_device_reapply (NMDevice *self,
 	} else
 		reapply_data = NULL;
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               context,
+	               invocation,
 	               nm_device_get_applied_connection (self),
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	               TRUE,
@@ -9744,40 +10425,44 @@ get_applied_connection_cb (NMDevice *self,
 	g_dbus_method_invocation_return_value (context,
 	                                       g_variant_new ("(@a{sa{sv}}t)",
 	                                                      settings,
-	                                                      nm_active_connection_version_id_get ((NMActiveConnection *) priv->act_request)));
+	                                                      nm_active_connection_version_id_get ((NMActiveConnection *) priv->act_request.obj)));
 }
 
 static void
-impl_device_get_applied_connection (NMDevice *self,
-                                    GDBusMethodInvocation *context,
-                                    guint32 flags)
+impl_device_get_applied_connection (NMDBusObject *obj,
+                                    const NMDBusInterfaceInfoExtended *interface_info,
+                                    const NMDBusMethodInfoExtended *method_info,
+                                    GDBusConnection *connection,
+                                    const char *sender,
+                                    GDBusMethodInvocation *invocation,
+                                    GVariant *parameters)
 {
+	NMDevice *self = NM_DEVICE (obj);
 	NMConnection *applied_connection;
-	GError *error = NULL;
+	guint32 flags;
 
-	g_return_if_fail (NM_IS_DEVICE (self));
+	g_variant_get (parameters, "(u)", &flags);
 
 	/* No flags supported as of now. */
 	if (flags != 0) {
-		error = g_error_new_literal (NM_DEVICE_ERROR,
-		                             NM_DEVICE_ERROR_FAILED,
-		                             "Invalid flags specified");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_FAILED,
+		                                               "Invalid flags specified");
 		return;
 	}
 
 	applied_connection = nm_device_get_applied_connection (self);
 	if (!applied_connection) {
-		error = g_error_new_literal (NM_DEVICE_ERROR,
-		                             NM_DEVICE_ERROR_NOT_ACTIVE,
-		                             "Device is not activated");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "Device is not activated");
 		return;
 	}
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               context,
+	               invocation,
 	               applied_connection,
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	               TRUE,
@@ -9926,25 +10611,31 @@ _clear_queued_act_request (NMDevicePrivate *priv)
 }
 
 static void
-impl_device_disconnect (NMDevice *self, GDBusMethodInvocation *context)
+impl_device_disconnect (NMDBusObject *obj,
+                        const NMDBusInterfaceInfoExtended *interface_info,
+                        const NMDBusMethodInfoExtended *method_info,
+                        GDBusConnection *dbus_connection,
+                        const char *sender,
+                        GDBusMethodInvocation *invocation,
+                        GVariant *parameters)
 {
+	NMDevice *self = NM_DEVICE (obj);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
-	GError *error = NULL;
 
-	if (NM_DEVICE_GET_PRIVATE (self)->act_request == NULL) {
-		error = g_error_new_literal (NM_DEVICE_ERROR,
-		                             NM_DEVICE_ERROR_NOT_ACTIVE,
-		                             "This device is not active");
-		g_dbus_method_invocation_take_error (context, error);
+	if (!priv->act_request.obj) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "This device is not active");
 		return;
 	}
 
 	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
+	nm_assert (connection);
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               context,
+	               invocation,
 	               connection,
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	               TRUE,
@@ -9976,21 +10667,27 @@ delete_cb (NMDevice *self,
 }
 
 static void
-impl_device_delete (NMDevice *self, GDBusMethodInvocation *context)
+impl_device_delete (NMDBusObject *obj,
+                    const NMDBusInterfaceInfoExtended *interface_info,
+                    const NMDBusMethodInfoExtended *method_info,
+                    GDBusConnection *connection,
+                    const char *sender,
+                    GDBusMethodInvocation *invocation,
+                    GVariant *parameters)
 {
-	GError *error = NULL;
+	NMDevice *self = NM_DEVICE (obj);
 
-	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 or is not realized");
-		g_dbus_method_invocation_take_error (context, error);
+	if (   !nm_device_is_software (self)
+	    || !nm_device_is_real (self)) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_SOFTWARE,
+		                                               "This device is not a software device or is not realized");
 		return;
 	}
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               context,
+	               invocation,
 	               NULL,
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	               TRUE,
@@ -10114,8 +10811,8 @@ nm_device_steal_connection (NMDevice *self, NMSettingsConnection *connection)
 	    && 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_settings_connection (NM_ACTIVE_CONNECTION (priv->act_request))
+	if (   priv->act_request.obj
+	    && connection == nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (priv->act_request.obj))
 	    && priv->state < NM_DEVICE_STATE_DEACTIVATING) {
 		nm_device_state_changed (self,
 		                         NM_DEVICE_STATE_DEACTIVATING,
@@ -10131,7 +10828,7 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 
 	must_queue = _carrier_wait_check_act_request_must_queue (self, req);
 
-	if (   !priv->act_request
+	if (   !priv->act_request.obj
 	    && !must_queue
 	    && nm_device_is_real (self)) {
 		_device_activate (self, req);
@@ -10146,7 +10843,7 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 	_LOGD (LOGD_DEVICE, "queue activation request waiting for %s", must_queue ? "carrier" : "currently active connection to disconnect");
 
 	/* Deactivate existing activation request first */
-	if (priv->act_request) {
+	if (priv->act_request.obj) {
 		_LOGI (LOGD_DEVICE, "disconnecting for new activation request.");
 		nm_device_state_changed (self,
 		                         NM_DEVICE_STATE_DEACTIVATING,
@@ -10231,116 +10928,155 @@ nm_device_get_ip4_config (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_config;
+	return NM_DEVICE_GET_PRIVATE (self)->ip_config_4;
 }
 
 
 static gboolean
-nm_device_set_ip4_config (NMDevice *self,
-                          NMIP4Config *new_config,
-                          gboolean commit,
-                          GPtrArray *ip4_dev_route_blacklist)
+nm_device_set_ip_config (NMDevice *self,
+                         int addr_family,
+                         NMIPConfig *new_config,
+                         gboolean commit,
+                         GPtrArray *ip4_dev_route_blacklist)
 {
-	NMDevicePrivate *priv;
-	NMIP4Config *old_config = NULL;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+	NMIPConfig *old_config;
 	gboolean has_changes = FALSE;
 	gboolean success = TRUE;
+	NMSettingsConnection *settings_connection;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-
-	_LOGD (LOGD_IP4, "ip4-config: update (commit=%d, new-config=%p)",
-	       commit, new_config);
-
+	nm_assert_addr_family (addr_family);
+	nm_assert (!new_config || nm_ip_config_get_addr_family (new_config) == addr_family);
 	nm_assert (   !new_config
 	           || (   new_config
 	               && ({
 	                    int ip_ifindex = nm_device_get_ip_ifindex (self);
 
 	                    (   ip_ifindex > 0
-	                     && ip_ifindex == nm_ip4_config_get_ifindex (new_config));
+	                     && ip_ifindex == nm_ip_config_get_ifindex (new_config));
 	                  })));
+	nm_assert (IS_IPv4 || !ip4_dev_route_blacklist);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
-	old_config = priv->ip4_config;
+	_LOGD (LOGD_IP_from_af (addr_family),
+	       "ip%c-config: update (commit=%d, new-config=%p)",
+	       nm_utils_addr_family_to_char (addr_family),
+	       commit,
+	       new_config);
 
 	/* Always commit to nm-platform to update lifetimes */
 	if (commit && new_config) {
-		_commit_mtu (self, new_config);
-		success = nm_ip4_config_commit (new_config,
-		                                nm_device_get_platform (self),
-		                                nm_device_get_route_table (self, AF_INET, FALSE)
-		                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
-		                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN);
-		nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self),
-		                                         nm_ip4_config_get_ifindex (new_config),
-		                                         ip4_dev_route_blacklist);
-	}
-
-	if (new_config) {
-		if (old_config) {
-			/* has_changes is set only on relevant changes, because when the configuration changes,
-			 * this causes a re-read and reset. This should only happen for relevant changes */
-			nm_ip4_config_replace (old_config, new_config, &has_changes);
-			if (has_changes) {
-				_LOGD (LOGD_IP4, "ip4-config: update IP4Config instance (%s)",
-				       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
-			}
+
+		_commit_mtu (self,
+		             IS_IPv4
+		               ? NM_IP4_CONFIG (new_config)
+		               : priv->ip_config_4);
+
+		if (IS_IPv4) {
+			success = nm_ip4_config_commit (NM_IP4_CONFIG (new_config),
+			                                nm_device_get_platform (self),
+			                                nm_device_get_route_table (self, addr_family, FALSE)
+			                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
+			                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN);
+			nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self),
+			                                         nm_ip_config_get_ifindex (new_config),
+			                                         ip4_dev_route_blacklist);
 		} else {
-			has_changes = TRUE;
-			priv->ip4_config = g_object_ref (new_config);
+			gs_unref_ptrarray GPtrArray *temporary_not_available = NULL;
 
-			if (success && !nm_exported_object_is_exported (NM_EXPORTED_OBJECT (new_config)))
-				nm_exported_object_export (NM_EXPORTED_OBJECT (new_config));
+			success = nm_ip6_config_commit (NM_IP6_CONFIG (new_config),
+			                                nm_device_get_platform (self),
+			                                nm_device_get_route_table (self, addr_family, FALSE)
+			                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
+			                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
+			                                &temporary_not_available);
 
-			_LOGD (LOGD_IP4, "ip4-config: set IP4Config instance (%s)",
-			       nm_exported_object_get_path (NM_EXPORTED_OBJECT (new_config)));
+			if (!_rt6_temporary_not_available_set (self, temporary_not_available))
+				success = FALSE;
 		}
-	} else if (old_config) {
-		has_changes = TRUE;
-		priv->ip4_config = NULL;
-		_LOGD (LOGD_IP4, "ip4-config: clear IP4Config instance (%s)",
-		       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);
 	}
 
-	concheck_periodic_update (self);
+	old_config = priv->ip_config_x[IS_IPv4];
 
-	if (!nm_device_sys_iface_state_is_external_or_assume (self))
-		ip4_rp_filter_update (self);
+	if (new_config && old_config) {
+		/* has_changes is set only on relevant changes, because when the configuration changes,
+		 * this causes a re-read and reset. This should only happen for relevant changes */
+		nm_ip_config_replace (old_config, new_config, &has_changes);
+		if (has_changes) {
+			_LOGD (LOGD_IP_from_af (addr_family),
+			       "ip%c-config: update IP Config instance (%s)",
+			       nm_utils_addr_family_to_char (addr_family),
+			       nm_dbus_object_get_path (NM_DBUS_OBJECT (old_config)));
+		}
+	} else if (new_config /*&& !old_config*/) {
+		has_changes = TRUE;
+		priv->ip_config_x[IS_IPv4] = g_object_ref (new_config);
+		if (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (new_config)))
+			nm_dbus_object_export (NM_DBUS_OBJECT (new_config));
+
+		_LOGD (LOGD_IP_from_af (addr_family),
+		       "ip%c-config: set IP Config instance (%s)",
+		       nm_utils_addr_family_to_char (addr_family),
+		       nm_dbus_object_get_path (NM_DBUS_OBJECT (new_config)));
+	} else if (old_config /*&& !new_config*/) {
+		has_changes = TRUE;
+		priv->ip_config_x[IS_IPv4] = NULL;
+		_LOGD (LOGD_IP_from_af (addr_family),
+		       "ip%c-config: clear IP Config instance (%s)",
+		       nm_utils_addr_family_to_char (addr_family),
+		       nm_dbus_object_get_path (NM_DBUS_OBJECT (old_config)));
+		if (IS_IPv4) {
+			/* Device config is invalid if combined config is invalid */
+			applied_config_clear (&priv->dev_ip4_config);
+		} else
+			priv->needs_ip6_subnet = FALSE;
+	}
+
+	if (IS_IPv4) {
+		if (!nm_device_sys_iface_state_is_external_or_assume (self))
+			ip4_rp_filter_update (self);
+	}
 
 	if (has_changes) {
-		NMSettingsConnection *settings_connection;
 
-		_update_ip4_address (self);
+		if (IS_IPv4)
+			_update_ip4_address (self);
 
-		if (old_config != priv->ip4_config)
-			_notify (self, PROP_IP4_CONFIG);
-		g_signal_emit (self, signals[IP4_CONFIG_CHANGED], 0, priv->ip4_config, old_config);
+		if (old_config != priv->ip_config_x[IS_IPv4])
+			_notify (self, IS_IPv4 ? PROP_IP4_CONFIG : PROP_IP6_CONFIG);
 
-		if (old_config != priv->ip4_config)
-			nm_exported_object_clear_and_unexport (&old_config);
+		g_signal_emit (self,
+		               signals[IS_IPv4 ? IP4_CONFIG_CHANGED : IP6_CONFIG_CHANGED],
+		               0,
+		               priv->ip_config_x[IS_IPv4],
+		               old_config);
+
+		if (old_config != priv->ip_config_x[IS_IPv4])
+			nm_dbus_object_clear_and_unexport (&old_config);
 
 		if (   nm_device_sys_iface_state_is_external (self)
 		    && (settings_connection = nm_device_get_settings_connection (self))
 		    && NM_FLAGS_HAS (nm_settings_connection_get_flags (settings_connection),
-		                     NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED)
-		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request)) == NM_ACTIVATION_TYPE_EXTERNAL) {
-			NMSetting *s_ip4;
-
+		                     NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
+		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request.obj)) == NM_ACTIVATION_TYPE_EXTERNAL) {
 			g_object_freeze_notify (G_OBJECT (settings_connection));
-
-			nm_connection_remove_setting (NM_CONNECTION (settings_connection), NM_TYPE_SETTING_IP4_CONFIG);
-			s_ip4 = nm_ip4_config_create_setting (priv->ip4_config);
-			nm_connection_add_setting (NM_CONNECTION (settings_connection), s_ip4);
-
+			nm_connection_add_setting (NM_CONNECTION (settings_connection),
+			                           IS_IPv4
+			                             ? nm_ip4_config_create_setting (priv->ip_config_4)
+			                             : nm_ip6_config_create_setting (priv->ip_config_6));
 			g_object_thaw_notify (G_OBJECT (settings_connection));
 		}
 
 		nm_device_queue_recheck_assume (self);
+
+		if (!IS_IPv4) {
+			if (priv->ndisc)
+				ndisc_set_router_config (priv->ndisc, self);
+		}
 	}
 
+	nm_assert (!old_config || old_config == priv->ip_config_x[IS_IPv4]);
+
 	return success;
 }
 
@@ -10387,11 +11123,11 @@ nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *co
 	nm_assert (!old || nm_ip4_config_get_ifindex (old) == nm_device_get_ip_ifindex (self));
 	nm_assert (!config || nm_ip4_config_get_ifindex (config) == nm_device_get_ip_ifindex (self));
 
-	if (!_replace_vpn_config_in_list (&priv->vpn4_configs, (GObject *) old, (GObject *) config))
+	if (!_replace_vpn_config_in_list (&priv->vpn_configs_4, (GObject *) old, (GObject *) config))
 		return;
 
 	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (self, TRUE))
+	if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 		_LOGW (LOGD_IP4, "failed to set VPN routes for device");
 }
 
@@ -10400,125 +11136,11 @@ nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->wwan_ip4_config == config)
-		return;
-
-	g_clear_object (&priv->wwan_ip4_config);
-	if (config)
-		priv->wwan_ip4_config = g_object_ref (config);
-
-	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (self, TRUE))
+	applied_config_init (&priv->wwan_ip_config_4, config);
+	if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 		_LOGW (LOGD_IP4, "failed to set WWAN IPv4 configuration");
 }
 
-static gboolean
-nm_device_set_ip6_config (NMDevice *self,
-                          NMIP6Config *new_config,
-                          gboolean commit)
-{
-	NMDevicePrivate *priv;
-	NMIP6Config *old_config = NULL;
-	gboolean has_changes = FALSE;
-	gboolean success = TRUE;
-
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-
-	_LOGD (LOGD_IP6, "ip6-config: update (commit=%d, new-config=%p)",
-	       commit, new_config);
-
-	nm_assert (   !new_config
-	           || (   new_config
-	               && ({
-	                    int ip_ifindex = nm_device_get_ip_ifindex (self);
-
-	                    (   ip_ifindex > 0
-	                     && ip_ifindex == nm_ip6_config_get_ifindex (new_config));
-	                  })));
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
-	old_config = priv->ip6_config;
-
-	/* Always commit to nm-platform to update lifetimes */
-	if (commit && new_config) {
-		gs_unref_ptrarray GPtrArray *temporary_not_available = NULL;
-
-		_commit_mtu (self, priv->ip4_config);
-
-		success = nm_ip6_config_commit (new_config,
-		                                nm_device_get_platform (self),
-		                                nm_device_get_route_table (self, AF_INET6, FALSE)
-		                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
-		                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
-		                                &temporary_not_available);
-
-		if (!_rt6_temporary_not_available_set (self, temporary_not_available))
-			success = FALSE;
-	}
-
-	if (new_config) {
-		if (old_config) {
-			/* has_changes is set only on relevant changes, because when the configuration changes,
-			 * this causes a re-read and reset. This should only happen for relevant changes */
-			nm_ip6_config_replace (old_config, new_config, &has_changes);
-			if (has_changes) {
-				_LOGD (LOGD_IP6, "ip6-config: update IP6Config instance (%s)",
-				       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_exported_object_is_exported (NM_EXPORTED_OBJECT (new_config)))
-				nm_exported_object_export (NM_EXPORTED_OBJECT (new_config));
-
-			_LOGD (LOGD_IP6, "ip6-config: set IP6Config instance (%s)",
-			       nm_exported_object_get_path (NM_EXPORTED_OBJECT (new_config)));
-		}
-	} else if (old_config) {
-		has_changes = TRUE;
-		priv->ip6_config = NULL;
-		priv->needs_ip6_subnet = FALSE;
-		_LOGD (LOGD_IP6, "ip6-config: clear IP6Config instance (%s)",
-		       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
-	}
-
-	if (has_changes) {
-		NMSettingsConnection *settings_connection;
-
-		if (old_config != priv->ip6_config)
-			_notify (self, PROP_IP6_CONFIG);
-		g_signal_emit (self, signals[IP6_CONFIG_CHANGED], 0, priv->ip6_config, old_config);
-
-		if (old_config != priv->ip6_config)
-			nm_exported_object_clear_and_unexport (&old_config);
-
-		if (   nm_device_sys_iface_state_is_external (self)
-		    && (settings_connection = nm_device_get_settings_connection (self))
-		    && NM_FLAGS_HAS (nm_settings_connection_get_flags (settings_connection),
-		                     NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED)
-		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request)) == NM_ACTIVATION_TYPE_EXTERNAL) {
-			NMSetting *s_ip6;
-
-			g_object_freeze_notify (G_OBJECT (settings_connection));
-
-			nm_connection_remove_setting (NM_CONNECTION (settings_connection), NM_TYPE_SETTING_IP6_CONFIG);
-			s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
-			nm_connection_add_setting (NM_CONNECTION (settings_connection), s_ip6);
-
-			g_object_thaw_notify (G_OBJECT (settings_connection));
-		}
-
-		nm_device_queue_recheck_assume (self);
-
-		if (priv->ndisc)
-			ndisc_set_router_config (priv->ndisc, self);
-	}
-
-	return success;
-}
-
 void
 nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *config)
 {
@@ -10529,11 +11151,11 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
 	nm_assert (!old || nm_ip6_config_get_ifindex (old) == nm_device_get_ip_ifindex (self));
 	nm_assert (!config || nm_ip6_config_get_ifindex (config) == nm_device_get_ip_ifindex (self));
 
-	if (!_replace_vpn_config_in_list (&priv->vpn6_configs, (GObject *) old, (GObject *) config))
+	if (!_replace_vpn_config_in_list (&priv->vpn_configs_6, (GObject *) old, (GObject *) config))
 		return;
 
 	/* NULL to use existing configs */
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "failed to set VPN routes for device");
 }
 
@@ -10542,15 +11164,8 @@ nm_device_set_wwan_ip6_config (NMDevice *self, NMIP6Config *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->wwan_ip6_config == config)
-		return;
-
-	g_clear_object (&priv->wwan_ip6_config);
-	if (config)
-		priv->wwan_ip6_config = g_object_ref (config);
-
-	/* NULL to use existing configs */
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	applied_config_init (&priv->wwan_ip_config_6, config);
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "failed to set WWAN IPv6 configuration");
 }
 
@@ -10567,7 +11182,7 @@ nm_device_get_ip6_config (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_config;
+	return NM_DEVICE_GET_PRIVATE (self)->ip_config_6;
 }
 
 /*****************************************************************************/
@@ -10758,7 +11373,7 @@ start_ping (NMDevice *self,
 	priv->gw_ping.log_domain = log_domain;
 	priv->gw_ping.address = g_strdup (address);
 	priv->gw_ping.binary = g_strdup (binary);
-	priv->gw_ping.deadline = timeout + 10;	/* the proper termination is enforced by a timer */
+	priv->gw_ping.deadline = timeout + 10; /* the proper termination is enforced by a timer */
 
 	if (spawn_ping (self)) {
 		priv->gw_ping.watch = g_child_watch_add (priv->gw_ping.pid, ip_check_ping_watch_cb, self);
@@ -10801,15 +11416,15 @@ nm_device_start_ip_check (NMDevice *self)
 	if (timeout) {
 		const NMPObject *gw;
 
-		if (priv->ip4_config && priv->ip4_state == IP_DONE) {
-			gw = nm_ip4_config_best_default_route_get (priv->ip4_config);
+		if (priv->ip_config_4 && priv->ip4_state == IP_DONE) {
+			gw = nm_ip4_config_best_default_route_get (priv->ip_config_4);
 			if (gw) {
 				nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
 				log_domain = LOGD_IP4;
 			}
-		} else if (priv->ip6_config && priv->ip6_state == IP_DONE) {
-			gw = nm_ip6_config_best_default_route_get (priv->ip6_config);
+		} else if (priv->ip_config_6 && priv->ip6_state == IP_DONE) {
+			gw = nm_ip6_config_best_default_route_get (priv->ip_config_6);
 			if (gw) {
 				nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping6", "/usr/bin/ping6", NULL);
@@ -10949,11 +11564,11 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 
 	/* when the link comes up, we must restore IP configuration if necessary. */
 	if (priv->ip4_state == IP_DONE) {
-		if (!ip4_config_merge_and_apply (self, TRUE))
+		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 			_LOGW (LOGD_IP4, "failed applying IP4 config after bringing link up");
 	}
 	if (priv->ip6_state == IP_DONE) {
-		if (!ip6_config_merge_and_apply (self, TRUE))
+		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 			_LOGW (LOGD_IP6, "failed applying IP6 config after bringing link up");
 	}
 
@@ -11018,122 +11633,37 @@ nm_device_get_firmware_missing (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->firmware_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;
-
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (ext_ip4_config), NULL);
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
-
-	leases = nm_dhcp_manager_get_lease_ip_configs (nm_dhcp_manager_get (),
-	                                               nm_device_get_multi_index (self),
-	                                               AF_INET,
-	                                               ip_iface,
-	                                               ip_ifindex,
-	                                               nm_connection_get_uuid (connection),
-	                                               nm_device_get_route_table (self, AF_INET, TRUE),
-	                                               nm_device_get_route_metric (self, AF_INET));
-	for (liter = leases; liter && !found; liter = liter->next) {
-		NMIP4Config *lease_config = liter->data;
-		const NMPlatformIP4Address *address = nm_ip4_config_get_first_address (lease_config);
-		const NMPObject *gw1, *gw2;
-
-		g_assert (address);
-		if (!nm_ip4_config_address_exists (ext_ip4_config, address))
-			continue;
-		gw1 = nm_ip4_config_best_default_route_get (lease_config);
-		if (!gw1)
-			continue;
-		gw2 = nm_ip4_config_best_default_route_get (ext_ip4_config);
-		if (!gw2)
-			continue;
-		if (NMP_OBJECT_CAST_IP4_ROUTE (gw1)->gateway != NMP_OBJECT_CAST_IP4_ROUTE (gw2)->gateway)
-			continue;
-		found = g_object_ref (lease_config);
-	}
-
-	g_slist_free_full (leases, g_object_unref);
-	return found;
-}
-
 static void
-capture_lease_config (NMDevice *self,
-                      NMIP4Config *ext_ip4_config,
-                      NMIP4Config **out_ip4_config,
-                      NMIP6Config *ext_ip6_config,
-                      NMIP6Config **out_ip6_config)
+intersect_ext_config (NMDevice *self, AppliedConfig *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMSettingsConnection *const*connections;
-	guint i;
-	gboolean dhcp_used = FALSE;
-	NMDedupMultiIter ipconf_iter;
+	NMIPConfig *ext;
+	guint32 penalty;
+	int family;
 
-	/* Ensure at least one address on the device has a non-infinite lifetime,
-	 * otherwise DHCP cannot possibly be active on the device right now.
-	 */
-	if (ext_ip4_config && out_ip4_config) {
-		const NMPlatformIP4Address *addr;
-
-		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ext_ip4_config, &addr) {
-			if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
-				dhcp_used = TRUE;
-				break;
-			}
-		}
-	} else if (ext_ip6_config && out_ip6_config) {
-		const NMPlatformIP6Address *addr;
-
-		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ext_ip6_config, &addr) {
-			if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
-				dhcp_used = TRUE;
-				break;
-			}
-		}
-	} else {
-		g_return_if_fail (   (ext_ip6_config && out_ip6_config)
-		                  || (ext_ip4_config && out_ip4_config));
-	}
-
-	if (!dhcp_used)
+	if (!config->orig)
 		return;
 
-	connections = nm_settings_get_connections (priv->settings, NULL);
-	for (i = 0; connections[i]; i++) {
-		NMConnection *candidate = (NMConnection *) connections[i];
-		const char *method;
-
-		if (!nm_device_check_connection_compatible (self, candidate))
-			continue;
-
-		/* IPv4 leases */
-		method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP4_CONFIG);
-		if (out_ip4_config && strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
-			*out_ip4_config = find_ip4_lease_config (self, candidate, ext_ip4_config);
-			if (*out_ip4_config)
-				return;
-		}
+	family = nm_ip_config_get_addr_family (config->orig);
+	penalty = default_route_metric_penalty_get (self, family);
+	ext = family == AF_INET
+	      ? (NMIPConfig *) priv->ext_ip_config_4
+	      : (NMIPConfig *) priv->ext_ip_config_6;
 
-		/* IPv6 leases */
-		method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP6_CONFIG);
-		if (out_ip6_config && strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
-			/* FIXME: implement find_ip6_lease_config() */
-		}
+	if (config->current)
+		nm_ip_config_intersect (config->current, ext, penalty);
+	else {
+		config->current = nm_ip_config_intersect_alloc (config->orig,
+		                                                ext,
+		                                                penalty);
 	}
 }
 
 static gboolean
-update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolean intersect_configs)
+update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_configs)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex;
-	gboolean capture_resolv_conf;
 	GSList *iter;
 
 	nm_assert_addr_family (addr_family);
@@ -11142,122 +11672,110 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolea
 	if (!ifindex)
 		return FALSE;
 
-	capture_resolv_conf =    initial
-	                      && nm_dns_manager_get_resolv_conf_explicit (nm_dns_manager_get ());
-
 	if (addr_family == AF_INET) {
 
-		g_clear_object (&priv->ext_ip4_config);
-		priv->ext_ip4_config = nm_ip4_config_capture (nm_device_get_multi_index (self),
+		g_clear_object (&priv->ext_ip_config_4);
+		priv->ext_ip_config_4 = nm_ip4_config_capture (nm_device_get_multi_index (self),
 		                                              nm_device_get_platform (self),
-		                                              ifindex,
-		                                              capture_resolv_conf);
-		if (priv->ext_ip4_config) {
-			if (initial) {
-				g_clear_object (&priv->dev_ip4_config);
-				capture_lease_config (self, priv->ext_ip4_config, &priv->dev_ip4_config, NULL, NULL);
-			}
-
+		                                              ifindex);
+		if (priv->ext_ip_config_4) {
 			if (intersect_configs) {
 				/* This function was called upon external changes. Remove the configuration
 				 * (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->con_ip_config_4) {
+					nm_ip4_config_intersect (priv->con_ip_config_4, priv->ext_ip_config_4,
 					                         default_route_metric_penalty_get (self, AF_INET));
 				}
-				if (priv->dev_ip4_config) {
-					nm_ip4_config_intersect (priv->dev_ip4_config, priv->ext_ip4_config,
-					                         default_route_metric_penalty_get (self, AF_INET));
-				}
-				if (priv->wwan_ip4_config) {
-					nm_ip4_config_intersect (priv->wwan_ip4_config, priv->ext_ip4_config,
-					                         default_route_metric_penalty_get (self, AF_INET));
-				}
-				for (iter = priv->vpn4_configs; iter; iter = iter->next)
-					nm_ip4_config_intersect (iter->data, priv->ext_ip4_config, 0);
+
+				intersect_ext_config (self, &priv->dev_ip4_config);
+				intersect_ext_config (self, &priv->wwan_ip_config_4);
+
+				for (iter = priv->vpn_configs_4; iter; iter = iter->next)
+					nm_ip4_config_intersect (iter->data, priv->ext_ip_config_4, 0);
 			}
 
-			/* Remove parts from ext_ip4_config to only contain the information that
+			/* Remove parts from ext_ip_config_4 to only contain the information that
 			 * was configured externally -- we already have the same configuration from
 			 * internal origins. */
-			if (priv->con_ip4_config) {
-				nm_ip4_config_subtract (priv->ext_ip4_config, priv->con_ip4_config,
+			if (priv->con_ip_config_4) {
+				nm_ip4_config_subtract (priv->ext_ip_config_4, priv->con_ip_config_4,
 				                        default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (priv->dev_ip4_config) {
-				nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config,
-				                        default_route_metric_penalty_get (self, AF_INET));
+			if (applied_config_get_current (&priv->dev_ip4_config)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
+				                       applied_config_get_current (&priv->dev_ip4_config),
+				                       default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (priv->wwan_ip4_config) {
-				nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_config,
-				                        default_route_metric_penalty_get (self, AF_INET));
+			if (applied_config_get_current (&priv->wwan_ip_config_4)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
+				                       applied_config_get_current (&priv->wwan_ip_config_4),
+				                       default_route_metric_penalty_get (self, AF_INET));
 			}
-			for (iter = priv->vpn4_configs; iter; iter = iter->next)
-				nm_ip4_config_subtract (priv->ext_ip4_config, iter->data, 0);
+			for (iter = priv->vpn_configs_4; iter; iter = iter->next)
+				nm_ip4_config_subtract (priv->ext_ip_config_4, iter->data, 0);
 		}
 
 	} else {
 		nm_assert (addr_family == AF_INET6);
 
-		g_clear_object (&priv->ext_ip6_config);
+		g_clear_object (&priv->ext_ip_config_6);
 		g_clear_object (&priv->ext_ip6_config_captured);
 		priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
 		                                                       nm_device_get_platform (self),
 		                                                       ifindex,
-		                                                       capture_resolv_conf,
 		                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 		if (priv->ext_ip6_config_captured) {
 
-			priv->ext_ip6_config = nm_ip6_config_new_cloned (priv->ext_ip6_config_captured);
+			priv->ext_ip_config_6 = nm_ip6_config_new_cloned (priv->ext_ip6_config_captured);
 
 			if (intersect_configs) {
 				/* This function was called upon external changes. Remove the configuration
 				 * (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,
+				if (priv->con_ip_config_6) {
+					nm_ip6_config_intersect (priv->con_ip_config_6, priv->ext_ip_config_6,
 					                         default_route_metric_penalty_get (self, AF_INET6));
 				}
-				if (priv->ac_ip6_config) {
-					nm_ip6_config_intersect (priv->ac_ip6_config, priv->ext_ip6_config,
-					                         default_route_metric_penalty_get (self, AF_INET6));
-				}
-				if (priv->dhcp6.ip6_config) {
-					nm_ip6_config_intersect (priv->dhcp6.ip6_config, priv->ext_ip6_config,
-					                         default_route_metric_penalty_get (self, AF_INET6));
-				}
-				if (priv->wwan_ip6_config) {
-					nm_ip6_config_intersect (priv->wwan_ip6_config, priv->ext_ip6_config,
-					                         default_route_metric_penalty_get (self, AF_INET6));
-				}
-				for (iter = priv->vpn6_configs; iter; iter = iter->next)
-					nm_ip6_config_intersect (iter->data, priv->ext_ip6_config, 0);
+
+				intersect_ext_config (self, &priv->ac_ip6_config);
+				intersect_ext_config (self, &priv->dhcp6.ip6_config);
+				intersect_ext_config (self, &priv->wwan_ip_config_6);
+
+				for (iter = priv->vpn_configs_6; iter; iter = iter->next)
+					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, 0);
+
+				if (   priv->ipv6ll_has
+				    && !nm_ip6_config_lookup_address (priv->ext_ip_config_6, &priv->ipv6ll_addr))
+					priv->ipv6ll_has = FALSE;
 			}
 
-			/* Remove parts from ext_ip6_config to only contain the information that
+			/* Remove parts from ext_ip_config_6 to only contain the information that
 			 * was configured externally -- we already have the same configuration from
 			 * internal origins. */
-			if (priv->con_ip6_config) {
-				nm_ip6_config_subtract (priv->ext_ip6_config, priv->con_ip6_config,
+			if (priv->con_ip_config_6) {
+				nm_ip6_config_subtract (priv->ext_ip_config_6, priv->con_ip_config_6,
 				                        default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (priv->ac_ip6_config) {
-				nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config,
-				                        default_route_metric_penalty_get (self, AF_INET6));
+			if (applied_config_get_current (&priv->ac_ip6_config)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
+				                       applied_config_get_current (&priv->ac_ip6_config),
+				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (priv->dhcp6.ip6_config) {
-				nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6.ip6_config,
-				                        default_route_metric_penalty_get (self, AF_INET6));
+			if (applied_config_get_current (&priv->dhcp6.ip6_config)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
+				                       applied_config_get_current (&priv->dhcp6.ip6_config),
+				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (priv->wwan_ip6_config) {
-				nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_ip6_config,
-				                        default_route_metric_penalty_get (self, AF_INET6));
+			if (applied_config_get_current (&priv->wwan_ip_config_6)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
+				                       applied_config_get_current (&priv->wwan_ip_config_6),
+				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			for (iter = priv->vpn6_configs; iter; iter = iter->next)
-				nm_ip6_config_subtract (priv->ext_ip6_config, iter->data, 0);
+			for (iter = priv->vpn_configs_6; iter; iter = iter->next)
+				nm_ip6_config_subtract (priv->ext_ip_config_6, iter->data, 0);
 		}
 	}
 
@@ -11265,156 +11783,139 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolea
 }
 
 static void
-update_ip_config (NMDevice *self, int addr_family, gboolean initial)
+update_ip_config (NMDevice *self, int addr_family)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	nm_assert_addr_family (addr_family);
 
-	if (update_ext_ip_config (self, addr_family, initial, TRUE)) {
+	if (addr_family == AF_INET)
+		priv->update_ip_config_completed_v4 = TRUE;
+	else
+		priv->update_ip_config_completed_v6 = TRUE;
+
+	if (update_ext_ip_config (self, addr_family, TRUE)) {
 		if (addr_family == AF_INET) {
-			if (priv->ext_ip4_config)
-				ip4_config_merge_and_apply (self, FALSE);
+			if (priv->ext_ip_config_4)
+				ip_config_merge_and_apply (self, AF_INET, FALSE);
 		} else {
 			if (priv->ext_ip6_config_captured)
-				ip6_config_merge_and_apply (self, FALSE);
+				ip_config_merge_and_apply (self, AF_INET6, FALSE);
 		}
 	}
-
-	if (   addr_family == AF_INET6
-	    && priv->linklocal6_timeout_id
-	    && priv->ext_ip6_config_captured
-	    && nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE)) {
-		/* linklocal6 is ready now, do the state transition... we are also
-		 * invoked as g_idle_add, so no problems with reentrance doing it now.
-		 */
-		linklocal6_complete (self);
-	}
 }
 
 void
 nm_device_capture_initial_config (NMDevice *self)
 {
-	update_ip_config (self, AF_INET,  TRUE);
-	update_ip_config (self, AF_INET6, TRUE);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->update_ip_config_completed_v4)
+		update_ip_config (self, AF_INET);
+	if (!priv->update_ip_config_completed_v6)
+		update_ip_config (self, AF_INET6);
 }
 
 static gboolean
-queued_ip4_config_change (gpointer user_data)
+queued_ip_config_change (NMDevice *self, int addr_family)
 {
-	NMDevice *self = user_data;
 	NMDevicePrivate *priv;
+	gboolean need_ipv6ll = FALSE;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+	NMPlatform *platform;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_assert (!priv->queued_ip4_config_pending);
+	nm_assert (IS_IPv4 ? !priv->queued_ip4_config_pending : !priv->queued_ip6_config_pending);
 
 	/* Wait for any queued state changes */
 	if (priv->queued_state.id)
-		return TRUE;
+		return G_SOURCE_CONTINUE;
 
-	priv->queued_ip4_config_id = 0;
+	priv->queued_ip_config_id_x[IS_IPv4] = 0;
 
 	/* If a commit is scheduled, this function would potentially interfere with
 	 * it changing IP configurations before they are applied. Postpone the
 	 * update in such case.
 	 */
 	if (activation_source_is_scheduled (self,
-	                                    activate_stage5_ip4_config_result,
-	                                    AF_INET)) {
-		priv->queued_ip4_config_pending = FALSE;
-		priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
-		_LOGT (LOGD_DEVICE, "IP4 update was postponed");
-	} else
-		update_ip_config (self, AF_INET, FALSE);
-
-	set_unmanaged_external_down (self, TRUE);
+	                                    IS_IPv4
+	                                      ? activate_stage5_ip4_config_result
+	                                      : activate_stage5_ip6_config_commit,
+	                                    addr_family)) {
+		if (IS_IPv4) {
+			priv->queued_ip4_config_pending = FALSE;
+			priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self);
+		} else {
+			priv->queued_ip6_config_pending = FALSE;
+			priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self);
+		}
+		_LOGT (LOGD_DEVICE, "IP%c update was postponed",
+		       nm_utils_addr_family_to_char (addr_family));
+	} else {
+		update_ip_config (self, addr_family);
 
-	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
-		priv->v4_has_shadowed_routes = _v4_has_shadowed_routes_detect (self);;
-		ip4_rp_filter_update (self);
+		if (!IS_IPv4) {
+			/* Check whether we need to complete waiting for link-local.
+			 * We are also called from an idle handler, so no problem doing state transitions
+			 * now. */
+			linklocal6_check_complete (self);
+		}
 	}
 
-	return FALSE;
-}
-
-static gboolean
-queued_ip6_config_change (gpointer user_data)
-{
-	NMDevice *self = user_data;
-	NMDevicePrivate *priv;
-	GSList *iter;
-	gboolean need_ipv6ll = FALSE;
-
-	g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
-	nm_assert (!priv->queued_ip4_config_pending);
+	if (!IS_IPv4) {
+		if (   priv->state < NM_DEVICE_STATE_DEACTIVATING
+		    && (platform = nm_device_get_platform (self))
+		    && nm_platform_link_get (platform, priv->ifindex)) {
+			/* Handle DAD failures */
+			while (priv->dad6_failed_addrs) {
+				nm_auto_nmpobj const NMPObject *obj = NULL;
+				const NMPlatformIP6Address *addr;
 
-	/* Wait for any queued state changes */
-	if (priv->queued_state.id)
-		return TRUE;
+				obj = priv->dad6_failed_addrs->data;
+				priv->dad6_failed_addrs = g_slist_delete_link (priv->dad6_failed_addrs, priv->dad6_failed_addrs);
 
-	priv->queued_ip6_config_id = 0;
-
-	/* If a commit is scheduled, this function would potentially interfere with
-	 * it changing IP configurations before they are applied. Postpone the
-	 * update in such case.
-	 */
-	if (activation_source_is_scheduled (self,
-	                                    activate_stage5_ip6_config_commit,
-	                                    AF_INET6)) {
-		priv->queued_ip6_config_pending = FALSE;
-		priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
-		_LOGT (LOGD_DEVICE, "IP6 update was postponed");
-	} else
-		update_ip_config (self, AF_INET6, FALSE);
+				if (!nm_ndisc_dad_addr_is_fail_candidate (platform, obj))
+					continue;
 
-	if (priv->state < NM_DEVICE_STATE_DEACTIVATING
-	    && nm_platform_link_get (nm_device_get_platform (self), priv->ifindex)) {
-		/* Handle DAD failures */
-		for (iter = priv->dad6_failed_addrs; iter; iter = g_slist_next (iter)) {
-			NMPlatformIP6Address *addr = iter->data;
+				addr = NMP_OBJECT_CAST_IP6_ADDRESS (obj);
 
-			if (addr->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));
 
-			_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->ndisc)
+					nm_ndisc_dad_failed (priv->ndisc, &addr->address);
+			}
 
-			if (IN6_IS_ADDR_LINKLOCAL (&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->ip_config_6
+			    && nm_ip6_config_get_num_addresses (priv->ip_config_6))
 				need_ipv6ll = TRUE;
-			else if (priv->ndisc)
-				nm_ndisc_dad_failed (priv->ndisc, &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;
+			if (need_ipv6ll)
+				check_and_add_ipv6ll_addr (self);
+		} else {
+			g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
+			priv->dad6_failed_addrs = NULL;
+		}
 
-	/* Check if DAD is still pending */
-	if (   priv->ip6_state == IP_CONF
-	    && priv->dad6_ip6_config
-	    && priv->ext_ip6_config_captured) {
-		if (!nm_ip6_config_has_any_dad_pending (priv->ext_ip6_config_captured,
-		                                        priv->dad6_ip6_config)) {
+		/* Check if DAD is still pending */
+		if (   priv->ip6_state == IP_CONF
+		    && priv->dad6_ip6_config
+		    && priv->ext_ip6_config_captured
+		    && !nm_ip6_config_has_any_dad_pending (priv->ext_ip6_config_captured,
+		                                           priv->dad6_ip6_config)) {
 			_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated");
 			g_clear_object (&priv->dad6_ip6_config);
-			_set_ip_state (self, AF_INET6, IP_DONE);
+			_set_ip_state (self, addr_family, IP_DONE);
 			check_ip_state (self, FALSE, TRUE);
 			if (priv->rt6_temporary_not_available)
 				nm_device_activate_schedule_ip6_config_result (self);
@@ -11423,21 +11924,40 @@ queued_ip6_config_change (gpointer user_data)
 
 	set_unmanaged_external_down (self, TRUE);
 
-	return FALSE;
+	if (IS_IPv4) {
+		if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
+			priv->v4_has_shadowed_routes = _v4_has_shadowed_routes_detect (self);;
+			ip4_rp_filter_update (self);
+		}
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+queued_ip4_config_change (gpointer user_data)
+{
+	return queued_ip_config_change (user_data, AF_INET);
+}
+
+static gboolean
+queued_ip6_config_change (gpointer user_data)
+{
+	return queued_ip_config_change (user_data, AF_INET6);
 }
 
 static void
 device_ipx_changed (NMPlatform *platform,
                     int obj_type_i,
                     int ifindex,
-                    gpointer platform_object,
+                    gconstpointer platform_object,
                     int change_type_i,
                     NMDevice *self)
 {
 	const NMPObjectType obj_type = obj_type_i;
 	const NMPlatformSignalChangeType change_type = change_type_i;
 	NMDevicePrivate *priv;
-	NMPlatformIP6Address *addr;
+	const NMPlatformIP6Address *addr;
 
 	if (nm_device_get_ip_ifindex (self) != ifindex)
 		return;
@@ -11449,10 +11969,10 @@ device_ipx_changed (NMPlatform *platform,
 	case NMP_OBJECT_TYPE_IP4_ROUTE:
 		if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 			priv->queued_ip4_config_pending = TRUE;
-			nm_assert_se (!nm_clear_g_source (&priv->queued_ip4_config_id));
-		} else if (!priv->queued_ip4_config_id) {
+			nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_4));
+		} else if (!priv->queued_ip_config_id_4) {
 			priv->queued_ip4_config_pending = FALSE;
-			priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
+			priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self);
 			_LOGD (LOGD_DEVICE, "queued IP4 config change");
 		}
 		break;
@@ -11461,19 +11981,18 @@ device_ipx_changed (NMPlatform *platform,
 
 		if (   priv->state > NM_DEVICE_STATE_DISCONNECTED
 		    && priv->state < NM_DEVICE_STATE_DEACTIVATING
-		    && (   (change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->n_ifa_flags & IFA_F_DADFAILED)
-		        || (change_type == NM_PLATFORM_SIGNAL_REMOVED && addr->n_ifa_flags & IFA_F_TENTATIVE))) {
-			priv->dad6_failed_addrs = g_slist_append (priv->dad6_failed_addrs,
-			                                          g_memdup (addr, sizeof (NMPlatformIP6Address)));
+		    && nm_ndisc_dad_addr_is_fail_candidate_event (change_type, addr)) {
+			priv->dad6_failed_addrs = g_slist_prepend (priv->dad6_failed_addrs,
+			                                           (gpointer) nmp_object_ref (NMP_OBJECT_UP_CAST (addr)));
 		}
 		/* fall through */
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
 		if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 			priv->queued_ip6_config_pending = TRUE;
-			nm_assert_se (!nm_clear_g_source (&priv->queued_ip6_config_id));
-		} else if (!priv->queued_ip6_config_id) {
+			nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_6));
+		} else if (!priv->queued_ip_config_id_6) {
 			priv->queued_ip6_config_pending = FALSE;
-			priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
+			priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self);
 			_LOGD (LOGD_DEVICE, "queued IP6 config change");
 		}
 		break;
@@ -11741,14 +12260,14 @@ _set_unmanaged_flags (NMDevice *self,
 
 		if (priv->queued_ip4_config_pending) {
 			priv->queued_ip4_config_pending = FALSE;
-			nm_assert_se (!nm_clear_g_source (&priv->queued_ip4_config_id));
-			priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
+			nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_4));
+			priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self);
 		}
 
 		if (priv->queued_ip6_config_pending) {
 			priv->queued_ip6_config_pending = FALSE;
-			nm_assert_se (!nm_clear_g_source (&priv->queued_ip6_config_id));
-			priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
+			nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_6));
+			priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self);
 		}
 
 		if (!priv->pending_actions) {
@@ -12014,7 +12533,7 @@ nm_device_reapply_settings_immediately (NMDevice *self)
 	if (g_strcmp0 ((zone = nm_setting_connection_get_zone (s_con_settings)),
 	               nm_setting_connection_get_zone (s_con_applied)) != 0) {
 
-		version_id = nm_active_connection_version_id_bump ((NMActiveConnection *) self->_priv->act_request);
+		version_id = nm_active_connection_version_id_bump ((NMActiveConnection *) self->_priv->act_request.obj);
 		_LOGD (LOGD_DEVICE, "reapply setting: zone = %s%s%s (version-id %llu)", NM_PRINT_FMT_QUOTE_STRING (zone), (unsigned long long) version_id);
 
 		g_object_set (G_OBJECT (s_con_applied),
@@ -12026,7 +12545,7 @@ nm_device_reapply_settings_immediately (NMDevice *self)
 
 	if ((metered = nm_setting_connection_get_metered (s_con_settings)) != nm_setting_connection_get_metered (s_con_applied)) {
 
-		version_id = nm_active_connection_version_id_bump ((NMActiveConnection *) self->_priv->act_request);
+		version_id = nm_active_connection_version_id_bump ((NMActiveConnection *) self->_priv->act_request.obj);
 		_LOGD (LOGD_DEVICE, "reapply setting: metered = %d (version-id %llu)", (int) metered, (unsigned long long) version_id);
 
 		g_object_set (G_OBJECT (s_con_applied),
@@ -12082,13 +12601,27 @@ nm_device_update_metered (NMDevice *self)
 
 	/* Try to guess a value using the metered flag in IP configuration */
 	if (value == NM_METERED_INVALID) {
-		if (   priv->ip4_config
+		if (   priv->ip_config_4
 		    && priv->ip4_state == IP_DONE
-		    && nm_ip4_config_get_metered (priv->ip4_config))
+		    && nm_ip4_config_get_metered (priv->ip_config_4))
+			value = NM_METERED_GUESS_YES;
+	}
+
+	/* Otherwise look at connection type. For Bluetooth, we look at the type of
+	 * Bluetooth sharing: for PANU/DUN (where we are receiving internet from
+	 * another device) we set GUESS_YES; for NAP (where we are sharing internet
+	 * to another device) we set GUESS_NO. We ignore WiMAX here as it’s no
+	 * longer supported by NetworkManager. */
+	if (   value == NM_METERED_INVALID
+	    && nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) {
+
+		if (_nm_connection_get_setting_bluetooth_for_nap (connection)) {
+			/* NAP types are not metered, but other types are. */
+			value = NM_METERED_GUESS_NO;
+		} else
 			value = NM_METERED_GUESS_YES;
 	}
 
-	/* Otherwise look at connection type */
 	if (value == NM_METERED_INVALID) {
 		if (   nm_connection_is_type (connection, NM_SETTING_GSM_SETTING_NAME)
 		    || nm_connection_is_type (connection, NM_SETTING_CDMA_SETTING_NAME))
@@ -12179,7 +12712,7 @@ nm_device_check_connection_available (NMDevice *self,
 
 		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)) {
+				if (NM_FLAGS_ANY (i, j)) {
 					k = i & ~j;
 					nm_assert (   available_all[i] == available_all[k]
 					           || available_all[i]);
@@ -12204,7 +12737,7 @@ available_connections_del_all (NMDevice *self)
 static gboolean
 available_connections_add (NMDevice *self, NMConnection *connection)
 {
-	return nm_g_hash_table_add (self->_priv->available_connections, g_object_ref (connection));
+	return g_hash_table_add (self->_priv->available_connections, g_object_ref (connection));
 }
 
 static gboolean
@@ -12262,7 +12795,7 @@ nm_device_recheck_available_connections (NMDevice *self)
 	priv = NM_DEVICE_GET_PRIVATE(self);
 
 	if (g_hash_table_size (priv->available_connections) > 0) {
-		prune_list = g_hash_table_new (g_direct_hash, g_direct_equal);
+		prune_list = g_hash_table_new (nm_direct_hash, NULL);
 		g_hash_table_iter_init (&h_iter, priv->available_connections);
 		while (g_hash_table_iter_next (&h_iter, (gpointer *) &connection, NULL))
 			g_hash_table_add (prune_list, connection);
@@ -12371,19 +12904,19 @@ cp_connection_added_or_updated (NMDevice *self, NMConnection *connection)
 }
 
 static void
-cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+cp_connection_added (NMSettings *settings, NMConnection *connection, gpointer user_data)
 {
 	cp_connection_added_or_updated (user_data, connection);
 }
 
 static void
-cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gboolean by_user, gpointer user_data)
+cp_connection_updated (NMSettings *settings, NMConnection *connection, gboolean by_user, gpointer user_data)
 {
 	cp_connection_added_or_updated (user_data, connection);
 }
 
 static void
-cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+cp_connection_removed (NMSettings *settings, NMConnection *connection, gpointer user_data)
 {
 	NMDevice *self = user_data;
 
@@ -12565,8 +13098,8 @@ _cleanup_generic_pre (NMDevice *self, CleanupType cleanup_type)
 
 	queued_state_clear (self);
 
-	_cleanup_ip4_pre (self, cleanup_type);
-	_cleanup_ip6_pre (self, cleanup_type);
+	_cleanup_ip_pre (self, AF_INET, cleanup_type);
+	_cleanup_ip_pre (self, AF_INET6, cleanup_type);
 }
 
 static void
@@ -12588,39 +13121,41 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	/* Clean up IP configs; this does not actually deconfigure the
 	 * interface; the caller must flush routes and addresses explicitly.
 	 */
-	nm_device_set_ip4_config (self, NULL, TRUE, NULL);
-	nm_device_set_ip6_config (self, NULL, TRUE);
+	nm_device_set_ip_config (self, AF_INET, NULL, TRUE, NULL);
+	nm_device_set_ip_config (self, AF_INET6, NULL, TRUE, NULL);
 	g_clear_object (&priv->proxy_config);
-	g_clear_object (&priv->con_ip4_config);
-	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->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->con_ip_config_4);
+	applied_config_clear (&priv->dev_ip4_config);
+	applied_config_clear (&priv->wwan_ip_config_4);
+	g_clear_object (&priv->ext_ip_config_4);
+	g_clear_object (&priv->ip_config_4);
+	g_clear_object (&priv->con_ip_config_6);
+	applied_config_clear (&priv->ac_ip6_config);
+	g_clear_object (&priv->ext_ip_config_6);
 	g_clear_object (&priv->ext_ip6_config_captured);
-	g_clear_object (&priv->wwan_ip6_config);
-	g_clear_object (&priv->ip6_config);
+	applied_config_clear (&priv->wwan_ip_config_6);
+	g_clear_object (&priv->ip_config_6);
 	g_clear_object (&priv->dad6_ip6_config);
+	priv->ipv6ll_has = FALSE;
+	memset (&priv->ipv6ll_addr, 0, sizeof (priv->ipv6ll_addr));
 
 	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 
-	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;
+	g_slist_free_full (priv->vpn_configs_4, g_object_unref);
+	priv->vpn_configs_4 = NULL;
+	g_slist_free_full (priv->vpn_configs_6, g_object_unref);
+	priv->vpn_configs_6 = NULL;
 
-	/* We no longer accept the delegations. nm_device_set_ip6_config(NULL)
+	/* We no longer accept the delegations. nm_device_set_ip_config(NULL)
 	 * above disables them. */
 	nm_assert (priv->needs_ip6_subnet == FALSE);
 
-	if (priv->act_request) {
-		nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request), AF_INET, FALSE);
+	if (priv->act_request.obj) {
+		nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request.obj), AF_INET, FALSE);
 
 		priv->master_ready_handled = FALSE;
-		nm_clear_g_signal_handler (priv->act_request, &priv->master_ready_id);
+		nm_clear_g_signal_handler (priv->act_request.obj, &priv->master_ready_id);
 
 		act_request_set (self, NULL);
 	}
@@ -12642,7 +13177,7 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	 * those are identified by ip_iface, not by iface (which might be a tty
 	 * or ATM device).
 	 */
-	nm_device_set_ip_iface (self, NULL);
+	_set_ip_ifindex (self, 0, NULL);
 }
 
 /*
@@ -12762,10 +13297,10 @@ find_dhcp4_address (NMDevice *self)
 	const NMPlatformIP4Address *a;
 	NMDedupMultiIter ipconf_iter;
 
-	if (!priv->ip4_config)
+	if (!priv->ip_config_4)
 		return NULL;
 
-	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip4_config, &a) {
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip_config_4, &a) {
 		if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP)
 			return g_strdup (nm_utils_inet4_ntop (a->address, NULL));
 	}
@@ -13072,7 +13607,7 @@ _set_state_full (NMDevice *self,
 		g_cancellable_cancel (priv->deactivating_cancellable);
 
 	/* Cache the activation request for the dispatcher */
-	req = nm_g_object_ref (priv->act_request);
+	req = nm_g_object_ref (priv->act_request.obj);
 
 	if (   state >  NM_DEVICE_STATE_UNMANAGED
 	    && state <= NM_DEVICE_STATE_ACTIVATED
@@ -13180,8 +13715,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_ip4_pre (self, CLEANUP_TYPE_DECONFIGURE);
-			_cleanup_ip6_pre (self, CLEANUP_TYPE_DECONFIGURE);
+			_cleanup_ip_pre (self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
+			_cleanup_ip_pre (self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
 		}
 		break;
 	default:
@@ -13195,6 +13730,13 @@ _set_state_full (NMDevice *self,
 
 	_notify (self, PROP_STATE);
 	_notify (self, PROP_STATE_REASON);
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+	                            &interface_info_device,
+	                            &signal_info_state_changed,
+	                            "(uuu)",
+	                            (guint32) state,
+	                            (guint32) old_state,
+	                            (guint32) reason);
 	g_signal_emit (self, signals[STATE_CHANGED], 0, (guint) state, (guint) old_state, (guint) reason);
 
 	/* Post-process the event after internal notification */
@@ -13345,7 +13887,7 @@ _set_state_full (NMDevice *self,
 	if (ip_config_valid (old_state) && !ip_config_valid (state))
 	    notify_ip_properties (self);
 
-	concheck_periodic_update (self);
+	nm_device_check_connectivity_update_interval (self);
 
 	/* Dispose of the cached activation request */
 	if (req)
@@ -14307,8 +14849,15 @@ nm_device_init (NMDevice *self)
 
 	self->_priv = priv;
 
+	c_list_init (&priv->concheck_lst_head);
+	c_list_init (&self->devices_lst);
 	c_list_init (&priv->slaves);
 
+	priv->connectivity_state = NM_CONNECTIVITY_UNKNOWN;
+
+	nm_dbus_track_obj_path_init (&priv->parent_device, G_OBJECT (self), obj_properties[PROP_PARENT]);
+	nm_dbus_track_obj_path_init (&priv->act_request, G_OBJECT (self), obj_properties[PROP_ACTIVE_CONNECTION]);
+
 	priv->netns = g_object_ref (NM_NETNS_GET);
 
 	priv->autoconnect_blocked_flags = DEFAULT_AUTOCONNECT
@@ -14323,7 +14872,7 @@ nm_device_init (NMDevice *self)
 	priv->rfkill_type = RFKILL_TYPE_UNKNOWN;
 	priv->unmanaged_flags = NM_UNMANAGED_PLATFORM_INIT;
 	priv->unmanaged_mask = priv->unmanaged_flags;
-	priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
+	priv->available_connections = g_hash_table_new_full (nm_direct_hash, NULL, g_object_unref, NULL);
 	priv->ip6_saved_properties = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 	priv->sys_iface_state = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
 
@@ -14422,9 +14971,19 @@ dispose (GObject *object)
 	NMDevice *self = NM_DEVICE (object);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatform *platform;
+	NMDeviceConnectivityHandle *con_handle;
+	gs_free_error GError *cancelled_error = NULL;
 
 	_LOGD (LOGD_DEVICE, "disposing");
 
+	nm_assert (c_list_is_empty (&self->devices_lst));
+
+	while ((con_handle = c_list_first_entry (&priv->concheck_lst_head, NMDeviceConnectivityHandle, concheck_lst))) {
+		if (!cancelled_error)
+			nm_utils_error_set_cancelled (&cancelled_error, FALSE, "NMDevice");
+		concheck_handle_complete (con_handle, cancelled_error);
+	}
+
 	nm_clear_g_cancellable (&priv->deactivating_cancellable);
 
 	nm_device_assume_state_reset (self);
@@ -14435,8 +14994,8 @@ dispose (GObject *object)
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ipx_changed), self);
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self);
 
-	g_slist_free_full (priv->arping.dad_list, (GDestroyNotify) nm_arping_manager_destroy);
-	priv->arping.dad_list = NULL;
+	g_slist_free_full (priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_destroy);
+	priv->acd.dad_list = NULL;
 
 	arp_cleanup (self);
 
@@ -14498,6 +15057,8 @@ dispose (GObject *object)
 		g_clear_object (&priv->lldp_listener);
 	}
 
+	nm_clear_g_source (&priv->concheck_p_cur_id);
+
 	G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
 
 	if (nm_clear_g_source (&priv->queued_state.id)) {
@@ -14520,7 +15081,7 @@ finalize (GObject *object)
 	g_free (priv->hw_addr_perm);
 	g_free (priv->hw_addr_initial);
 	g_slist_free (priv->pending_actions);
-	g_slist_free_full (priv->dad6_failed_addrs, g_free);
+	g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
 	g_clear_pointer (&priv->physical_port_id, g_free);
 	g_free (priv->udi);
 	g_free (priv->iface);
@@ -14529,19 +15090,22 @@ finalize (GObject *object)
 	g_free (priv->driver_version);
 	g_free (priv->firmware_version);
 	g_free (priv->type_desc);
-	g_free (priv->type_description);
 	g_free (priv->dhcp_anycast_address);
 	g_free (priv->current_stable_id);
 
 	g_hash_table_unref (priv->ip6_saved_properties);
 	g_hash_table_unref (priv->available_connections);
 
+	nm_dbus_track_obj_path_deinit (&priv->parent_device);
+	nm_dbus_track_obj_path_deinit (&priv->act_request);
+
 	G_OBJECT_CLASS (nm_device_parent_class)->finalize (object);
 
 	/* for testing, NMDeviceTest does not invoke NMDevice::constructed,
 	 * and thus @settings might be unset. */
-	if (priv->settings)
-		g_object_unref (priv->settings);
+	nm_g_object_unref (priv->settings);
+
+	nm_g_object_unref (priv->concheck_mgr);
 
 	g_object_unref (priv->netns);
 }
@@ -14651,9 +15215,6 @@ get_property (GObject *object, guint prop_id,
 {
 	NMDevice *self = NM_DEVICE (object);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	GPtrArray *array;
-	GHashTableIter iter;
-	NMConnection *connection;
 	GVariantBuilder array_builder;
 
 	switch (prop_id) {
@@ -14709,16 +15270,16 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->mtu);
 		break;
 	case PROP_IP4_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip4_config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip_config_4 : NULL);
 		break;
 	case PROP_DHCP4_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp4.config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp4.config : NULL);
 		break;
 	case PROP_IP6_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip6_config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip_config_6 : NULL);
 		break;
 	case PROP_DHCP6_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp6.config : NULL);
+		nm_dbus_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);
@@ -14728,7 +15289,7 @@ get_property (GObject *object, guint prop_id,
 		                      g_variant_new ("(uu)", priv->state, priv->state_reason));
 		break;
 	case PROP_ACTIVE_CONNECTION:
-		nm_utils_g_value_set_object_path (value, priv->act_request_public ? priv->act_request : NULL);
+		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->act_request));
 		break;
 	case PROP_DEVICE_TYPE:
 		g_value_set_uint (value, priv->type);
@@ -14759,12 +15320,9 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->rfkill_type);
 		break;
 	case PROP_AVAILABLE_CONNECTIONS:
-		array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections));
-		g_hash_table_iter_init (&iter, priv->available_connections);
-		while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL))
-			g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection)));
-		g_ptr_array_add (array, NULL);
-		g_value_take_boxed (value, (char **) g_ptr_array_free (array, FALSE));
+		nm_dbus_utils_g_value_set_object_path_from_hash (value,
+		                                                 priv->available_connections,
+		                                                 TRUE);
 		break;
 	case PROP_PHYSICAL_PORT_ID:
 		g_value_set_string (value, priv->physical_port_id);
@@ -14773,7 +15331,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_object (value, nm_device_get_master (self));
 		break;
 	case PROP_PARENT:
-		nm_utils_g_value_set_object_path (value, priv->parent_device);
+		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->parent_device));
 		break;
 	case PROP_HW_ADDRESS:
 		g_value_set_string (value, priv->hw_addr);
@@ -14818,7 +15376,7 @@ get_property (GObject *object, guint prop_id,
 
 			if (!NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
 				continue;
-			path = nm_exported_object_get_path ((NMExportedObject *) info->slave);
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (info->slave));
 			if (path)
 				slave_list[i++] = g_strdup (path);
 		}
@@ -14845,15 +15403,115 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"StateChanged",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("new_state", "u"),
+		NM_DEFINE_GDBUS_ARG_INFO ("old_state", "u"),
+		NM_DEFINE_GDBUS_ARG_INFO ("reason",    "u"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_device = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Reapply",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"),
+						NM_DEFINE_GDBUS_ARG_INFO ("version_id", "t"),
+						NM_DEFINE_GDBUS_ARG_INFO ("flags",      "u"),
+					),
+				),
+				.handle = impl_device_reapply,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetAppliedConnection",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("flags", "u"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"),
+						NM_DEFINE_GDBUS_ARG_INFO ("version_id", "t"),
+					),
+				),
+				.handle = impl_device_get_applied_connection,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Disconnect",
+				),
+				.handle = impl_device_disconnect,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Delete",
+				),
+				.handle = impl_device_delete,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&signal_info_state_changed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Udi",                  "s",      NM_DEVICE_UDI),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Interface",            "s",      NM_DEVICE_IFACE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("IpInterface",          "s",      NM_DEVICE_IP_IFACE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Driver",               "s",      NM_DEVICE_DRIVER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("DriverVersion",        "s",      NM_DEVICE_DRIVER_VERSION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("FirmwareVersion",      "s",      NM_DEVICE_FIRMWARE_VERSION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Capabilities",         "u",      NM_DEVICE_CAPABILITIES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Ip4Address",           "u",      NM_DEVICE_IP4_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("State",                "u",      NM_DEVICE_STATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("StateReason",          "(uu)",   NM_DEVICE_STATE_REASON),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("ActiveConnection",     "o",      NM_DEVICE_ACTIVE_CONNECTION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Ip4Config",            "o",      NM_DEVICE_IP4_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Dhcp4Config",          "o",      NM_DEVICE_DHCP4_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Ip6Config",            "o",      NM_DEVICE_IP6_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Dhcp6Config",          "o",      NM_DEVICE_DHCP6_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("Managed",              "b",      NM_DEVICE_MANAGED,               NM_AUTH_PERMISSION_NETWORK_CONTROL, NM_AUDIT_OP_DEVICE_MANAGED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("Autoconnect",          "b",      NM_DEVICE_AUTOCONNECT,           NM_AUTH_PERMISSION_NETWORK_CONTROL, NM_AUDIT_OP_DEVICE_AUTOCONNECT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("FirmwareMissing",      "b",      NM_DEVICE_FIRMWARE_MISSING),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("NmPluginMissing",      "b",      NM_DEVICE_NM_PLUGIN_MISSING),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("DeviceType",           "u",      NM_DEVICE_DEVICE_TYPE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("AvailableConnections", "ao",     NM_DEVICE_AVAILABLE_CONNECTIONS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("PhysicalPortId",       "s",      NM_DEVICE_PHYSICAL_PORT_ID),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Mtu",                  "u",      NM_DEVICE_MTU),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Metered",              "u",      NM_DEVICE_METERED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("LldpNeighbors",        "aa{sv}", NM_DEVICE_LLDP_NEIGHBORS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Real",                 "b",      NM_DEVICE_REAL),
+		),
+	),
+};
+
+const NMDBusInterfaceInfoExtended nm_interface_info_device_statistics = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_STATISTICS,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE ("RefreshRateMs", "u", NM_DEVICE_STATISTICS_REFRESH_RATE_MS, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS, NM_AUDIT_OP_STATISTICS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE     ("TxBytes",       "t", NM_DEVICE_STATISTICS_TX_BYTES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE     ("RxBytes",       "t", NM_DEVICE_STATISTICS_RX_BYTES),
+		),
+	),
+};
+
 static void
 nm_device_class_init (NMDeviceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 
 	g_type_class_add_private (object_class, sizeof (NMDevicePrivate));
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/Devices");
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/Devices");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device,
+	                                                              &nm_interface_info_device_statistics);
 
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
@@ -15085,7 +15743,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
-	/* Statistics */
 	obj_properties[PROP_REFRESH_RATE_MS] =
 	    g_param_spec_uint (NM_DEVICE_STATISTICS_REFRESH_RATE_MS, "", "",
 	                       0, UINT32_MAX, 0,
@@ -15102,7 +15759,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
-	/* Connectivity */
 	obj_properties[PROP_CONNECTIVITY] =
 	     g_param_spec_uint (NM_DEVICE_CONNECTIVITY, "", "",
 	                        NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
@@ -15185,15 +15841,11 @@ nm_device_class_init (NMDeviceClass *klass)
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 0);
 
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_SKELETON,
-	                                        "Reapply", impl_device_reapply,
-	                                        "GetAppliedConnection", impl_device_get_applied_connection,
-	                                        "Disconnect", impl_device_disconnect,
-	                                        "Delete", impl_device_delete,
-	                                        NULL);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_STATISTICS_SKELETON,
-	                                        NULL);
+	signals[CONNECTIVITY_CHANGED] =
+	    g_signal_new (NM_DEVICE_CONNECTIVITY_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
 }