summary refs log tree commit diff
path: root/src/devices/nm-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/nm-device.c')
-rw-r--r--src/devices/nm-device.c3107
1 files changed, 1722 insertions, 1385 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index bacbfb33..e979b875 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -34,12 +34,16 @@
 #include <arpa/inet.h>
 #include <fcntl.h>
 #include <linux/if_addr.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
 
 #include "nm-common-macros.h"
 #include "nm-device-private.h"
 #include "NetworkManagerUtils.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
 #include "dhcp/nm-dhcp-manager.h"
@@ -59,16 +63,16 @@
 #include "nm-netns.h"
 #include "nm-dispatcher.h"
 #include "nm-config.h"
+#include "nm-utils/c-list.h"
 #include "dns/nm-dns-manager.h"
 #include "nm-core-internal.h"
-#include "nm-default-route-manager.h"
-#include "nm-route-manager.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"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF (NMDevice);
@@ -82,6 +86,13 @@ _LOG_DECLARE_SELF (NMDevice);
 #define DHCP_NUM_TRIES_MAX     3
 #define DEFAULT_AUTOCONNECT    TRUE
 
+#define CARRIER_WAIT_TIME_MS 5000
+#define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000
+
+#define NM_DEVICE_AUTH_RETRIES_UNSET    -1
+#define NM_DEVICE_AUTH_RETRIES_INFINITY -2
+#define NM_DEVICE_AUTH_RETRIES_DEFAULT  3
+
 /*****************************************************************************/
 
 typedef void (*ActivationHandleFunc) (NMDevice *self);
@@ -106,6 +117,7 @@ typedef enum {
 } IpState;
 
 typedef struct {
+	CList lst_slave;
 	NMDevice *slave;
 	gulong watch_id;
 	bool slave_is_enslaved;
@@ -186,7 +198,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 	PROP_IFINDEX,
 	PROP_AVAILABLE_CONNECTIONS,
 	PROP_PHYSICAL_PORT_ID,
-	PROP_IS_MASTER,
 	PROP_MASTER,
 	PROP_PARENT,
 	PROP_HW_ADDRESS,
@@ -231,6 +242,8 @@ typedef struct _NMDevicePrivate {
 
 	int parent_ifindex;
 
+	int auth_retries;
+
 	union {
 		const guint8 hw_addr_len; /* read-only */
 		guint8 hw_addr_len_;
@@ -312,6 +325,17 @@ typedef struct _NMDevicePrivate {
 	guint32 mtu_initial;
 	guint32 ip6_mtu_initial;
 
+	guint32         v4_route_table;
+	guint32         v6_route_table;
+
+	/* when carrier goes away, we give a grace period of CARRIER_WAIT_TIME_MS
+	 * until taking action.
+	 *
+	 * When changing MTU, the device might take longer then that. So, whenever
+	 * NM changes the MTU it sets @carrier_wait_until_ms to CARRIER_WAIT_TIME_AFTER_MTU_MS
+	 * in the future. This is used to extend the grace period in this particular case. */
+	gint64          carrier_wait_until_ms;
+
 	bool            carrier:1;
 	bool            ignore_carrier:1;
 
@@ -322,10 +346,15 @@ typedef struct _NMDevicePrivate {
 	bool            v4_commit_first_time:1;
 	bool            v6_commit_first_time:1;
 
+	bool            default_route_metric_penalty_ip4_has:1;
+	bool            default_route_metric_penalty_ip6_has:1;
+
 	NMDeviceSysIfaceState sys_iface_state:2;
 
+	bool            v4_route_table_initalized:1;
+	bool            v6_route_table_initalized:1;
+
 	/* Generic DHCP stuff */
-	guint32         dhcp_timeout;
 	char *          dhcp_anycast_address;
 
 	char *          current_stable_id;
@@ -346,14 +375,7 @@ typedef struct _NMDevicePrivate {
 	NMIP4Config *   ext_ip4_config; /* Stuff added outside NM */
 	NMIP4Config *   wwan_ip4_config; /* WWAN configuration */
 	GSList *        vpn4_configs;   /* VPNs which use this device */
-	struct {
-		bool v4_has;
-		bool v4_is_assumed;
-		bool v6_has;
-		bool v6_is_assumed;
-		NMPlatformIP4Route v4;
-		NMPlatformIP6Route v6;
-	} default_route;
+
 	bool v4_has_shadowed_routes;
 	const char *ip4_rp_filter;
 
@@ -390,6 +412,7 @@ typedef struct _NMDevicePrivate {
 	/* IPv4LL stuff */
 	sd_ipv4ll *    ipv4ll;
 	guint          ipv4ll_timeout;
+	guint          rt6_temporary_not_available_id;
 
 	/* IPv4 DAD stuff */
 	struct {
@@ -411,6 +434,8 @@ typedef struct _NMDevicePrivate {
 	bool           nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
 	NMIP6Config *  dad6_ip6_config;
 
+	GHashTable *   rt6_temporary_not_available;
+
 	NMNDisc *      ndisc;
 	gulong         ndisc_changed_id;
 	gulong         ndisc_timeout_id;
@@ -452,8 +477,7 @@ typedef struct _NMDevicePrivate {
 	gulong          master_ready_id;
 
 	/* slave management */
-	bool            is_master;
-	GSList *        slaves;    /* list of SlaveInfo */
+	CList           slaves;    /* list of SlaveInfo */
 
 	NMMetered       metered;
 
@@ -485,19 +509,18 @@ G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_EXPORTED_OBJECT)
 
 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 nm_device_set_ip4_config (NMDevice *self,
                                           NMIP4Config *config,
-                                          guint32 default_route_metric,
                                           gboolean commit,
-                                          gboolean routes_full_sync);
+                                          GPtrArray *ip4_dev_route_blacklist);
 static gboolean ip4_config_merge_and_apply (NMDevice *self,
-                                            NMIP4Config *config,
                                             gboolean commit);
 
 static gboolean nm_device_set_ip6_config (NMDevice *self,
                                           NMIP6Config *config,
-                                          gboolean commit,
-                                          gboolean routes_full_sync);
+                                          gboolean commit);
 static gboolean ip6_config_merge_and_apply (NMDevice *self,
                                             gboolean commit);
 
@@ -514,7 +537,7 @@ static void nm_device_set_autoconnect_both (NMDevice *self, gboolean autoconnect
 static void nm_device_set_autoconnect_full (NMDevice *self, int autoconnect_intern, int autoconnect_user);
 
 static const char *_activation_func_to_string (ActivationHandleFunc func);
-static void activation_source_handle_cb (NMDevice *self, int family);
+static void activation_source_handle_cb (NMDevice *self, int addr_family);
 
 static void _set_state_full (NMDevice *self,
                              NMDeviceState state,
@@ -525,7 +548,7 @@ static gboolean queued_ip4_config_change (gpointer user_data);
 static gboolean queued_ip6_config_change (gpointer user_data);
 static void ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data);
 static gboolean ip_config_valid (NMDeviceState state);
-static NMActStageReturn dhcp4_start (NMDevice *self, NMConnection *connection);
+static NMActStageReturn dhcp4_start (NMDevice *self);
 static gboolean dhcp6_start (NMDevice *self, gboolean wait_for_ll);
 static void nm_device_start_ip_check (NMDevice *self);
 static void realize_start_setup (NMDevice *self,
@@ -534,8 +557,9 @@ static void realize_start_setup (NMDevice *self,
                                  const char *assume_state_connection_uuid,
                                  gboolean set_nm_owned,
                                  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 family, const char *reason);
+static void dhcp_schedule_restart (NMDevice *self, int addr_family, const char *reason);
 static void _cancel_activation (NMDevice *self);
 
 /*****************************************************************************/
@@ -628,6 +652,7 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_reason_to_string, NMDeviceStateReason,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_NEW_ACTIVATION,                 "new-activation"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PARENT_CHANGED,                 "parent-changed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED,         "parent-managed-changed"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_OVSDB_FAILED,                   "ovsdb-failed"),
 );
 
 #define reason_to_string(reason) \
@@ -647,12 +672,32 @@ nm_device_get_netns (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->netns;
 }
 
+NMDedupMultiIndex *
+nm_device_get_multi_index (NMDevice *self)
+{
+	return nm_netns_get_multi_idx (nm_device_get_netns (self));
+}
+
 NMPlatform *
 nm_device_get_platform (NMDevice *self)
 {
 	return nm_netns_get_platform (nm_device_get_netns (self));
 }
 
+static NMIP4Config *
+_ip4_config_new (NMDevice *self)
+{
+	return nm_ip4_config_new (nm_device_get_multi_index (self),
+	                          nm_device_get_ip_ifindex (self));
+}
+
+static NMIP6Config *
+_ip6_config_new (NMDevice *self)
+{
+	return nm_ip6_config_new (nm_device_get_multi_index (self),
+	                          nm_device_get_ip_ifindex (self));
+}
+
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_sys_iface_state_to_str, NMDeviceSysIfaceState,
@@ -714,6 +759,25 @@ nm_device_sys_iface_state_set (NMDevice *self,
 	nm_assert (priv->sys_iface_state == sys_iface_state);
 }
 
+static void
+_active_connection_set_state_flags_full (NMDevice *self,
+                                         NMActivationStateFlags flags,
+                                         NMActivationStateFlags mask)
+{
+	NMActiveConnection *ac;
+
+	ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (self));
+	if (ac)
+		nm_active_connection_set_state_flags_full (ac, flags, mask);
+}
+
+static void
+_active_connection_set_state_flags (NMDevice *self,
+                                    NMActivationStateFlags flags)
+{
+	_active_connection_set_state_flags_full (self, flags, flags);
+}
+
 /*****************************************************************************/
 
 void
@@ -796,26 +860,35 @@ nm_device_ipv4_sysctl_set (NMDevice *self, const char *property, const char *val
 	NMPlatform *platform = nm_device_get_platform (self);
 	gs_free char *value_to_free = NULL;
 	const char *value_to_set;
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	if (!nm_device_get_ip_ifindex (self))
+		return FALSE;
 
 	if (value) {
 		value_to_set = value;
 	} else {
 		/* Set to a default value when we've got a NULL @value. */
 		value_to_free = nm_platform_sysctl_get (platform,
-		                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip4_property_path ("default", property)));
+		                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, "default", property)));
 		value_to_set = value_to_free;
 	}
 
 	return nm_platform_sysctl_set (platform,
-	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip4_property_path (nm_device_get_ip_iface (self), property)),
+	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, nm_device_get_ip_iface (self), property)),
 	                               value_to_set);
 }
 
 static guint32
 nm_device_ipv4_sysctl_get_uint32 (NMDevice *self, const char *property, guint32 fallback)
 {
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	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_ip4_property_path (nm_device_get_ip_iface (self), property)),
+	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, nm_device_get_ip_iface (self), property)),
 	                                           10,
 	                                           0,
 	                                           G_MAXUINT32,
@@ -825,14 +898,24 @@ nm_device_ipv4_sysctl_get_uint32 (NMDevice *self, const char *property, guint32
 gboolean
 nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value)
 {
-	return nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), property)), value);
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	if (!nm_device_get_ip_ifindex (self))
+		return FALSE;
+
+	return nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), property)), value);
 }
 
 static guint32
 nm_device_ipv6_sysctl_get_uint32 (NMDevice *self, const char *property, guint32 fallback)
 {
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	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_ip6_property_path (nm_device_get_ip_iface (self), property)),
+	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), property)),
 	                                           10,
 	                                           0,
 	                                           G_MAXUINT32,
@@ -949,14 +1032,29 @@ _set_ip_state (NMDevice *self, int addr_family, IpState new_state)
 	IpState *p;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert_addr_family (addr_family);
 
-	p = addr_family == AF_INET ? &priv->ip4_state_ : &priv->ip6_state_;
+	p =   (addr_family == AF_INET)
+	    ? &priv->ip4_state_
+        : &priv->ip6_state_;
 
 	if (*p != new_state) {
-		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)", addr_family == AF_INET ? '4' : '6',
-		       (int) new_state, _ip_state_to_string (new_state));
+		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
+		       nm_utils_addr_family_to_char (addr_family),
+		       (int) new_state,
+		       _ip_state_to_string (new_state));
 		*p = new_state;
+
+		if (new_state == IP_DONE) {
+			/* we only set the IPx_READY flag once we reach IP_DONE state. We don't
+			 * ever clear it, even if we later enter IP_FAIL state.
+			 *
+			 * This is not documented/guaranteed behavior, but seems to make sense for now. */
+			_active_connection_set_state_flags (self,
+			                                    addr_family == AF_INET
+			                                      ? NM_ACTIVATION_STATE_FLAG_IP4_READY
+			                                      : NM_ACTIVATION_STATE_FLAG_IP6_READY);
+		}
 	}
 }
 
@@ -978,6 +1076,48 @@ nm_device_get_iface (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->iface;
 }
 
+gboolean
+nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const NMPlatformLink *plink;
+	NMPlatform *platform;
+	gboolean up, success = TRUE;
+	int ifindex;
+
+	g_return_val_if_fail (priv->ifindex <= 0, FALSE);
+	g_return_val_if_fail (ifname, FALSE);
+
+	NM_SET_OUT (renamed, FALSE);
+
+	platform = nm_device_get_platform (self);
+	plink = nm_platform_link_get_by_ifname (platform, ifname);
+	if (!plink)
+		return FALSE;
+
+	ifindex = plink->ifindex;
+
+	if (!nm_streq (ifname, nm_device_get_iface (self))) {
+		up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
+
+		/* Rename the link to the device ifname */
+		if (up)
+			nm_platform_link_set_down (platform, ifindex);
+		success = nm_platform_link_set_name (platform, ifindex, nm_device_get_iface (self));
+		if (up)
+			nm_platform_link_set_up (platform, ifindex, NULL);
+
+		NM_SET_OUT (renamed, success);
+	}
+
+	if (success) {
+		priv->ifindex = ifindex;
+		_notify (self, PROP_IFINDEX);
+	}
+
+	return success;
+}
+
 int
 nm_device_get_ifindex (NMDevice *self)
 {
@@ -1091,7 +1231,8 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface)
 	}
 
 	if (priv->ip_ifindex > 0) {
-		if (nm_platform_check_support_user_ipv6ll (nm_device_get_platform (self)))
+		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
+		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
 			nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ip_ifindex, TRUE);
 
 		if (!nm_platform_link_is_up (nm_device_get_platform (self), priv->ip_ifindex))
@@ -1478,23 +1619,14 @@ nm_device_get_metered (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->metered;
 }
 
-/**
- * nm_device_get_priority():
- * @self: the #NMDevice
- *
- * Returns: the device's routing priority.  Lower numbers means a "better"
- *  device, eg higher priority.
- */
-int
-nm_device_get_priority (NMDevice *self)
+static guint32
+_get_route_metric_default (NMDevice *self)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (self), 1000);
-
 	/* Device 'priority' is used for the default route-metric and is based on
 	 * the device type. The settings ipv4.route-metric and ipv6.route-metric
 	 * can overwrite this default.
 	 *
-	 * Currently for both IPv4 and IPv6 we use the same default values.
+	 * For both IPv4 and IPv6 we use the same default values.
 	 *
 	 * The route-metric is used for the metric of the routes of device.
 	 * This also applies to the default route. Therefore it affects also
@@ -1532,6 +1664,8 @@ nm_device_get_priority (NMDevice *self)
 		return 425;
 	case NM_DEVICE_TYPE_TUN:
 		return 450;
+	case NM_DEVICE_TYPE_PPP:
+		return 460;
 	case NM_DEVICE_TYPE_VXLAN:
 		return 500;
 	case NM_DEVICE_TYPE_DUMMY:
@@ -1546,6 +1680,10 @@ nm_device_get_priority (NMDevice *self)
 		return 700;
 	case NM_DEVICE_TYPE_BT:
 		return 750;
+	case NM_DEVICE_TYPE_OVS_BRIDGE:
+	case NM_DEVICE_TYPE_OVS_INTERFACE:
+	case NM_DEVICE_TYPE_OVS_PORT:
+		return 800;
 	case NM_DEVICE_TYPE_GENERIC:
 		return 950;
 	case NM_DEVICE_TYPE_UNKNOWN:
@@ -1558,29 +1696,40 @@ nm_device_get_priority (NMDevice *self)
 	return 11000;
 }
 
-static guint32
-route_metric_with_penalty (NMDevice *self, guint32 metric)
+static gboolean
+default_route_metric_penalty_detect (NMDevice *self)
 {
 #if WITH_CONCHECK
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const guint32 PENALTY = 20000;
-
-	/* Beware: for IPv6, a metric of 0 effectively means 1024.
-	 * Only pass a normalized IPv6 metric (nm_utils_ip6_route_metric_normalize). */
 
+	/* 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 ())) {
-		if (metric >= G_MAXUINT32 - PENALTY)
-			return G_MAXUINT32;
-		return metric + PENALTY;
+		&& nm_connectivity_check_enabled (nm_connectivity_get ())) {
+		return TRUE;
 	}
 #endif
-	return metric;
+
+	return FALSE;
 }
 
 static guint32
-_get_ipx_route_metric (NMDevice *self,
-                       gboolean is_v4)
+default_route_metric_penalty_get (NMDevice *self, int addr_family)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert_addr_family (addr_family);
+
+	if (  addr_family == AF_INET
+	    ? priv->default_route_metric_penalty_ip4_has
+	    : priv->default_route_metric_penalty_ip6_has)
+		return 20000;
+	return 0;
+}
+
+guint32
+nm_device_get_route_metric (NMDevice *self,
+                            int addr_family)
 {
 	char *value;
 	gint64 route_metric;
@@ -1588,10 +1737,11 @@ _get_ipx_route_metric (NMDevice *self,
 	NMConnection *connection;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), G_MAXUINT32);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), G_MAXUINT32);
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		s_ip = is_v4
+		s_ip = addr_family == AF_INET
 		       ? nm_connection_get_setting_ip4_config (connection)
 		       : nm_connection_get_setting_ip6_config (connection);
 
@@ -1610,7 +1760,7 @@ _get_ipx_route_metric (NMDevice *self,
 	 * Note that that means that the route-metric might change between SIGHUP.
 	 * You must cache the returned value if that is a problem. */
 	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               is_v4 ? "ipv4.route-metric" : "ipv6.route-metric", self);
+	                                               addr_family == AF_INET ? "ipv4.route-metric" : "ipv6.route-metric", self);
 	if (value) {
 		route_metric = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1);
 		g_free (value);
@@ -1618,81 +1768,96 @@ _get_ipx_route_metric (NMDevice *self,
 		if (route_metric >= 0)
 			goto out;
 	}
-	route_metric = nm_device_get_priority (self);
+	route_metric = _get_route_metric_default (self);
 out:
-	if (!is_v4)
-		route_metric = nm_utils_ip6_route_metric_normalize (route_metric);
-	return route_metric;
+	return nm_utils_ip_route_metric_normalize (addr_family, route_metric);
 }
 
 guint32
-nm_device_get_ip4_route_metric (NMDevice *self)
+nm_device_get_route_table (NMDevice *self,
+                           int addr_family,
+                           gboolean fallback_main)
 {
-	return _get_ipx_route_metric (self, TRUE);
-}
+	NMDevicePrivate *priv;
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	guint32 route_table = 0;
 
-guint32
-nm_device_get_ip6_route_metric (NMDevice *self)
-{
-	return _get_ipx_route_metric (self, FALSE);
-}
+	nm_assert_addr_family (addr_family);
 
-static void
-_update_default_route (NMDevice *self, int addr_family, gboolean has, gboolean is_assumed)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	bool *p_has, *p_is_assumed;
+	g_return_val_if_fail (NM_IS_DEVICE (self), RT_TABLE_MAIN);
 
-	nm_assert (NM_IN_SET (addr_family, 0, AF_INET, AF_INET6));
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
+	/* the route table setting affects how we sync routes. We shall
+	 * not change it while the device is active, hence, cache it. */
 	if (addr_family == AF_INET) {
-		p_has = &priv->default_route.v4_has;
-		p_is_assumed = &priv->default_route.v4_is_assumed;
+		if (priv->v4_route_table_initalized)
+			return priv->v4_route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
 	} else {
-		p_has = &priv->default_route.v6_has;
-		p_is_assumed = &priv->default_route.v6_is_assumed;
+		if (priv->v6_route_table_initalized)
+			return priv->v6_route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
 	}
 
-	if (*p_has == has && *p_is_assumed == is_assumed)
-		return;
+	connection = nm_device_get_applied_connection (self);
+	if (connection) {
+		if (addr_family == AF_INET)
+			s_ip = nm_connection_get_setting_ip4_config (connection);
+		else
+			s_ip = nm_connection_get_setting_ip6_config (connection);
 
-	*p_has = has;
-	*p_is_assumed = is_assumed;
+		if (s_ip)
+			route_table = nm_setting_ip_config_get_route_table (s_ip);
 
-	if (addr_family == AF_INET)
-		nm_default_route_manager_ip4_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-	else
-		nm_default_route_manager_ip6_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-}
-
-const NMPlatformIP4Route *
-nm_device_get_ip4_default_route (NMDevice *self, gboolean *out_is_assumed)
-{
-	NMDevicePrivate *priv;
+		/* we only lookup the global default if we also have an applied
+		 * connection. Otherwise, the connection is not active, and the
+		 * connection default doesn't matter. */
+		if (route_table == 0) {
+			gs_free char *value = NULL;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+			                                               addr_family == AF_INET
+			                                                 ? "ipv4.route-table"
+			                                                 : "ipv6.route-table",
+			                                               self);
+			route_table = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, 0);
+		}
+	}
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
+	if (addr_family == AF_INET) {
+		priv->v4_route_table_initalized = TRUE;
+		priv->v4_route_table = route_table;
+	} else {
+		priv->v6_route_table_initalized = TRUE;
+		priv->v6_route_table = route_table;
+	}
 
-	if (out_is_assumed)
-		*out_is_assumed = priv->default_route.v4_is_assumed;
+	_LOGT (LOGD_DEVICE,
+	       "ipv%c.route-table = %u%s",
+	       addr_family == AF_INET ? '4' : '6',
+	       (guint) (route_table ?: RT_TABLE_MAIN),
+	       route_table ? "" : " (policy routing not enabled)");
 
-	return priv->default_route.v4_has ? &priv->default_route.v4 : NULL;
+	return route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
 }
 
-const NMPlatformIP6Route *
-nm_device_get_ip6_default_route (NMDevice *self, gboolean *out_is_assumed)
+const NMPObject *
+nm_device_get_best_default_route (NMDevice *self,
+                                  int addr_family)
 {
-	NMDevicePrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
-	if (out_is_assumed)
-		*out_is_assumed = priv->default_route.v6_is_assumed;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	return priv->default_route.v6_has ? &priv->default_route.v6 : NULL;
+	switch (addr_family) {
+	case AF_INET:
+		return priv->ip4_config ? nm_ip4_config_best_default_route_get (priv->ip4_config) : NULL;
+	case AF_INET6:
+		return priv->ip6_config ? nm_ip6_config_best_default_route_get (priv->ip6_config) : 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);
+	default:
+		g_return_val_if_reached (NULL);
+	}
 }
 
 const char *
@@ -1809,7 +1974,7 @@ update_connectivity_state (NMDevice *self, NMConnectivityState state)
 	/* If the connectivity check is disabled, make an optimistic guess. */
 	if (state == NM_CONNECTIVITY_UNKNOWN) {
 		if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
-			if (priv->default_route.v4_has || priv->default_route.v6_has)
+			if (nm_device_get_best_default_route (self, AF_UNSPEC))
 				state = NM_CONNECTIVITY_FULL;
 			else
 				state = NM_CONNECTIVITY_LIMITED;
@@ -1829,12 +1994,12 @@ update_connectivity_state (NMDevice *self, NMConnectivityState state)
 
 		if (   priv->state == NM_DEVICE_STATE_ACTIVATED
 		    && !nm_device_sys_iface_state_is_external (self)) {
-			if (   priv->default_route.v4_has
-			    && !ip4_config_merge_and_apply (self, NULL, TRUE))
-				_LOGW (LOGD_IP4, "Failed to update IPv4 default route metric");
-			if (   priv->default_route.v6_has
+			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 default route metric");
+				_LOGW (LOGD_IP6, "Failed to update IPv6 route metric");
 		}
 	}
 }
@@ -1948,7 +2113,7 @@ concheck_periodic_update (NMDevice *self)
 	gboolean check_enable;
 
 	check_enable =    (priv->state == NM_DEVICE_STATE_ACTIVATED)
-	               && (priv->default_route.v4_has || priv->default_route.v6_has);
+	               && nm_device_get_best_default_route (self, AF_UNSPEC);
 
 	if (check_enable && !priv->concheck_periodic_id) {
 		/* We just gained a default route. Enable periodic checking. */
@@ -1976,11 +2141,11 @@ static SlaveInfo *
 find_slave_info (NMDevice *self, NMDevice *slave)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	CList *iter;
 	SlaveInfo *info;
-	GSList *iter;
 
-	for (iter = priv->slaves; iter; iter = g_slist_next (iter)) {
-		info = iter->data;
+	c_list_for_each (iter, &priv->slaves) {
+		info = c_list_entry (iter, SlaveInfo, lst_slave);
 		if (info->slave == slave)
 			return info;
 	}
@@ -2100,13 +2265,19 @@ nm_device_master_release_one_slave (NMDevice *self, NMDevice *slave, gboolean co
 	 * Transfers ownership from slave_priv->master.  */
 	self_free = self;
 
-	priv->slaves = g_slist_remove (priv->slaves, info);
+	c_list_unlink_init (&info->lst_slave);
 	slave_priv->master = NULL;
 
 	g_signal_handler_disconnect (slave, info->watch_id);
 	g_object_unref (slave);
 	g_slice_free (SlaveInfo, info);
 
+	if (c_list_is_empty (&priv->slaves)) {
+		_active_connection_set_state_flags_full (self,
+		                                         0,
+		                                         NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES);
+	}
+
 	/* Ensure the device's hardware address is up-to-date; it often changes
 	 * when slaves change.
 	 */
@@ -2140,7 +2311,8 @@ is_unmanaged_external_down (NMDevice *self, gboolean consider_can)
 	/* Manage externally-created software interfaces only when they are IFF_UP */
 	if (   priv->ifindex <= 0
 	    || !priv->up
-	    || !(priv->slaves || nm_platform_link_can_assume (nm_device_get_platform (self), priv->ifindex)))
+	    || !(   !c_list_is_empty (&priv->slaves)
+	         || nm_platform_link_can_assume (nm_device_get_platform (self), priv->ifindex)))
 		return NM_UNMAN_FLAG_OP_SET_UNMANAGED;
 
 	return NM_UNMAN_FLAG_OP_SET_MANAGED;
@@ -2235,8 +2407,6 @@ carrier_changed (NMDevice *self, gboolean carrier)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	NM_DEVICE_GET_CLASS (self)->carrier_changed_notify (self, carrier);
-
 	if (priv->state <= NM_DEVICE_STATE_UNMANAGED)
 		return;
 
@@ -2246,21 +2416,22 @@ carrier_changed (NMDevice *self, gboolean carrier)
 	if (priv->ignore_carrier && !carrier)
 		return;
 
-	if (priv->is_master) {
-		/* Bridge/bond/team carrier does not affect its own activation,
-		 * but when carrier comes on, if there are slaves waiting,
-		 * it will restart them.
-		 */
-		if (!carrier)
+	if (nm_device_is_master (self)) {
+		if (carrier) {
+			/* Force master to retry getting ip addresses when carrier
+			* is restored. */
+			if (priv->state == NM_DEVICE_STATE_ACTIVATED)
+				nm_device_update_dynamic_ip_setup (self);
+			else {
+				if (nm_device_activate_ip4_state_in_wait (self))
+					nm_device_activate_stage3_ip4_start (self);
+				if (nm_device_activate_ip6_state_in_wait (self))
+					nm_device_activate_stage3_ip6_start (self);
+			}
 			return;
-
-		if (nm_device_activate_ip4_state_in_wait (self))
-			nm_device_activate_stage3_ip4_start (self);
-		if (nm_device_activate_ip6_state_in_wait (self))
-			nm_device_activate_stage3_ip6_start (self);
-
-		return;
-	} else if (nm_device_get_enslaved (self) && !carrier) {
+		}
+		/* fall-through and change state of device */
+	} else if (priv->is_enslaved && !carrier) {
 		/* Slaves don't deactivate when they lose carrier; for
 		 * bonds/teams in particular that would be actively
 		 * counterproductive.
@@ -2306,7 +2477,7 @@ carrier_disconnected_action_cb (gpointer user_data)
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_LOGD (LOGD_DEVICE, "link disconnected (calling deferred action) (id=%u)", priv->carrier_defer_id);
+	_LOGD (LOGD_DEVICE, "carrier: link disconnected (calling deferred action) (id=%u)", priv->carrier_defer_id);
 
 	priv->carrier_defer_id = 0;
 	carrier_changed (self, FALSE);
@@ -2320,7 +2491,7 @@ carrier_disconnected_action_cancel (NMDevice *self)
 	guint id = priv->carrier_defer_id;
 
 	if (nm_clear_g_source (&priv->carrier_defer_id)) {
-		_LOGD (LOGD_DEVICE, "link disconnected (canceling deferred action) (id=%u)",
+		_LOGD (LOGD_DEVICE, "carrier: link disconnected (canceling deferred action) (id=%u)",
 		       id);
 	}
 }
@@ -2338,8 +2509,9 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 	_notify (self, PROP_CARRIER);
 
 	if (priv->carrier) {
-		_LOGI (LOGD_DEVICE, "link connected");
+		_LOGI (LOGD_DEVICE, "carrier: link connected");
 		carrier_disconnected_action_cancel (self);
+		NM_DEVICE_GET_CLASS (self)->carrier_changed_notify (self, carrier);
 		carrier_changed (self, TRUE);
 
 		if (priv->carrier_wait_id) {
@@ -2349,14 +2521,15 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 	} else {
 		if (priv->carrier_wait_id)
 			nm_device_add_pending_action (self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
+		NM_DEVICE_GET_CLASS (self)->carrier_changed_notify (self, carrier);
 		if (   state <= NM_DEVICE_STATE_DISCONNECTED
 		    && !priv->queued_act_request) {
-			_LOGD (LOGD_DEVICE, "link disconnected");
+			_LOGD (LOGD_DEVICE, "carrier: link disconnected");
 			carrier_changed (self, FALSE);
 		} else {
 			priv->carrier_defer_id = g_timeout_add_seconds (LINK_DISCONNECT_DELAY,
 			                                                carrier_disconnected_action_cb, self);
-			_LOGD (LOGD_DEVICE, "link disconnected (deferring action for %d seconds) (id=%u)",
+			_LOGD (LOGD_DEVICE, "carrier: link disconnected (deferring action for %d seconds) (id=%u)",
 			       LINK_DISCONNECT_DELAY, priv->carrier_defer_id);
 		}
 	}
@@ -2388,12 +2561,27 @@ static void
 device_recheck_slave_status (NMDevice *self, const NMPlatformLink *plink)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevice *master;
+	nm_auto_nmpobj const NMPObject *plink_master_keep_alive = NULL;
+	const NMPlatformLink *plink_master;
 
 	g_return_if_fail (plink);
 
 	if (plink->master <= 0)
 		return;
 
+	master = nm_manager_get_device_by_ifindex (nm_manager_get (), plink->master);
+	plink_master = nm_platform_link_get (nm_device_get_platform (self), plink->master);
+	plink_master_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink_master));
+
+	if (   master == NULL
+	    && plink_master
+	    && g_strcmp0 (plink_master->name, "ovs-system") == 0
+	    && plink_master->type == NM_LINK_TYPE_OPENVSWITCH) {
+		_LOGD (LOGD_DEVICE, "the device claimed by openvswitch");
+		return;
+	}
+
 	if (priv->master) {
 		if (   plink->master > 0
 		    && plink->master == nm_device_get_ifindex (priv->master)) {
@@ -2405,20 +2593,16 @@ device_recheck_slave_status (NMDevice *self, const NMPlatformLink *plink)
 
 		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 	}
-	if (plink->master > 0) {
-		NMDevice *master;
 
-		master = nm_manager_get_device_by_ifindex (nm_manager_get (), plink->master);
-		if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave)
-			nm_device_master_add_slave (master, self, FALSE);
-		else if (master) {
-			_LOGI (LOGD_DEVICE, "enslaved to non-master-type device %s; ignoring",
-			       nm_device_get_iface (master));
-		} else {
-			_LOGW (LOGD_DEVICE, "enslaved to unknown device %d %s",
-			       plink->master,
-			       nm_platform_link_get_name (nm_device_get_platform (self), plink->master));
-		}
+	if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave)
+		nm_device_master_add_slave (master, self, FALSE);
+	else if (master) {
+		_LOGI (LOGD_DEVICE, "enslaved to non-master-type device %s; ignoring",
+		       nm_device_get_iface (master));
+	} else {
+		_LOGW (LOGD_DEVICE, "enslaved to unknown device %d (%s%s%s)",
+		       plink->master,
+		       NM_PRINT_FMT_QUOTED (plink_master, "\"", plink_master->name, "\"", "??"));
 	}
 }
 
@@ -2429,16 +2613,19 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 	gint32 now;
 	GArray *addresses, *dns_servers, *dns_domains;
 	guint len, i;
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter ipconf_iter;
 
 	if (nm_ndisc_get_node_type (ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
 		return;
 
 	now = nm_utils_get_monotonic_timestamp_s ();
 
-	len = nm_ip6_config_get_num_addresses (priv->ip6_config);
-	addresses = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscAddress), len);
-	for (i = 0; i < len; i++) {
-		const NMPlatformIP6Address *addr = nm_ip6_config_get_address (priv->ip6_config, i);
+	head_entry = nm_ip6_config_lookup_addresses (priv->ip6_config);
+	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;
 
 		if (IN6_IS_ADDR_LINKLOCAL (&addr->address))
@@ -2497,8 +2684,7 @@ device_link_changed (NMDevice *self)
 	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean ip_ifname_changed = FALSE;
-	const char *udi;
-	NMPlatformLink info;
+	nm_auto_nmpobj const NMPObject *pllink_keep_alive = NULL;
 	const NMPlatformLink *pllink;
 	int ifindex;
 	gboolean was_up;
@@ -2512,40 +2698,20 @@ device_link_changed (NMDevice *self)
 	if (!pllink)
 		return G_SOURCE_REMOVE;
 
-	info = *pllink;
+	pllink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (pllink));
 
-	udi = nm_platform_link_get_udi (nm_device_get_platform (self), info.ifindex);
-	if (udi && !nm_streq0 (udi, priv->udi)) {
-		/* Update UDI to what udev gives us */
-		g_free (priv->udi);
-		priv->udi = g_strdup (udi);
-		_notify (self, PROP_UDI);
-	}
-
-	if (!nm_streq0 (info.driver, priv->driver)) {
-		g_free (priv->driver);
-		priv->driver = g_strdup (info.driver);
-		_notify (self, PROP_DRIVER);
-	}
-
-	if (priv->mtu != info.mtu) {
-		priv->mtu = info.mtu;
-		_notify (self, PROP_MTU);
-	}
-
-	if (ifindex == nm_device_get_ip_ifindex (self))
-		_stats_update_counters_from_pllink (self, &info);
+	nm_device_update_from_platform_link (self, pllink);
 
 	had_hw_addr = (priv->hw_addr != NULL);
 	nm_device_update_hw_address (self);
 	got_hw_addr = (!had_hw_addr && priv->hw_addr);
 	nm_device_update_permanent_hw_address (self, FALSE);
 
-	if (info.name[0] && strcmp (priv->iface, info.name) != 0) {
+	if (pllink->name[0] && strcmp (priv->iface, pllink->name) != 0) {
 		_LOGI (LOGD_DEVICE, "interface index %d renamed iface from '%s' to '%s'",
-		       priv->ifindex, priv->iface, info.name);
+		       priv->ifindex, priv->iface, pllink->name);
 		g_free (priv->iface);
-		priv->iface = g_strdup (info.name);
+		priv->iface = g_strdup (pllink->name);
 
 		/* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */
 		ip_ifname_changed = !priv->ip_iface;
@@ -2568,8 +2734,8 @@ device_link_changed (NMDevice *self)
 		nm_device_emit_recheck_auto_activate (self);
 	}
 
-	if (priv->ndisc && info.inet6_token.id) {
-		if (nm_ndisc_set_iid (priv->ndisc, info.inet6_token))
+	if (priv->ndisc && pllink->inet6_token.id) {
+		if (nm_ndisc_set_iid (priv->ndisc, pllink->inet6_token))
 			_LOGD (LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface);
 	}
 
@@ -2578,20 +2744,21 @@ device_link_changed (NMDevice *self)
 	    && !nm_device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
 		nm_device_set_carrier (self, pllink->connected);
 
-	klass->link_changed (self, &info);
+	klass->link_changed (self, pllink);
 
 	/* Update DHCP, etc, if needed */
 	if (ip_ifname_changed)
 		nm_device_update_dynamic_ip_setup (self);
 
 	was_up = priv->up;
-	priv->up = NM_FLAGS_HAS (info.n_ifi_flags, IFF_UP);
+	priv->up = NM_FLAGS_HAS (pllink->n_ifi_flags, IFF_UP);
 
-	if (   info.initialized
+	if (   pllink->initialized
 	    && nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 		NMDeviceStateReason reason;
 
 		nm_device_set_unmanaged_by_user_udev (self);
+		nm_device_set_unmanaged_by_user_conf (self);
 
 		reason = NM_DEVICE_STATE_REASON_NOW_MANAGED;
 
@@ -2616,13 +2783,13 @@ device_link_changed (NMDevice *self)
 
 	set_unmanaged_external_down (self, FALSE);
 
-	device_recheck_slave_status (self, &info);
+	device_recheck_slave_status (self, pllink);
 
 	if (priv->up && !was_up) {
 		/* 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, NULL, TRUE))
+			if (!ip4_config_merge_and_apply (self, TRUE))
 				_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
 		}
 		if (priv->ip6_state == IP_DONE) {
@@ -2703,6 +2870,103 @@ link_changed_cb (NMPlatform *platform,
 	}
 }
 
+/*****************************************************************************/
+
+typedef struct {
+	in_addr_t network;
+	guint8 plen;
+} IP4RPFilterData;
+
+static guint
+_v4_has_shadowed_routes_detect_hash (const IP4RPFilterData *d)
+{
+	NMHashState h;
+
+	nm_hash_init (&h, 1105201169u);
+	nm_hash_update_vals (&h,
+	                     d->network,
+	                     d->plen);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_v4_has_shadowed_routes_detect_equal (const IP4RPFilterData *d1, const IP4RPFilterData *d2)
+{
+	return d1->network == d2->network && d1->plen == d2->plen;
+}
+
+static gboolean
+_v4_has_shadowed_routes_detect (NMDevice *self)
+{
+	NMPlatform *platform;
+	int ifindex;
+	NMPLookup lookup;
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *o;
+	guint data_len;
+	gs_unref_hashtable GHashTable *data_hash = NULL;
+	gs_free IP4RPFilterData *data_arr = NULL;
+
+	ifindex = nm_device_get_ip_ifindex (self);
+	if (ifindex <= 0)
+		return FALSE;
+
+	platform = nm_device_get_platform (self);
+
+	head_entry = nm_platform_lookup (platform,
+	                                 nmp_lookup_init_addrroute (&lookup,
+	                                                            NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                            ifindex));
+	if (!head_entry)
+		return FALSE;
+
+	/* first, create a lookup index @data_hash for all network/plen pairs. */
+	data_len = 0;
+	data_arr = g_new (IP4RPFilterData, head_entry->len);
+	data_hash = g_hash_table_new ((GHashFunc) _v4_has_shadowed_routes_detect_hash,
+	                              (GEqualFunc) _v4_has_shadowed_routes_detect_equal);
+
+	nmp_cache_iter_for_each (&iter, head_entry, &o) {
+		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
+		IP4RPFilterData *d;
+
+		nm_assert (r->ifindex == ifindex);
+
+		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    || r->table_coerced)
+			continue;
+
+		d = &data_arr[data_len++];
+		d->network = nm_utils_ip4_address_clear_host_address (r->network, r->plen);
+		d->plen = r->plen;
+		g_hash_table_add (data_hash, d);
+	}
+
+	/* then, search if there is any route on another interface with the same
+	 * network/plen destination. If yes, we consider this a multihoming
+	 * setup. */
+	head_entry = nm_platform_lookup (platform,
+	                                 nmp_lookup_init_obj_type (&lookup,
+	                                                           NMP_OBJECT_TYPE_IP4_ROUTE));
+	nmp_cache_iter_for_each (&iter, head_entry, &o) {
+		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
+		IP4RPFilterData d;
+
+		if (   r->ifindex == ifindex
+		    || NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    || r->table_coerced)
+			continue;
+
+		d.network = nm_utils_ip4_address_clear_host_address (r->network, r->plen);
+		d.plen = r->plen;
+		if (g_hash_table_contains (data_hash, &d))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
 static void
 ip4_rp_filter_update (NMDevice *self)
 {
@@ -2710,7 +2974,7 @@ ip4_rp_filter_update (NMDevice *self)
 	const char *ip4_rp_filter;
 
 	if (   priv->v4_has_shadowed_routes
-	    || priv->default_route.v4_has) {
+	    || nm_device_get_best_default_route (self, AF_INET)) {
 		if (nm_device_ipv4_sysctl_get_uint32 (self, "rp_filter", 0) != 1) {
 			/* Don't touch the rp_filter if it's not strict. */
 			return;
@@ -2729,20 +2993,6 @@ ip4_rp_filter_update (NMDevice *self)
 }
 
 static void
-ip4_routes_changed_changed_cb (NMRouteManager *route_manager, NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ifindex = nm_device_get_ip_ifindex (self);
-
-	if (nm_device_sys_iface_state_is_external_or_assume (self))
-		return;
-
-	priv->v4_has_shadowed_routes = nm_route_manager_ip4_routes_shadowed (route_manager,
-	                                                                     ifindex);
-	ip4_rp_filter_update (self);
-}
-
-static void
 link_changed (NMDevice *self, const NMPlatformLink *pllink)
 {
 	/* stub implementation of virtual function to allow subclasses to chain up. */
@@ -2822,7 +3072,9 @@ nm_device_realize_start (NMDevice *self,
                          gboolean *out_compatible,
                          GError **error)
 {
-	NMPlatformLink plink_copy;
+	nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
+
+	nm_assert (!plink || NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (plink)) == NMP_OBJECT_TYPE_LINK);
 
 	NM_SET_OUT (out_compatible, TRUE);
 
@@ -2836,13 +3088,12 @@ nm_device_realize_start (NMDevice *self,
 
 		if (!link_type_compatible (self, plink->type, out_compatible, error))
 			return FALSE;
-	}
 
-	if (plink) {
-		plink_copy = *plink;
-		plink = &plink_copy;
+		plink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
 	}
-	realize_start_setup (self, plink,
+
+	realize_start_setup (self,
+	                     plink,
 	                     assume_state_guess_assume,
 	                     assume_state_connection_uuid,
 	                     set_nm_owned,
@@ -2868,8 +3119,8 @@ nm_device_create_and_realize (NMDevice *self,
                               NMDevice *parent,
                               GError **error)
 {
+	nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMPlatformLink plink_copy;
 	const NMPlatformLink *plink = NULL;
 
 	/* Must be set before device is realized */
@@ -2881,11 +3132,14 @@ nm_device_create_and_realize (NMDevice *self,
 	if (NM_DEVICE_GET_CLASS (self)->create_and_realize) {
 		if (!NM_DEVICE_GET_CLASS (self)->create_and_realize (self, connection, parent, &plink, error))
 			return FALSE;
-		plink_copy = *plink;
-		plink = &plink_copy;
+		if (plink) {
+			nm_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (plink)) == NMP_OBJECT_TYPE_LINK);
+			plink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
+		}
 	}
 
-	realize_start_setup (self, plink,
+	realize_start_setup (self,
+	                     plink,
 	                     FALSE, /* assume_state_guess_assume */
 	                     NULL,  /* assume_state_connection_uuid */
 	                     FALSE, NM_UNMAN_FLAG_OP_FORGET);
@@ -2899,38 +3153,54 @@ nm_device_create_and_realize (NMDevice *self,
 	return TRUE;
 }
 
-static void
-update_device_from_platform_link (NMDevice *self, const NMPlatformLink *plink)
+void
+nm_device_update_from_platform_link (NMDevice *self, const NMPlatformLink *plink)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *udi;
+	const char *str;
+	int ifindex;
+	guint32 mtu;
 
-	g_return_if_fail (plink != NULL);
+	g_return_if_fail (plink == NULL || link_type_compatible (self, plink->type, NULL, NULL));
 
-	udi = nm_platform_link_get_udi (nm_device_get_platform (self), plink->ifindex);
-	if (udi && !nm_streq0 (udi, priv->udi)) {
+	str = plink ? nm_platform_link_get_udi (nm_device_get_platform (self), plink->ifindex) : NULL;
+	if (g_strcmp0 (str, priv->udi)) {
 		g_free (priv->udi);
-		priv->udi = g_strdup (udi);
+		priv->udi = g_strdup (str);
 		_notify (self, PROP_UDI);
 	}
 
-	if (!g_strcmp0 (plink->name, priv->iface)) {
+	str = plink ? plink->name : NULL;
+	if (str && g_strcmp0 (str, priv->iface)) {
 		g_free (priv->iface);
-		priv->iface = g_strdup (plink->name);
+		priv->iface = g_strdup (str);
 		_notify (self, PROP_IFACE);
 	}
 
-	if (priv->ifindex != plink->ifindex) {
-		priv->ifindex = plink->ifindex;
-		_notify (self, PROP_IFINDEX);
-	}
-
-	priv->up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
-	if (plink->driver && g_strcmp0 (plink->driver, priv->driver) != 0) {
+	str = plink ? plink->driver : NULL;
+	if (g_strcmp0 (str, priv->driver) != 0) {
 		g_free (priv->driver);
-		priv->driver = g_strdup (plink->driver);
+		priv->driver = g_strdup (str);
 		_notify (self, PROP_DRIVER);
 	}
+
+	if (plink) {
+		priv->up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
+		if (plink->ifindex == nm_device_get_ip_ifindex (self))
+			_stats_update_counters_from_pllink (self, plink);
+	} else {
+		priv->up = FALSE;
+	}
+
+	mtu = plink ? plink->mtu : 0;
+	_set_mtu (self, mtu);
+
+	ifindex = plink ? plink->ifindex : 0;
+	if (priv->ifindex != ifindex) {
+		priv->ifindex = ifindex;
+		_notify (self, PROP_IFINDEX);
+		NM_DEVICE_GET_CLASS (self)->link_changed (self, plink);
+	}
 }
 
 static void
@@ -2943,7 +3213,7 @@ device_init_sriov_num_vfs (NMDevice *self)
 	if (   priv->ifindex > 0
 	    && nm_device_has_capability (self, NM_DEVICE_CAP_SRIOV)) {
 		value = nm_config_data_get_device_config (NM_CONFIG_GET_DATA,
-		                                          "sriov-num-vfs",
+		                                          NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
 		                                          self,
 		                                          NULL);
 		num_vfs = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXINT32, -1);
@@ -3010,7 +3280,10 @@ realize_start_setup (NMDevice *self,
 	NMDeviceCapabilities capabilities = 0;
 	NMConfig *config;
 	guint real_rate;
-	guint32 mtu;
+
+	/* plink is a NMPlatformLink type, however, we require it to come from the platform
+	 * cache (where else would it come from?). */
+	nm_assert (!plink || NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (plink)) == NMP_OBJECT_TYPE_LINK);
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
@@ -3035,20 +3308,14 @@ realize_start_setup (NMDevice *self,
 	priv->mtu_initial = 0;
 	priv->ip6_mtu_initial = 0;
 	priv->ip6_mtu = 0;
-	if (priv->mtu) {
-		priv->mtu = 0;
-		_notify (self, PROP_MTU);
-	}
+	_set_mtu (self, 0);
 
 	_assume_state_set (self, assume_state_guess_assume, assume_state_connection_uuid);
 
 	nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
 
-	if (plink) {
-		g_return_if_fail (link_type_compatible (self, plink->type, NULL, NULL));
-		update_device_from_platform_link (self, plink);
-		_stats_update_counters_from_pllink (self, plink);
-	}
+	if (plink)
+		nm_device_update_from_platform_link (self, plink);
 
 	if (priv->ifindex > 0) {
 		priv->physical_port_id = nm_platform_link_get_physical_port_id (nm_device_get_platform (self), priv->ifindex);
@@ -3059,11 +3326,9 @@ realize_start_setup (NMDevice *self,
 		if (nm_platform_link_is_software (nm_device_get_platform (self), priv->ifindex))
 			capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
 
-		mtu = nm_platform_link_get_mtu (nm_device_get_platform (self), priv->ifindex);
-		if (priv->mtu != mtu) {
-			priv->mtu = mtu;
-			_notify (self, PROP_MTU);
-		}
+		_set_mtu (self,
+		          nm_platform_link_get_mtu (nm_device_get_platform (self),
+		                                    priv->ifindex));
 
 		nm_platform_link_get_driver_info (nm_device_get_platform (self),
 		                                  priv->ifindex,
@@ -3075,7 +3340,8 @@ realize_start_setup (NMDevice *self,
 		if (priv->firmware_version)
 			_notify (self, PROP_FIRMWARE_VERSION);
 
-		if (nm_platform_check_support_user_ipv6ll (nm_device_get_platform (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);
 
 		if (nm_platform_link_supports_sriov (nm_device_get_platform (self), priv->ifindex))
@@ -3147,6 +3413,7 @@ realize_start_setup (NMDevice *self,
 	nm_device_set_unmanaged_flags (self, NM_UNMANAGED_LOOPBACK, priv->ifindex == 1);
 
 	nm_device_set_unmanaged_by_user_udev (self);
+	nm_device_set_unmanaged_by_user_conf (self);
 
 	nm_device_set_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT,
 	                               plink && !plink->initialized);
@@ -3257,7 +3524,6 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 	g_return_val_if_fail (priv->iface != NULL, FALSE);
 	g_return_val_if_fail (priv->real, FALSE);
 
-	g_object_freeze_notify (G_OBJECT (self));
 
 	ifindex = nm_device_get_ifindex (self);
 
@@ -3274,6 +3540,7 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 		}
 	}
 
+	g_object_freeze_notify (G_OBJECT (self));
 	NM_DEVICE_GET_CLASS (self)->unrealize_notify (self);
 
 	_parent_set_ifindex (self, 0, FALSE);
@@ -3286,10 +3553,7 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 	if (nm_clear_g_free (&priv->ip_iface))
 		_notify (self, PROP_IP_IFACE);
 
-	if (priv->mtu != 0) {
-		priv->mtu = 0;
-		_notify (self, PROP_MTU);
-	}
+	_set_mtu (self, 0);
 
 	if (priv->driver_version) {
 		g_clear_pointer (&priv->driver_version, g_free);
@@ -3373,13 +3637,25 @@ gboolean
 nm_device_notify_component_added (NMDevice *self, GObject *component)
 {
 	NMDeviceClass *klass;
+	NMDevicePrivate *priv;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-	g_return_val_if_fail (G_IS_OBJECT (component), FALSE);
 
+	priv = NM_DEVICE_GET_PRIVATE (self);
 	klass = NM_DEVICE_GET_CLASS (self);
+
+	if (priv->state == NM_DEVICE_STATE_DISCONNECTED) {
+		/* A device could have stayed disconnected because it would
+		 * want to register with a network server that now become
+		 * available. */
+		nm_device_recheck_available_connections (self);
+		if (g_hash_table_size (priv->available_connections) > 0)
+			nm_device_emit_recheck_auto_activate (self);
+	}
+
 	if (klass->component_added)
 		return klass->component_added (self, component);
+
 	return FALSE;
 }
 
@@ -3453,7 +3729,8 @@ slave_state_changed (NMDevice *slave,
 		                                    configure,
 		                                    reason);
 		/* Bridge/bond/team interfaces are left up until manually deactivated */
-		if (priv->slaves == NULL && priv->state == NM_DEVICE_STATE_ACTIVATED)
+		if (   c_list_is_empty (&priv->slaves)
+		    && priv->state == NM_DEVICE_STATE_ACTIVATED)
 			_LOGD (LOGD_DEVICE, "last slave removed; remaining activated");
 	}
 }
@@ -3504,9 +3781,12 @@ nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure)
 		info->watch_id = g_signal_connect (slave,
 		                                   NM_DEVICE_STATE_CHANGED,
 		                                   G_CALLBACK (slave_state_changed), self);
-		priv->slaves = g_slist_append (priv->slaves, info);
+		c_list_link_tail (&priv->slaves, &info->lst_slave);
 		slave_priv->master = g_object_ref (self);
 
+		_active_connection_set_state_flags (self,
+		                                    NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES);
+
 		/* no need to emit
 		 *
 		 *   _notify (slave, PROP_MASTER);
@@ -3527,46 +3807,6 @@ nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure)
 }
 
 /**
- * nm_device_master_get_slaves:
- * @self: the master device
- *
- * Returns: any slaves of which @self is the master.  Caller owns returned list.
- */
-static GSList *
-nm_device_master_get_slaves (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	GSList *slaves = NULL, *iter;
-
-	for (iter = priv->slaves; iter; iter = g_slist_next (iter))
-		slaves = g_slist_prepend (slaves, ((SlaveInfo *) iter->data)->slave);
-
-	return slaves;
-}
-
-/**
- * nm_device_master_get_slave_by_ifindex:
- * @self: the master device
- * @ifindex: the slave's interface index
- *
- * Returns: the slave with the given @ifindex of which @self is the master,
- *   or %NULL if no device with @ifindex is a slave of @self.
- */
-NMDevice *
-nm_device_master_get_slave_by_ifindex (NMDevice *self, int ifindex)
-{
-	GSList *iter;
-
-	for (iter = NM_DEVICE_GET_PRIVATE (self)->slaves; iter; iter = g_slist_next (iter)) {
-		SlaveInfo *info = iter->data;
-
-		if (nm_device_get_ip_ifindex (info->slave) == ifindex)
-			return info->slave;
-	}
-	return NULL;
-}
-
-/**
  * nm_device_master_check_slave_physical_port:
  * @self: the master device
  * @slave: a slave device
@@ -3582,14 +3822,14 @@ nm_device_master_check_slave_physical_port (NMDevice *self, NMDevice *slave,
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const char *slave_physical_port_id, *existing_physical_port_id;
 	SlaveInfo *info;
-	GSList *iter;
+	CList *iter;
 
 	slave_physical_port_id = nm_device_get_physical_port_id (slave);
 	if (!slave_physical_port_id)
 		return;
 
-	for (iter = priv->slaves; iter; iter = iter->next) {
-		info = iter->data;
+	c_list_for_each (iter, &priv->slaves) {
+		info = c_list_entry (iter, SlaveInfo, lst_slave);
 		if (info->slave == slave)
 			continue;
 
@@ -3615,6 +3855,7 @@ nm_device_master_release_slaves (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceStateReason reason;
 	gboolean configure = TRUE;
+	CList *iter, *safe;
 
 	/* Don't release the slaves if this connection doesn't belong to NM. */
 	if (nm_device_sys_iface_state_is_external (self))
@@ -3627,8 +3868,8 @@ nm_device_master_release_slaves (NMDevice *self)
 	if (!nm_platform_link_get (nm_device_get_platform (self), priv->ifindex))
 		configure = FALSE;
 
-	while (priv->slaves) {
-		SlaveInfo *info = priv->slaves->data;
+	c_list_for_each_safe (iter, safe, &priv->slaves) {
+		SlaveInfo *info = c_list_entry (iter, SlaveInfo, lst_slave);
 
 		nm_device_master_release_one_slave (self, info->slave, configure, reason);
 	}
@@ -3643,7 +3884,9 @@ nm_device_master_release_slaves (NMDevice *self)
 gboolean
 nm_device_is_master (NMDevice *self)
 {
-	return NM_DEVICE_GET_PRIVATE (self)->is_master;
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	return NM_DEVICE_GET_CLASS (self)->is_master;
 }
 
 /**
@@ -3669,6 +3912,113 @@ nm_device_get_master (NMDevice *self)
 	return NULL;
 }
 
+static gboolean
+get_ip_config_may_fail (NMDevice *self, int addr_family)
+{
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip = NULL;
+
+	connection = nm_device_get_applied_connection (self);
+
+	/* Fail the connection if the failed IP method is required to complete */
+	switch (addr_family) {
+	case AF_INET:
+		s_ip = nm_connection_get_setting_ip4_config (connection);
+		break;
+	case AF_INET6:
+		s_ip = nm_connection_get_setting_ip6_config (connection);
+		break;
+	default:
+		nm_assert_not_reached ();
+	}
+
+	return !s_ip || nm_setting_ip_config_get_may_fail (s_ip);
+}
+
+/*
+ * check_ip_state
+ *
+ * Transition the device from IP_CONFIG to the next state according to the
+ * outcome of IPv4 and IPv6 configuration. @may_fail indicates that we are
+ * called just after the initial configuration and thus IPv4/IPv6 are allowed to
+ * fail if the ipvx.may-fail properties say so, because the IP methods couldn't
+ * even be started.
+ */
+static void
+check_ip_state (NMDevice *self, gboolean may_fail)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean ip4_disabled = FALSE, ip6_ignore = FALSE;
+	NMSettingIPConfig *s_ip4, *s_ip6;
+	NMDeviceState state;
+
+	if (nm_device_get_state (self) != NM_DEVICE_STATE_IP_CONFIG)
+		return;
+
+	/* 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))
+	    && !priv->is_enslaved)
+		return;
+
+	s_ip4 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP4_CONFIG);
+	if (s_ip4 && nm_streq0 (nm_setting_ip_config_get_method (s_ip4),
+	                        NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+		ip4_disabled = TRUE;
+
+	s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+	if (s_ip6 && nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
+	                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
+		ip6_ignore = TRUE;
+
+	if (   priv->ip4_state == IP_DONE
+	    && priv->ip6_state == IP_DONE) {
+		/* Both method completed (or disabled), proceed with activation */
+		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
+		return;
+	}
+
+	if (   (priv->ip4_state == IP_FAIL || (ip4_disabled && priv->ip4_state == IP_DONE))
+	    && (priv->ip6_state == IP_FAIL || (ip6_ignore && priv->ip6_state == IP_DONE))) {
+		/* Either both methods failed, or only one failed and the other is
+		 * disabled */
+		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
+			/* We have assumed configuration, but couldn't redo it. No problem,
+			 * move to check state. */
+			_set_ip_state (self, AF_INET, IP_DONE);
+			_set_ip_state (self, AF_INET6, IP_DONE);
+			state = NM_DEVICE_STATE_IP_CHECK;
+		} else if (   may_fail
+		           && get_ip_config_may_fail (self, AF_INET)
+		           && get_ip_config_may_fail (self, AF_INET6)) {
+			/* Couldn't start either IPv6 and IPv4 autoconfiguration,
+			 * but both are allowed to fail. */
+			state = NM_DEVICE_STATE_SECONDARIES;
+		} else {
+			/* Autoconfiguration attempted without success. */
+			state = NM_DEVICE_STATE_FAILED;
+		}
+
+		nm_device_state_changed (self,
+		                         state,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		return;
+	}
+
+	/* If a method is still pending but required, wait */
+	if (priv->ip4_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET))
+		return;
+	if (priv->ip6_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET6))
+		return;
+
+	/* If at least a method has completed, proceed with activation */
+	if (   (priv->ip4_state == IP_DONE && !ip4_disabled)
+	    || (priv->ip6_state == IP_DONE && !ip6_ignore)) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
+		return;
+	}
+}
+
 /**
  * nm_device_slave_notify_enslave:
  * @self: the slave device
@@ -3705,10 +4055,8 @@ nm_device_slave_notify_enslave (NMDevice *self, gboolean success)
 	}
 
 	if (activating) {
-		_set_ip_state (self, AF_INET, IP_DONE);
-		_set_ip_state (self, AF_INET6, IP_DONE);
 		if (success)
-			nm_device_queue_state (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE);
+			check_ip_state (self, FALSE);
 		else
 			nm_device_queue_state (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_UNKNOWN);
 	} else
@@ -3767,19 +4115,6 @@ nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 }
 
 /**
- * nm_device_get_enslaved:
- * @self: the #NMDevice
- *
- * Returns: %TRUE if the device is enslaved to a master device (eg bridge or
- * bond or team), %FALSE if not
- */
-gboolean
-nm_device_get_enslaved (NMDevice *self)
-{
-	return NM_DEVICE_GET_PRIVATE (self)->is_enslaved;
-}
-
-/**
  * nm_device_removed:
  * @self: the #NMDevice
  * @unconfigure_ip_config: whether to clear the IP config objects
@@ -3805,17 +4140,8 @@ nm_device_removed (NMDevice *self, gboolean unconfigure_ip_config)
 	if (!unconfigure_ip_config)
 		return;
 
-	/* Clean up IP configs; this does not actually deconfigure the
-	 * interface, it just clears the configuration to which policy
-	 * is reacting via NM_DEVICE_IP4_CONFIG_CHANGED/NM_DEVICE_IP6_CONFIG_CHANGED
-	 * signal. As NMPolicy registered the NMIPxConfig instances in NMDnsManager,
-	 * these would be leaked otherwise. */
-	_update_default_route (self, AF_INET,  priv->default_route.v4_has, TRUE);
-	_update_default_route (self, AF_INET6, priv->default_route.v6_has, TRUE);
-	_update_default_route (self, AF_INET,  FALSE, TRUE);
-	_update_default_route (self, AF_INET6, FALSE, TRUE);
-	nm_device_set_ip4_config (self, NULL, 0, FALSE, FALSE);
-	nm_device_set_ip6_config (self, NULL, FALSE, FALSE);
+	nm_device_set_ip4_config (self, NULL, FALSE, NULL);
+	nm_device_set_ip6_config (self, NULL, FALSE);
 }
 
 static gboolean
@@ -3823,12 +4149,17 @@ is_available (NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->carrier || priv->ignore_carrier)
+	if (   priv->carrier
+	    || priv->ignore_carrier)
 		return TRUE;
 
 	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER))
 		return TRUE;
 
+	/* master types are always available even without carrier. */
+	if (nm_device_is_master (self))
+		return TRUE;
+
 	return FALSE;
 }
 
@@ -3863,6 +4194,13 @@ nm_device_is_available (NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
 }
 
 gboolean
+nm_device_ignore_carrier_by_default (NMDevice *self)
+{
+	/* master types ignore-carrier by default. */
+	return nm_device_is_master (self);
+}
+
+gboolean
 nm_device_get_enabled (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
@@ -4057,7 +4395,8 @@ device_has_config (NMDevice *self)
 		return TRUE;
 
 	/* Master-slave relationship is also a configuration */
-	if (priv->slaves || nm_platform_link_get_master (nm_device_get_platform (self), priv->ifindex) > 0)
+	if (   !c_list_is_empty (&priv->slaves)
+	    || nm_platform_link_get_master (nm_device_get_platform (self), priv->ifindex) > 0)
 		return TRUE;
 
 	return FALSE;
@@ -4179,6 +4518,8 @@ nm_device_generate_connection (NMDevice *self,
 		s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
 		nm_connection_add_setting (connection, s_ip6);
 
+		nm_connection_add_setting (connection, nm_setting_proxy_new ());
+
 		pllink = nm_platform_link_get (nm_device_get_platform (self), priv->ifindex);
 		if (pllink && pllink->inet6_token.id) {
 			g_object_set (s_ip6,
@@ -4206,7 +4547,7 @@ nm_device_generate_connection (NMDevice *self,
 	if (   g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
 	    && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0
 	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
-	    && !priv->slaves) {
+	    && c_list_is_empty (&priv->slaves)) {
 		NM_SET_OUT (out_maybe_later, TRUE);
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 		                     "ignoring generated connection (no IP and not in master-slave relationship)");
@@ -4219,7 +4560,7 @@ nm_device_generate_connection (NMDevice *self,
 	if (   g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
 	    && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0
 	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
-	    && !priv->slaves
+	    && c_list_is_empty (&priv->slaves)
 	    && !nm_config_data_get_assume_ipv6ll_only (NM_CONFIG_GET_DATA, self)) {
 		_LOGD (LOGD_DEVICE, "ignoring generated connection (IPv6LL-only and not in master-slave relationship)");
 		NM_SET_OUT (out_maybe_later, TRUE);
@@ -4261,6 +4602,65 @@ nm_device_complete_connection (NMDevice *self,
 	return success;
 }
 
+gboolean
+nm_device_match_parent (NMDevice *self, const char *parent)
+{
+	NMDevice *parent_device;
+
+	g_return_val_if_fail (parent, FALSE);
+
+	parent_device = nm_device_parent_get_device (self);
+	if (!parent_device)
+		return FALSE;
+
+	if (nm_utils_is_uuid (parent)) {
+		NMConnection *connection;
+
+		/* If the parent is a UUID, the connection matches when there is
+		 * no connection active on the device or when a connection with
+		 * that UUID is active.
+		 */
+		connection = nm_device_get_applied_connection (self);
+		if (!connection)
+			return TRUE;
+
+		if (!nm_streq0 (parent, nm_connection_get_uuid (connection)))
+			return FALSE;
+	} else {
+		/* Interface name */
+		if (!nm_streq0 (parent, nm_device_get_ip_iface (parent_device)))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+nm_device_match_hwaddr (NMDevice *device,
+                        NMConnection *connection,
+                        gboolean fail_if_no_hwaddr)
+{
+	NMSettingWired *s_wired;
+	NMDevice *parent_device;
+	const char *setting_mac;
+	const char *parent_mac;
+
+	s_wired = nm_connection_get_setting_wired (connection);
+	if (!s_wired)
+		return !fail_if_no_hwaddr;
+
+	setting_mac = nm_setting_wired_get_mac_address (s_wired);
+	if (!setting_mac)
+		return !fail_if_no_hwaddr;
+
+	parent_device = nm_device_parent_get_device (device);
+	if (!parent_device)
+		return !fail_if_no_hwaddr;
+
+	parent_mac = nm_device_get_permanent_hw_address (parent_device);
+	return parent_mac && nm_utils_hwaddr_matches (setting_mac, -1, parent_mac, -1);
+}
+
 static gboolean
 check_connection_compatible (NMDevice *self, NMConnection *connection)
 {
@@ -4317,7 +4717,7 @@ nm_device_check_slave_connection_compatible (NMDevice *self, NMConnection *slave
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (!priv->is_master)
+	if (!nm_device_is_master (self))
 		return FALSE;
 
 	/* All masters should have connection type set */
@@ -4506,44 +4906,49 @@ activation_source_handle_cb6 (gpointer user_data)
 
 static ActivationHandleData *
 activation_source_get_by_family (NMDevice *self,
-                                 int family,
+                                 int addr_family,
                                  GSourceFunc *out_idle_func)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (family == AF_INET6) {
+	switch (addr_family) {
+	case AF_INET6:
 		NM_SET_OUT (out_idle_func, activation_source_handle_cb6);
 		return &priv->act_handle6;
-	} else {
+	case AF_INET:
 		NM_SET_OUT (out_idle_func, activation_source_handle_cb4);
-		g_return_val_if_fail (family == AF_INET, &priv->act_handle4);
 		return &priv->act_handle4;
 	}
+	g_return_val_if_reached (NULL);
 }
 
 static void
-activation_source_clear (NMDevice *self, int family)
+activation_source_clear (NMDevice *self,
+                         int addr_family)
 {
 	ActivationHandleData *act_data;
 
-	act_data = activation_source_get_by_family (self, family, NULL);
+	act_data = activation_source_get_by_family (self, addr_family, NULL);
 
 	if (act_data->id) {
-		_LOGD (LOGD_DEVICE, "activation-stage: clear %s,%d (id %u)",
-		       _activation_func_to_string (act_data->func), family, act_data->id);
+		_LOGD (LOGD_DEVICE, "activation-stage: clear %s,v%c (id %u)",
+		       _activation_func_to_string (act_data->func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       act_data->id);
 		nm_clear_g_source (&act_data->id);
 		act_data->func = NULL;
 	}
 }
 
 static void
-activation_source_handle_cb (NMDevice *self, int family)
+activation_source_handle_cb (NMDevice *self,
+                             int addr_family)
 {
 	ActivationHandleData *act_data, a;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	act_data = activation_source_get_by_family (self, family, NULL);
+	act_data = activation_source_get_by_family (self, addr_family, NULL);
 
 	g_return_if_fail (act_data->id);
 	g_return_if_fail (act_data->func);
@@ -4553,23 +4958,27 @@ activation_source_handle_cb (NMDevice *self, int family)
 	act_data->func = NULL;
 	act_data->id = 0;
 
-	_LOGD (LOGD_DEVICE, "activation-stage: invoke %s,%d (id %u)",
-	       _activation_func_to_string (a.func), family, a.id);
+	_LOGD (LOGD_DEVICE, "activation-stage: invoke %s,v%c (id %u)",
+	       _activation_func_to_string (a.func),
+	       nm_utils_addr_family_to_char (addr_family),
+	       a.id);
 
 	a.func (self);
 
-	_LOGD (LOGD_DEVICE, "activation-stage: complete %s,%d (id %u)",
-	       _activation_func_to_string (a.func), family, a.id);
+	_LOGD (LOGD_DEVICE, "activation-stage: complete %s,v%c (id %u)",
+	       _activation_func_to_string (a.func),
+	       nm_utils_addr_family_to_char (addr_family),
+	       a.id);
 }
 
 static void
-activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int family)
+activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int addr_family)
 {
 	ActivationHandleData *act_data;
 	GSourceFunc source_func;
 	guint new_id = 0;
 
-	act_data = activation_source_get_by_family (self, family, &source_func);
+	act_data = activation_source_get_by_family (self, addr_family, &source_func);
 
 	if (act_data->id && act_data->func == func) {
 		/* Don't bother rescheduling the same function that's about to
@@ -4577,22 +4986,28 @@ activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int famil
 		 * streams of associate events before NM has had a chance to process
 		 * the first one.
 		 */
-		_LOGD (LOGD_DEVICE, "activation-stage: already scheduled %s,%d (id %u)",
-		       _activation_func_to_string (func), family, act_data->id);
+		_LOGD (LOGD_DEVICE, "activation-stage: already scheduled %s,v%c (id %u)",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       act_data->id);
 		return;
 	}
 
 	new_id = g_idle_add (source_func, self);
 
 	if (act_data->id) {
-		_LOGW (LOGD_DEVICE, "activation-stage: schedule %s,%d which replaces %s,%d (id %u -> %u)",
-		       _activation_func_to_string (func), family,
-		       _activation_func_to_string (act_data->func), family,
+		_LOGW (LOGD_DEVICE, "activation-stage: schedule %s,v%c which replaces %s,v%c (id %u -> %u)",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       _activation_func_to_string (act_data->func),
+		       nm_utils_addr_family_to_char (addr_family),
 		       act_data->id, new_id);
 		nm_clear_g_source (&act_data->id);
 	} else {
-		_LOGD (LOGD_DEVICE, "activation-stage: schedule %s,%d (id %u)",
-		       _activation_func_to_string (func), family, new_id);
+		_LOGD (LOGD_DEVICE, "activation-stage: schedule %s,v%c (id %u)",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       new_id);
 	}
 
 	act_data->func = func;
@@ -4600,42 +5015,18 @@ activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int famil
 }
 
 static gboolean
-activation_source_is_scheduled (NMDevice *self, ActivationHandleFunc func, int family)
+activation_source_is_scheduled (NMDevice *self,
+                                ActivationHandleFunc func,
+                                int addr_family)
 {
 	ActivationHandleData *act_data;
 
-	act_data = activation_source_get_by_family (self, family, NULL);
+	act_data = activation_source_get_by_family (self, addr_family, NULL);
 	return act_data->func == func;
 }
 
 /*****************************************************************************/
 
-static gboolean
-get_ip_config_may_fail (NMDevice *self, int family)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip = NULL;
-
-	g_return_val_if_fail (self != NULL, TRUE);
-
-	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
-
-	/* Fail the connection if the failed IP method is required to complete */
-	switch (family) {
-	case AF_INET:
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-		break;
-	case AF_INET6:
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-		break;
-	default:
-		g_assert_not_reached ();
-	}
-
-	return !s_ip || nm_setting_ip_config_get_may_fail (s_ip);
-}
-
 static void
 master_ready (NMDevice *self,
               NMActiveConnection *active)
@@ -4836,7 +5227,7 @@ activate_stage2_device_config (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret;
 	gboolean no_firmware = FALSE;
-	GSList *iter;
+	CList *iter;
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
@@ -4863,8 +5254,8 @@ activate_stage2_device_config (NMDevice *self)
 	}
 
 	/* If we have slaves that aren't yet enslaved, do that now */
-	for (iter = priv->slaves; iter; iter = g_slist_next (iter)) {
-		SlaveInfo *info = iter->data;
+	c_list_for_each (iter, &priv->slaves) {
+		SlaveInfo *info = c_list_entry (iter, SlaveInfo, lst_slave);
 		NMDeviceState slave_state = nm_device_get_state (info->slave);
 
 		if (slave_state == NM_DEVICE_STATE_IP_CONFIG)
@@ -4930,97 +5321,21 @@ nm_device_activate_schedule_stage2_device_config (NMDevice *self)
 	activation_source_schedule (self, activate_stage2_device_config, AF_INET);
 }
 
-/*
- * check_ip_state
- *
- * Transition the device from IP_CONFIG to the next state according to the
- * outcome of IPv4 and IPv6 configuration. @may_fail indicates that we are
- * called just after the initial configuration and thus IPv4/IPv6 are allowed to
- * fail if the ipvx.may-fail properties say so, because the IP methods couldn't
- * even be started.
- */
-static void
-check_ip_state (NMDevice *self, gboolean may_fail)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean ip4_disabled = FALSE, ip6_ignore = FALSE;
-	NMSettingIPConfig *s_ip4, *s_ip6;
-	NMDeviceState state;
-
-	if (nm_device_get_state (self) != NM_DEVICE_STATE_IP_CONFIG)
-		return;
-
-	s_ip4 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP4_CONFIG);
-	if (s_ip4 && nm_streq0 (nm_setting_ip_config_get_method (s_ip4),
-	                        NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-		ip4_disabled = TRUE;
-
-	s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
-	if (s_ip6 && nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
-	                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
-		ip6_ignore = TRUE;
-
-	if (   priv->ip4_state == IP_DONE
-	    && priv->ip6_state == IP_DONE) {
-		/* Both method completed (or disabled), proceed with activation */
-		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
-		return;
-	}
-
-	if (   (priv->ip4_state == IP_FAIL || (ip4_disabled && priv->ip4_state == IP_DONE))
-	    && (priv->ip6_state == IP_FAIL || (ip6_ignore && priv->ip6_state == IP_DONE))) {
-		/* Either both methods failed, or only one failed and the other is
-		 * disabled */
-		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
-			/* We have assumed configuration, but couldn't redo it. No problem,
-			 * move to check state. */
-			_set_ip_state (self, AF_INET, IP_DONE);
-			_set_ip_state (self, AF_INET6, IP_DONE);
-			state = NM_DEVICE_STATE_IP_CHECK;
-		} else if (   may_fail
-		           && get_ip_config_may_fail (self, AF_INET)
-		           && get_ip_config_may_fail (self, AF_INET6)) {
-			/* Couldn't start either IPv6 and IPv4 autoconfiguration,
-			 * but both are allowed to fail. */
-			state = NM_DEVICE_STATE_SECONDARIES;
-		} else {
-			/* Autoconfiguration attempted without success. */
-			state = NM_DEVICE_STATE_FAILED;
-		}
-
-		nm_device_state_changed (self,
-		                         state,
-		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-		return;
-	}
-
-	/* If a method is still pending but required, wait */
-	if (priv->ip4_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET))
-		return;
-	if (priv->ip6_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET6))
-		return;
-
-	/* If at least a method has completed, proceed with activation */
-	if (   (priv->ip4_state == IP_DONE && !ip4_disabled)
-	    || (priv->ip6_state == IP_DONE && !ip6_ignore)) {
-		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
-		return;
-	}
-}
-
 void
-nm_device_ip_method_failed (NMDevice *self, int family, NMDeviceStateReason reason)
+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 (family == AF_INET || family == AF_INET6);
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_set_ip_state (self, family, IP_FAIL);
+	_set_ip_state (self, addr_family, IP_FAIL);
 
-	if (get_ip_config_may_fail (self, family))
+	if (get_ip_config_may_fail (self, addr_family))
 		check_ip_state (self, FALSE);
 	else
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
@@ -5077,7 +5392,7 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 	NMIP4Config *empty;
 
 	if (success) {
-		empty = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+		empty = _ip4_config_new (self);
 		nm_device_activate_schedule_ip4_config_result (self, empty);
 		g_object_unref (empty);
 	} else {
@@ -5091,17 +5406,17 @@ arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *da
 {
 	NMDevice *self;
 	NMDevicePrivate *priv;
+	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP4Address *address;
 	gboolean result, success = TRUE;
-	int i, j;
+	int i;
 
 	g_assert (data);
 	self = data->device;
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	for (i = 0; data->configs && data->configs[i]; i++) {
-		for (j = 0; j < nm_ip4_config_get_num_addresses (data->configs[i]); j++) {
-			address = nm_ip4_config_get_address (data->configs[i], j);
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) {
 			result = nm_arping_manager_check_address (arping_manager, address->address);
 			success &= result;
 
@@ -5135,13 +5450,14 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMArpingManager *arping_manager;
 	const NMPlatformIP4Address *address;
+	NMDedupMultiIter ipconf_iter;
 	ArpingData *data;
 	guint timeout;
 	gboolean ret, addr_found;
 	const guint8 *hw_addr;
 	size_t hw_addr_len = 0;
 	GError *error = NULL;
-	guint i, j;
+	guint i;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (configs);
@@ -5187,10 +5503,8 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
 	data->device = self;
 
 	for (i = 0; configs[i]; i++) {
-		for (j = 0; j < nm_ip4_config_get_num_addresses (configs[i]); j++) {
-			address = nm_ip4_config_get_address (configs[i], j);
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address)
 			nm_arping_manager_add_address (arping_manager, address->address);
-		}
 	}
 
 	g_signal_connect_data (arping_manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
@@ -5234,7 +5548,7 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 	NMPlatformIP4Address address;
 	NMPlatformIP4Route route;
 
-	config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+	config = _ip4_config_new (self);
 	g_assert (config);
 
 	memset (&address, 0, sizeof (address));
@@ -5247,8 +5561,9 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 	route.network = htonl (0xE0000000L);
 	route.plen = 4;
 	route.rt_source = NM_IP_CONFIG_SOURCE_IP4LL;
-	route.metric = nm_device_get_ip4_route_metric (self);
-	nm_ip4_config_add_route (config, &route);
+	route.table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (self, AF_INET, TRUE));
+	route.metric = nm_device_get_route_metric (self, AF_INET);
+	nm_ip4_config_add_route (config, &route, NULL);
 
 	return config;
 }
@@ -5304,7 +5619,9 @@ 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) {
-			if (!ip4_config_merge_and_apply (self, config, TRUE)) {
+			g_clear_object (&priv->dev_ip4_config);
+			priv->dev_ip4_config = g_object_ref (config);
+			if (!ip4_config_merge_and_apply (self, 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);
 			}
@@ -5399,57 +5716,10 @@ ipv4ll_start (NMDevice *self)
 
 /*****************************************************************************/
 
-static gboolean
-_device_get_default_route_from_platform (NMDevice *self, int addr_family, NMPlatformIPRoute *out_route)
-{
-	gboolean success = FALSE;
-	int ifindex = nm_device_get_ip_ifindex (self);
-	GArray *routes;
-
-	if (addr_family == AF_INET)
-		routes = nm_platform_ip4_route_get_all (nm_device_get_platform (self), ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
-	else
-		routes = nm_platform_ip6_route_get_all (nm_device_get_platform (self), ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
-
-	if (routes) {
-		guint route_metric = G_MAXUINT32, m;
-		const NMPlatformIPRoute *route = NULL, *r;
-		guint i;
-
-		/* if there are several default routes, find the one with the best metric */
-		for (i = 0; i < routes->len; i++) {
-			if (addr_family == AF_INET) {
-				r = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, i);
-				m = r->metric;
-			} else {
-				r = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, i);
-				m = nm_utils_ip6_route_metric_normalize (r->metric);
-			}
-			if (!route || m < route_metric) {
-				route = r;
-				route_metric = m;
-			}
-		}
-
-		if (route) {
-			if (addr_family == AF_INET)
-				*((NMPlatformIP4Route *) out_route) = *((NMPlatformIP4Route *) route);
-			else
-				*((NMPlatformIP6Route *) out_route) = *((NMPlatformIP6Route *) route);
-			success = TRUE;
-		}
-		g_array_free (routes, TRUE);
-	}
-	return success;
-}
-
-/*****************************************************************************/
-
 static void
 ensure_con_ip4_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	NMConnection *connection;
 
 	if (priv->con_ip4_config)
@@ -5459,10 +5729,11 @@ ensure_con_ip4_config (NMDevice *self)
 	if (!connection)
 		return;
 
-	priv->con_ip4_config = nm_ip4_config_new (ip_ifindex);
+	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_ip4_route_metric (self));
+	                             nm_device_get_route_table (self, AF_INET, TRUE),
+	                             nm_device_get_route_metric (self, AF_INET));
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
 		/* For assumed connections ignore all addresses and routes. */
@@ -5475,7 +5746,6 @@ static void
 ensure_con_ip6_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	NMConnection *connection;
 
 	if (priv->con_ip6_config)
@@ -5485,10 +5755,11 @@ ensure_con_ip6_config (NMDevice *self)
 	if (!connection)
 		return;
 
-	priv->con_ip6_config = nm_ip6_config_new (ip_ifindex);
+	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_ip6_route_metric (self));
+	                             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. */
@@ -5527,38 +5798,19 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	}
 }
 
-static void
-_ip4_config_merge_default (gpointer value, gpointer user_data)
-{
-	NMIP4Config *src = (NMIP4Config *) value;
-	NMIP4Config *dst = (NMIP4Config *) user_data;
-
-	nm_ip4_config_merge (dst, src, NM_IP_CONFIG_MERGE_DEFAULT);
-}
-
 static gboolean
 ip4_config_merge_and_apply (NMDevice *self,
-                            NMIP4Config *config,
                             gboolean commit)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	gboolean success;
 	NMIP4Config *composite;
-	gboolean has_direct_route;
-	const guint32 default_route_metric = nm_device_get_ip4_route_metric (self);
-	guint32 gateway;
-	gboolean connection_has_default_route, connection_is_never_default;
-	gboolean routes_full_sync;
 	gboolean ignore_auto_routes = FALSE;
 	gboolean ignore_auto_dns = FALSE;
-	gboolean auto_method = FALSE;
-
-	/* Merge all the configs into the composite config */
-	if (config) {
-		g_clear_object (&priv->dev_ip4_config);
-		priv->dev_ip4_config = g_object_ref (config);
-	}
+	gboolean ignore_default_routes = FALSE;
+	GSList *iter;
+	gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	connection = nm_device_get_applied_connection (self);
@@ -5569,35 +5821,38 @@ ip4_config_merge_and_apply (NMDevice *self,
 			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 (nm_streq0 (nm_setting_ip_config_get_method (s_ip4),
-			               NM_SETTING_IP4_CONFIG_METHOD_AUTO))
-				auto_method = TRUE;
+			/* 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);
 		}
 	}
 
-	composite = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+	composite = _ip4_config_new (self);
 	init_ip4_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_ip4_config_id) {
-			g_clear_object (&priv->ext_ip4_config);
-			priv->ext_ip4_config = nm_ip4_config_capture (nm_device_get_platform (self),
-			                                              nm_device_get_ip_ifindex (self),
-			                                              FALSE);
-		}
 	}
 
+	if (commit)
+		priv->default_route_metric_penalty_ip4_has = default_route_metric_penalty_detect (self);
+
 	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_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 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));
 	}
 
-	g_slist_foreach (priv->vpn4_configs, _ip4_config_merge_default, composite);
+	for (iter = priv->vpn4_configs; iter; iter = iter->next)
+		nm_ip4_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
 
 	if (priv->ext_ip4_config)
-		nm_ip4_config_merge (composite, priv->ext_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT);
+		nm_ip4_config_merge (composite, priv->ext_ip4_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.
@@ -5605,107 +5860,23 @@ ip4_config_merge_and_apply (NMDevice *self,
 	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_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 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));
 	}
 
 	/* 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);
-
-	/* Add the default route.
-	 *
-	 * We keep track of the default route of a device in a private field.
-	 * NMDevice needs to know the default route at this point, because the gateway
-	 * might require a direct route (see below).
-	 *
-	 * But also, we don't want to add the default route to priv->ip4_config,
-	 * because the default route from the setting might not be the same that
-	 * NMDefaultRouteManager eventually configures (because the it might
-	 * tweak the effective metric).
-	 */
-
-	/* unless we come to a different conclusion below, we have no default route and
-	 * the route is assumed. */
-	priv->default_route.v4_has = FALSE;
-	priv->default_route.v4_is_assumed = TRUE;
-
-	if (!commit) {
-		/* during a non-commit event, we always pickup whatever is configured. */
-		goto END_ADD_DEFAULT_ROUTE;
-	}
-
-	/* a generated-assumed connection detects the default route from the platform,
-	 * but if the IP method is automatic we need to update the default route to
-	 * maintain connectivity.
-	 */
-	if (nm_device_sys_iface_state_is_external (self) && !auto_method)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	/* At this point, we treat assumed and non-assumed connections alike.
-	 * For assumed connections we do that because we still manage RA and DHCP
-	 * leases for them, so we must extend/update the default route on commits.
-	 */
-
-	connection_has_default_route
-	    = nm_default_route_manager_ip4_connection_has_default_route (nm_netns_get_default_route_manager (priv->netns),
-	                                                                 connection, &connection_is_never_default);
-
-	if (   !priv->v4_commit_first_time
-	    && connection_is_never_default) {
-		/* If the connection is explicitly configured as never-default, we enforce the (absence of the)
-		 * default-route only once. That allows the user to configure a connection as never-default,
-		 * but he can add default routes externally (via a dispatcher script) and NM will not interfere. */
-		goto END_ADD_DEFAULT_ROUTE;
-	}
-
-	/* we are about to commit (for a non-assumed connection). Enforce whatever we have
-	 * configured. */
-	priv->default_route.v4_is_assumed = FALSE;
-
-	if (!connection_has_default_route)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	if (!nm_ip4_config_get_num_addresses (composite)) {
-		/* without addresses we can have no default route. */
-		goto END_ADD_DEFAULT_ROUTE;
-	}
-
-	gateway = nm_ip4_config_get_gateway (composite);
-	if (   !nm_ip4_config_has_gateway (composite)
-	    && nm_device_get_device_type (self) != NM_DEVICE_TYPE_MODEM)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	has_direct_route = (   gateway == 0
-	                    || nm_ip4_config_destination_is_direct (composite, gateway, 32)
-	                    || nm_ip4_config_get_direct_route_for_host (composite, gateway));
-
-	priv->default_route.v4_has = TRUE;
-	memset (&priv->default_route.v4, 0, sizeof (priv->default_route.v4));
-	priv->default_route.v4.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	priv->default_route.v4.gateway = gateway;
-	priv->default_route.v4.metric = route_metric_with_penalty (self, default_route_metric);
-	priv->default_route.v4.mss = nm_ip4_config_get_mss (composite);
-
-	if (!has_direct_route) {
-		NMPlatformIP4Route r = priv->default_route.v4;
-
-		/* add a direct route to the gateway */
-		r.network = gateway;
-		r.plen = 32;
-		r.gateway = 0;
-		nm_ip4_config_add_route (composite, &r);
+	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));
 	}
 
-END_ADD_DEFAULT_ROUTE:
-
-	if (priv->default_route.v4_is_assumed) {
-		/* If above does not explicitly assign a default route, we always pick up the
-		 * default route based on what is currently configured.
-		 * That means that even managed connections with never-default, can
-		 * get a default route (if configured externally).
-		 */
-		priv->default_route.v4_has = _device_get_default_route_from_platform (self, AF_INET, (NMPlatformIPRoute *) &priv->default_route.v4);
+	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 (commit) {
@@ -5713,11 +5884,7 @@ END_ADD_DEFAULT_ROUTE:
 			NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, composite);
 	}
 
-	routes_full_sync =    commit
-	                   && priv->v4_commit_first_time
-	                   && !nm_device_sys_iface_state_is_external_or_assume (self);
-
-	success = nm_device_set_ip4_config (self, composite, default_route_metric, commit, routes_full_sync);
+	success = nm_device_set_ip4_config (self, composite, commit, ip4_dev_route_blacklist);
 	g_object_unref (composite);
 
 	if (commit)
@@ -5728,9 +5895,14 @@ END_ADD_DEFAULT_ROUTE:
 static gboolean
 dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 {
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
 	g_return_val_if_fail (config, FALSE);
 
-	if (!ip4_config_merge_and_apply (self, config, TRUE)) {
+	g_clear_object (&priv->dev_ip4_config);
+	priv->dev_ip4_config = g_object_ref (config);
+
+	if (!ip4_config_merge_and_apply (self, TRUE)) {
 		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
 		return FALSE;
 	}
@@ -5750,15 +5922,13 @@ dhcp4_restart_cb (gpointer user_data)
 {
 	NMDevice *self = user_data;
 	NMDevicePrivate *priv;
-	NMConnection *connection;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	priv->dhcp4.restart_id = 0;
-	connection = nm_device_get_applied_connection (self);
 
-	if (dhcp4_start (self, connection) == NM_ACT_STAGE_RETURN_FAILURE)
+	if (dhcp4_start (self) == NM_ACT_STAGE_RETURN_FAILURE)
 		dhcp_schedule_restart (self, AF_INET, NULL);
 
 	return FALSE;
@@ -5825,7 +5995,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 	NMIP4Config *manual, **configs;
 	NMConnection *connection;
 
-	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE);
+	g_return_if_fail (nm_dhcp_client_get_addr_family (client) == AF_INET);
 	g_return_if_fail (!ip4_config || NM_IS_IP4_CONFIG (ip4_config));
 
 	_LOGD (LOGD_DHCP4, "new DHCPv4 client state %d", state);
@@ -5850,10 +6020,11 @@ dhcp4_state_changed (NMDhcpClient *client,
 			connection = nm_device_get_applied_connection (self);
 			g_assert (connection);
 
-			manual = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+			manual = _ip4_config_new (self);
 			nm_ip4_config_merge_setting (manual,
 			                             nm_connection_get_setting_ip4_config (connection),
-			                             nm_device_get_ip4_route_metric (self));
+			                             nm_device_get_route_table (self, AF_INET, TRUE),
+			                             nm_device_get_route_metric (self, AF_INET));
 
 			configs = g_new0 (NMIP4Config *, 3);
 			configs[0] = manual;
@@ -5885,36 +6056,60 @@ dhcp4_state_changed (NMDhcpClient *client,
 }
 
 static int
-dhcp4_get_timeout (NMDevice *self, NMSettingIP4Config *s_ip4)
+get_dhcp_timeout (NMDevice *self, int addr_family)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gs_free char *value = NULL;
-	int timeout;
+	NMDeviceClass *klass;
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	guint32 timeout;
 
-	timeout = nm_setting_ip_config_get_dhcp_timeout (NM_SETTING_IP_CONFIG (s_ip4));
-	if (timeout)
-		return timeout;
+	nm_assert (NM_IS_DEVICE (self));
+	nm_assert_addr_family (addr_family);
 
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "ipv4.dhcp-timeout",
-	                                               self);
-	timeout = _nm_utils_ascii_str_to_int64 (value, 10,
-	                                        0, G_MAXINT32, 0);
+	connection = nm_device_get_applied_connection (self);
+
+	if (addr_family == AF_INET)
+		s_ip = nm_connection_get_setting_ip4_config (connection);
+	else
+		s_ip = nm_connection_get_setting_ip6_config (connection);
+
+	timeout = nm_setting_ip_config_get_dhcp_timeout (s_ip);
 	if (timeout)
 		return timeout;
 
-	return priv->dhcp_timeout;
+	{
+		gs_free char *value = NULL;
+
+		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                               addr_family == AF_INET
+		                                                 ? "ipv4.dhcp-timeout"
+		                                                 : "ipv6.dhcp-timeout",
+		                                               self);
+		timeout = _nm_utils_ascii_str_to_int64 (value, 10,
+		                                        0, G_MAXINT32, 0);
+		if (timeout)
+			return timeout;
+	}
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (klass->get_dhcp_timeout)
+		timeout = klass->get_dhcp_timeout (self, addr_family);
+
+	return timeout ?: NM_DHCP_TIMEOUT_DEFAULT;
 }
 
 static NMActStageReturn
-dhcp4_start (NMDevice *self,
-             NMConnection *connection)
+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;
+	NMConnection *connection;
+
+	connection = nm_device_get_applied_connection (self);
+	g_return_val_if_fail (connection, FALSE);
 
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
@@ -5931,16 +6126,18 @@ dhcp4_start (NMDevice *self,
 	/* 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,
 	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_ip4_route_metric (self),
+	                                                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)),
-	                                                dhcp4_get_timeout (self, NM_SETTING_IP4_CONFIG (s_ip4)),
+	                                                get_dhcp_timeout (self, AF_INET),
 	                                                priv->dhcp_anycast_address,
 	                                                NULL);
 
@@ -5968,7 +6165,6 @@ gboolean
 nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
 
 	g_return_val_if_fail (priv->dhcp4.client != NULL, FALSE);
 
@@ -5977,11 +6173,8 @@ nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 	/* Terminate old DHCP instance and release the old lease */
 	dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, release);
 
-	connection = nm_device_get_applied_connection (self);
-	g_return_val_if_fail (connection, FALSE);
-
 	/* Start DHCP again on the interface */
-	return dhcp4_start (self, connection) != NM_ACT_STAGE_RETURN_FAILURE;
+	return dhcp4_start (self) != NM_ACT_STAGE_RETURN_FAILURE;
 }
 
 /*****************************************************************************/
@@ -5989,66 +6182,63 @@ nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 static GHashTable *shared_ips = NULL;
 
 static void
-release_shared_ip (gpointer data)
+shared_ip_release (gpointer data)
 {
 	g_hash_table_remove (shared_ips, data);
+	if (!g_hash_table_size (shared_ips))
+		g_clear_pointer (&shared_ips, g_hash_table_unref);
 }
 
-static gboolean
-reserve_shared_ip (NMDevice *self, NMSettingIPConfig *s_ip4, NMPlatformIP4Address *address)
+static NMIP4Config *
+shared4_new_config (NMDevice *self, NMConnection *connection)
 {
-	if (G_UNLIKELY (shared_ips == NULL))
-		shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
+	NMIP4Config *config = NULL;
+	gboolean is_generated = FALSE;
+	NMSettingIPConfig *s_ip4;
+	NMPlatformIP4Address address = {
+		.addr_source = NM_IP_CONFIG_SOURCE_SHARED,
+	};
 
-	memset (address, 0, sizeof (*address));
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (connection, NULL);
 
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	if (s_ip4 && nm_setting_ip_config_get_num_addresses (s_ip4)) {
 		/* Use the first user-supplied address */
 		NMIPAddress *user = nm_setting_ip_config_get_address (s_ip4, 0);
 		in_addr_t a;
 
-		g_assert (user);
 		nm_ip_address_get_address_binary (user, &a);
-		nm_platform_ip4_address_set_addr (address, a, nm_ip_address_get_prefix (user));
+		nm_platform_ip4_address_set_addr (&address, a, nm_ip_address_get_prefix (user));
 	} else {
 		/* Find an unused address in the 10.42.x.x range */
 		guint32 start = (guint32) ntohl (0x0a2a0001); /* 10.42.0.1 */
 		guint32 count = 0;
 
-		while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
-			count += ntohl (0x100);
-			if (count > ntohl (0xFE00)) {
-				_LOGE (LOGD_SHARING, "ran out of shared IP addresses!");
-				return FALSE;
+		if (G_UNLIKELY (!shared_ips))
+			shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
+		else {
+			while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
+				count += ntohl (0x100);
+				if (count > ntohl (0xFE00)) {
+					_LOGE (LOGD_SHARING, "ran out of shared IP addresses!");
+					return FALSE;
+				}
 			}
 		}
-		nm_platform_ip4_address_set_addr (address, start + count, 24);
-		g_hash_table_add (shared_ips, GUINT_TO_POINTER (address->address));
+		nm_platform_ip4_address_set_addr (&address, start + count, 24);
+		g_hash_table_add (shared_ips, GUINT_TO_POINTER (address.address));
+		is_generated = TRUE;
 	}
 
-	return TRUE;
-}
-
-static NMIP4Config *
-shared4_new_config (NMDevice *self, NMConnection *connection)
-{
-	NMIP4Config *config = NULL;
-	NMPlatformIP4Address address;
-
-	g_return_val_if_fail (self != NULL, NULL);
-
-	if (!reserve_shared_ip (self, nm_connection_get_setting_ip4_config (connection), &address))
-		return NULL;
-
-	config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
-	address.addr_source = NM_IP_CONFIG_SOURCE_SHARED;
+	config = _ip4_config_new (self);
 	nm_ip4_config_add_address (config, &address);
-
-	/* Remove the address lock when the object gets disposed */
-	g_object_set_qdata_full (G_OBJECT (config), NM_CACHED_QUARK ("shared-ip"),
-	                        GUINT_TO_POINTER (address.address),
-	                        release_shared_ip);
-
+	if (is_generated) {
+		/* Remove the address lock when the object gets disposed */
+		g_object_set_qdata_full (G_OBJECT (config), NM_CACHED_QUARK ("shared-ip"),
+		                         GUINT_TO_POINTER (address.address),
+		                         shared_ip_release);
+	}
 	return config;
 }
 
@@ -6092,9 +6282,16 @@ static gboolean
 connection_requires_carrier (NMConnection *connection)
 {
 	NMSettingIPConfig *s_ip4, *s_ip6;
+	NMSettingConnection *s_con;
 	gboolean ip4_carrier_wanted, ip6_carrier_wanted;
 	gboolean ip4_used = FALSE, ip6_used = FALSE;
 
+	/* We can progress to IP_CONFIG now, so that we're enslaved.
+	 * That may actually cause carrier to go up and thus continue acivation. */
+	s_con = nm_connection_get_setting_connection (connection);
+	if (nm_setting_connection_get_master (s_con))
+		return FALSE;
+
 	ip4_carrier_wanted = connection_ip4_method_requires_carrier (connection, &ip4_used);
 	if (ip4_carrier_wanted) {
 		/* If IPv4 wants a carrier and cannot fail, the whole connection
@@ -6128,16 +6325,19 @@ connection_requires_carrier (NMConnection *connection)
 }
 
 static gboolean
-have_any_ready_slaves (NMDevice *self, const GSList *slaves)
+have_any_ready_slaves (NMDevice *self)
 {
-	const GSList *iter;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	SlaveInfo *info;
+	CList *iter;
 
 	/* Any enslaved slave is "ready" in the generic case as it's
 	 * at least >= NM_DEVCIE_STATE_IP_CONFIG and has had Layer 2
 	 * properties set up.
 	 */
-	for (iter = slaves; iter; iter = g_slist_next (iter)) {
-		if (nm_device_get_enslaved (iter->data))
+	c_list_for_each (iter, &priv->slaves) {
+		info = c_list_entry (iter, SlaveInfo, lst_slave);
+		if (NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
 			return TRUE;
 	}
 	return FALSE;
@@ -6161,29 +6361,23 @@ act_stage3_ip4_config_start (NMDevice *self,
 	NMConnection *connection;
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	const char *method;
-	GSList *slaves;
-	gboolean ready_slaves;
 
 	connection = nm_device_get_applied_connection (self);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
 	if (   connection_ip4_method_requires_carrier (connection, NULL)
-	    && priv->is_master
+	    && nm_device_is_master (self)
 	    && !priv->carrier) {
 		_LOGI (LOGD_IP4 | LOGD_DEVICE,
 		       "IPv4 config waiting until carrier is on");
 		return NM_ACT_STAGE_RETURN_IP_WAIT;
 	}
 
-	if (priv->is_master && ip4_requires_slaves (connection)) {
+	if (nm_device_is_master (self) && ip4_requires_slaves (connection)) {
 		/* If the master has no ready slaves, and depends on slaves for
 		 * a successful IPv4 attempt, then postpone IPv4 addressing.
 		 */
-		slaves = nm_device_master_get_slaves (self);
-		ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves);
-		g_slist_free (slaves);
-
-		if (ready_slaves == FALSE) {
+		if (!have_any_ready_slaves (self)) {
 			_LOGI (LOGD_DEVICE | LOGD_IP4,
 			       "IPv4 config waiting until slaves are ready");
 			return NM_ACT_STAGE_RETURN_IP_WAIT;
@@ -6195,7 +6389,7 @@ act_stage3_ip4_config_start (NMDevice *self,
 
 	/* Start IPv4 addressing based on the method requested */
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
-		ret = dhcp4_start (self, connection);
+		ret = dhcp4_start (self);
 		if (ret == NM_ACT_STAGE_RETURN_FAILURE)
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
 	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) == 0) {
@@ -6205,10 +6399,11 @@ act_stage3_ip4_config_start (NMDevice *self,
 	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) {
 		NMIP4Config **configs, *config;
 
-		config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+		config = _ip4_config_new (self);
 		nm_ip4_config_merge_setting (config,
 		                             nm_connection_get_setting_ip4_config (connection),
-		                             nm_device_get_ip4_route_metric (self));
+		                             nm_device_get_route_table (self, AF_INET, TRUE),
+		                             nm_device_get_route_metric (self, AF_INET));
 
 		configs = g_new0 (NMIP4Config *, 2);
 		configs[0] = config;
@@ -6266,15 +6461,6 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	}
 }
 
-static void
-_ip6_config_merge_default (gpointer value, gpointer user_data)
-{
-	NMIP6Config *src = (NMIP6Config *) value;
-	NMIP6Config *dst = (NMIP6Config *) user_data;
-
-	nm_ip6_config_merge (dst, src, NM_IP_CONFIG_MERGE_DEFAULT);
-}
-
 static gboolean
 ip6_config_merge_and_apply (NMDevice *self,
                             gboolean commit)
@@ -6283,14 +6469,11 @@ ip6_config_merge_and_apply (NMDevice *self,
 	NMConnection *connection;
 	gboolean success;
 	NMIP6Config *composite;
-	gboolean has_direct_route;
-	const struct in6_addr *gateway;
-	gboolean connection_has_default_route, connection_is_never_default;
-	gboolean routes_full_sync;
 	gboolean ignore_auto_routes = FALSE;
 	gboolean ignore_auto_dns = FALSE;
-	gboolean auto_method = FALSE;
+	gboolean ignore_default_routes = FALSE;
 	const char *token = NULL;
+	GSList *iter;
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	connection = nm_device_get_applied_connection (self);
@@ -6303,17 +6486,17 @@ ip6_config_merge_and_apply (NMDevice *self,
 			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);
-
-			if (NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
-			                  NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-			                  NM_SETTING_IP6_CONFIG_METHOD_DHCP))
-				auto_method = TRUE;
 		}
 	}
 
-	composite = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+	composite = _ip6_config_new (self);
 	nm_ip6_config_set_privacy (composite,
 	                           priv->ndisc ?
 	                           priv->ndisc_use_tempaddr :
@@ -6321,35 +6504,35 @@ ip6_config_merge_and_apply (NMDevice *self,
 	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 (priv->queued_ip6_config_id) {
-			g_clear_object (&priv->ext_ip6_config);
-			g_clear_object (&priv->ext_ip6_config_captured);
-			priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_platform (self),
-			                                                       nm_device_get_ip_ifindex (self),
-			                                                       FALSE,
-			                                                       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);
-		}
 	}
 
+	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_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 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_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 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));
 	}
 
-	g_slist_foreach (priv->vpn6_configs, _ip6_config_merge_default, composite);
+	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);
+		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.
@@ -6357,108 +6540,34 @@ ip6_config_merge_and_apply (NMDevice *self,
 	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_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
-	}
-
-	/* 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);
-
-	/* Add the default route.
-	 *
-	 * We keep track of the default route of a device in a private field.
-	 * NMDevice needs to know the default route at this point, because the gateway
-	 * might require a direct route (see below).
-	 *
-	 * But also, we don't want to add the default route to priv->ip6_config,
-	 * because the default route from the setting might not be the same that
-	 * NMDefaultRouteManager eventually configures (because the it might
-	 * tweak the effective metric).
-	 */
-
-	/* unless we come to a different conclusion below, we have no default route and
-	 * the route is assumed. */
-	priv->default_route.v6_has = FALSE;
-	priv->default_route.v6_is_assumed = TRUE;
-
-	if (!commit) {
-		/* during a non-commit event, we always pickup whatever is configured. */
-		goto END_ADD_DEFAULT_ROUTE;
+		                     | (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));
 	}
 
-	/* a generated-assumed connection detects the default route from the platform,
-	 * but if the IP method is automatic we need to update the default route to
-	 * maintain connectivity.
-	 */
-	if (nm_device_sys_iface_state_is_external (self) && !auto_method)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	/* At this point, we treat assumed and non-assumed connections alike.
-	 * For assumed connections we do that because we still manage RA and DHCP
-	 * leases for them, so we must extend/update the default route on commits.
-	 */
-
-	connection_has_default_route
-	    = nm_default_route_manager_ip6_connection_has_default_route (nm_netns_get_default_route_manager (priv->netns),
-	                                                                 connection, &connection_is_never_default);
-
-	if (   !priv->v6_commit_first_time
-	    && connection_is_never_default) {
-		/* If the connection is explicitly configured as never-default, we enforce the (absence of the)
-		 * default-route only once. That allows the user to configure a connection as never-default,
-		 * but he can add default routes externally (via a dispatcher script) and NM will not interfere. */
-		goto END_ADD_DEFAULT_ROUTE;
-	}
+	if (priv->rt6_temporary_not_available) {
+		const NMPObject *o;
+		GHashTableIter hiter;
 
-	/* we are about to commit (for a non-assumed connection). Enforce whatever we have
-	 * configured. */
-	priv->default_route.v6_is_assumed = FALSE;
-
-	if (!connection_has_default_route)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	if (!nm_ip6_config_get_num_addresses (composite)) {
-		/* without addresses we can have no default route. */
-		goto END_ADD_DEFAULT_ROUTE;
+		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);
+		}
 	}
 
-	gateway = nm_ip6_config_get_gateway (composite);
-	if (!gateway)
-		goto END_ADD_DEFAULT_ROUTE;
-
-
-	has_direct_route = nm_ip6_config_get_direct_route_for_host (composite, gateway) != NULL;
-
-
-
-	priv->default_route.v6_has = TRUE;
-	memset (&priv->default_route.v6, 0, sizeof (priv->default_route.v6));
-	priv->default_route.v6.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	priv->default_route.v6.gateway = *gateway;
-	priv->default_route.v6.metric = route_metric_with_penalty (self,
-	                                                           nm_device_get_ip6_route_metric (self));
-	priv->default_route.v6.mss = nm_ip6_config_get_mss (composite);
-
-	if (!has_direct_route) {
-		NMPlatformIP6Route r = priv->default_route.v6;
-
-		/* add a direct route to the gateway */
-		r.network = *gateway;
-		r.plen = 128;
-		r.gateway = in6addr_any;
-		nm_ip6_config_add_route (composite, &r);
+	/* 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));
 	}
 
-END_ADD_DEFAULT_ROUTE:
-
-	if (priv->default_route.v6_is_assumed) {
-		/* If above does not explicitly assign a default route, we always pick up the
-		 * default route based on what is currently configured.
-		 * That means that even managed connections with never-default, can
-		 * get a default route (if configured externally).
-		 */
-		priv->default_route.v6_has = _device_get_default_route_from_platform (self, AF_INET6, (NMPlatformIPRoute *) &priv->default_route.v6);
+	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 */
@@ -6472,11 +6581,7 @@ END_ADD_DEFAULT_ROUTE:
 		}
 	}
 
-	routes_full_sync =    commit
-	                   && priv->v6_commit_first_time
-	                   && !nm_device_sys_iface_state_is_external_or_assume (self);
-
-	success = nm_device_set_ip6_config (self, composite, commit, routes_full_sync);
+	success = nm_device_set_ip6_config (self, composite, commit);
 	g_object_unref (composite);
 	if (commit)
 		priv->v6_commit_first_time = FALSE;
@@ -6533,28 +6638,30 @@ dhcp6_restart_cb (gpointer user_data)
 }
 
 static void
-dhcp_schedule_restart (NMDevice *self, int family, const char *reason)
+dhcp_schedule_restart (NMDevice *self,
+                       int addr_family,
+                       const char *reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean inet4;
 	guint tries_left;
-	gs_free char *tries_str = NULL;
+	char tries_str[255];
 
-	g_return_if_fail (family == AF_INET || family == AF_INET6);
-	inet4 = family == AF_INET;
+	nm_assert_addr_family (addr_family);
 
-	tries_left = inet4 ? priv->dhcp4.num_tries_left : priv->dhcp6.num_tries_left;
-	if (tries_left != DHCP_NUM_TRIES_MAX)
-		tries_str = g_strdup_printf (", %u tries left", tries_left + 1);
+	tries_left =   (addr_family == AF_INET)
+	             ? priv->dhcp4.num_tries_left
+	             : priv->dhcp6.num_tries_left;
 
-	_LOGI (inet4 ? LOGD_DHCP4 : LOGD_DHCP6,
+	_LOGI ((addr_family == AF_INET) ? LOGD_DHCP4 : LOGD_DHCP6,
 	       "scheduling DHCPv%c restart in %u seconds%s%s%s%s",
-	       inet4 ? '4' : '6',
+	       nm_utils_addr_family_to_char (addr_family),
 	       DHCP_RESTART_TIMEOUT,
-	       tries_str ? tries_str : "",
+	       (tries_left != DHCP_NUM_TRIES_MAX)
+	         ? nm_sprintf_buf (tries_str, ", %u tries left", tries_left + 1)
+	         : "",
 	       NM_PRINT_FMT_QUOTED (reason, " (reason: ", reason, ")", ""));
 
-	if (inet4) {
+	if (addr_family == AF_INET) {
 		priv->dhcp4.restart_id = g_timeout_add_seconds (DHCP_RESTART_TIMEOUT,
 		                                                dhcp4_restart_cb, self);
 	} else {
@@ -6631,9 +6738,8 @@ dhcp6_state_changed (NMDhcpClient *client,
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint i;
 
-	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE);
+	g_return_if_fail (nm_dhcp_client_get_addr_family (client) == AF_INET6);
 	g_return_if_fail (!ip6_config || NM_IS_IP6_CONFIG (ip6_config));
 
 	_LOGD (LOGD_DHCP6, "new DHCPv6 client state %d", state);
@@ -6648,10 +6754,11 @@ dhcp6_state_changed (NMDhcpClient *client,
 		    && event_id
 		    && priv->dhcp6.event_id
 		    && !strcmp (event_id, priv->dhcp6.event_id)) {
-			for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) {
-				nm_ip6_config_add_address (priv->dhcp6.ip6_config,
-				                           nm_ip6_config_get_address (ip6_config, i));
-			}
+			NMDedupMultiIter ipconf_iter;
+			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);
 		} else {
 			g_clear_object (&priv->dhcp6.ip6_config);
 			g_clear_pointer (&priv->dhcp6.event_id, g_free);
@@ -6741,15 +6848,17 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	}
 
 	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,
 	                                                &ll_addr->address,
 	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_ip6_route_metric (self),
+	                                                nm_device_get_route_table (self, AF_INET6, TRUE),
+	                                                nm_device_get_route_metric (self, AF_INET6),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip6),
-	                                                priv->dhcp_timeout,
+	                                                get_dhcp_timeout (self, AF_INET6),
 	                                                priv->dhcp_anycast_address,
 	                                                (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
 	                                                nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6)),
@@ -6769,7 +6878,7 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	}
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self))
-		priv->dhcp4.was_active = TRUE;
+		priv->dhcp6.was_active = TRUE;
 
 	return !!priv->dhcp6.client;
 }
@@ -6869,7 +6978,7 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 	NMPlatformIP6Address address = *subnet;
 
 	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+		priv->ac_ip6_config = _ip6_config_new (self);
 
 	/* Assign a ::1 address in the subnet for us. */
 	address.address.s6_addr32[3] |= htonl (1);
@@ -6899,7 +7008,7 @@ nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device)
 		nm_ip6_config_reset_nameservers (priv->ac_ip6_config);
 		nm_ip6_config_reset_searches (priv->ac_ip6_config);
 	} else
-		priv->ac_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+		priv->ac_ip6_config = _ip6_config_new (self);
 
 	if (from_device)
 		from_config = nm_device_get_ip6_config (from_device);
@@ -6990,7 +7099,6 @@ 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;
-	guint i, n;
 	NMConnection *connection;
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
@@ -6999,11 +7107,10 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 		return;
 
 	if (priv->ip6_config) {
-		n = nm_ip6_config_get_num_addresses (priv->ip6_config);
-		for (i = 0; i < n; i++) {
-			const NMPlatformIP6Address *addr;
+		NMDedupMultiIter ipconf_iter;
+		const NMPlatformIP6Address *addr;
 
-			addr = nm_ip6_config_get_address (priv->ip6_config, i);
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, priv->ip6_config, &addr) {
 			if (   IN6_IS_ADDR_LINKLOCAL (&addr->address)
 			    && !(addr->n_ifa_flags & IFA_F_DADFAILED)) {
 				/* Already have an LL address, nothing to do */
@@ -7152,6 +7259,26 @@ nm_device_get_configured_mtu_for_wired (NMDevice *self, gboolean *out_is_user_co
 /*****************************************************************************/
 
 static void
+_set_mtu (NMDevice *self, guint32 mtu)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->mtu == mtu)
+		return;
+
+	priv->mtu = mtu;
+	_notify (self, PROP_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
+		 * 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);
+	}
+}
+
+static void
 _commit_mtu (NMDevice *self, const NMIP4Config *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -7170,8 +7297,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		return;
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
-		/* for assumed connections we don't tamper with the MTU. This is
-		 * a bug and supposed to be fixed by the unmanaged/assumed rework. */
+		/* for assumed connections we don't tamper with the MTU. */
 		return;
 	}
 
@@ -7271,6 +7397,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 	})
 	if (   (mtu_desired && mtu_desired != mtu_plat)
 	    || (ip6_mtu && ip6_mtu != _IP6_MTU_SYS ())) {
+		gboolean anticipated_failure = FALSE;
 
 		if (!priv->mtu_initial && !priv->ip6_mtu_initial) {
 			/* before touching any of the MTU paramters, record the
@@ -7279,100 +7406,102 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 			priv->ip6_mtu_initial = _IP6_MTU_SYS ();
 		}
 
-		if (mtu_desired && mtu_desired != mtu_plat)
-			nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired);
+		if (mtu_desired && mtu_desired != mtu_plat) {
+			if (nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired) == NM_PLATFORM_ERROR_CANT_SET_MTU) {
+				anticipated_failure = TRUE;
+				_LOGW (LOGD_DEVICE, "mtu: failure to set MTU. %s",
+				       NM_IS_DEVICE_VLAN (self)
+				         ? "Is the parent's MTU size large enough?"
+				         : (!c_list_is_empty (&priv->slaves)
+				              ? "Are the MTU sizes of the slaves large enough?"
+				              : "Did you configure the MTU correctly?"));
+			}
+			priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+		}
 
 		if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS ()) {
-			nm_device_ipv6_sysctl_set (self, "mtu",
-			                           nm_sprintf_buf (sbuf, "%u", (unsigned) ip6_mtu));
+			if (!nm_device_ipv6_sysctl_set (self, "mtu",
+			                                nm_sprintf_buf (sbuf, "%u", (unsigned) ip6_mtu))) {
+				int errsv = errno;
+
+				_NMLOG (anticipated_failure && errsv == EINVAL ? LOGL_DEBUG : LOGL_WARN,
+				        LOGD_DEVICE,
+				        "mtu: failure to set IPv6 MTU%s",
+				        anticipated_failure && errsv == EINVAL
+				           ? ": Is the underlying MTU value successfully set?"
+				           : "");
+			}
+			priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
 		}
 	}
 #undef _IP6_MTU_SYS
 }
 
+void
+nm_device_commit_mtu (NMDevice *self)
+{
+	NMDeviceState state;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	state = nm_device_get_state (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);
+	} else
+		_LOGT (LOGD_DEVICE, "mtu: commit-mtu... skip due to state %s", nm_device_state_to_str (state));
+}
+
 static void
 ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, NMDevice *self)
 {
 	NMNDiscConfigMap changed = changed_int;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int i;
-	int system_support;
-	guint32 ifa_flags = 0x00;
-
-	/*
-	 * Check, whether kernel is recent enough to help user space handling RA.
-	 * If it's not supported, we have no ipv6-privacy and must add autoconf
-	 * addresses as /128. The reason for the /128 is to prevent the kernel
-	 * from adding a prefix route for this address.
-	 **/
-	system_support = nm_platform_check_support_kernel_extended_ifa_flags (nm_device_get_platform (self));
-
-	if (system_support)
-		ifa_flags = IFA_F_NOPREFIXROUTE;
-	if (   priv->ndisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR
-	    || priv->ndisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
-	{
-		/* without system_support, this flag will be ignored. Still set it, doesn't seem to do any harm. */
-		ifa_flags |= IFA_F_MANAGETEMPADDR;
-	}
+	guint i;
 
 	g_return_if_fail (priv->act_request);
 
 	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
-
-	if (changed & NM_NDISC_CONFIG_GATEWAYS) {
-		/* Use the first gateway as ordered in neighbor discovery cache. */
-		if (rdata->gateways_n)
-			nm_ip6_config_set_gateway (priv->ac_ip6_config, &rdata->gateways[0].address);
-		else
-			nm_ip6_config_set_gateway (priv->ac_ip6_config, NULL);
-	}
+		priv->ac_ip6_config = _ip6_config_new (self);
 
 	if (changed & NM_NDISC_CONFIG_ADDRESSES) {
-		/* Rebuild address list from neighbor discovery cache. */
-		nm_ip6_config_reset_addresses (priv->ac_ip6_config);
-
-		/* ndisc->addresses contains at most max_addresses entries.
-		 * This is different from what the kernel does, which
-		 * also counts static and temporary addresses when checking
-		 * max_addresses.
-		 **/
-		for (i = 0; i < rdata->addresses_n; i++) {
-			const NMNDiscAddress *discovered_address = &rdata->addresses[i];
-			NMPlatformIP6Address address;
-
-			memset (&address, 0, sizeof (address));
-			address.address = discovered_address->address;
-			address.plen = system_support ? 64 : 128;
-			address.timestamp = discovered_address->timestamp;
-			address.lifetime = discovered_address->lifetime;
-			address.preferred = discovered_address->preferred;
-			if (address.preferred > address.lifetime)
-				address.preferred = address.lifetime;
-			address.addr_source = NM_IP_CONFIG_SOURCE_NDISC;
-			address.n_ifa_flags = ifa_flags;
+		guint8 plen;
+		guint32 ifa_flags;
+
+		/* Check, whether kernel is recent enough to help user space handling RA.
+		 * If it's not supported, we have no ipv6-privacy and must add autoconf
+		 * addresses as /128. The reason for the /128 is to prevent the kernel
+		 * from adding a prefix route for this address. */
+		ifa_flags = 0;
+		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
+		                                      NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
+			ifa_flags |= IFA_F_NOPREFIXROUTE;
+			if (NM_IN_SET (priv->ndisc_use_tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+			                                         NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
+				ifa_flags |= IFA_F_MANAGETEMPADDR;
+			plen = 64;
+		} else
+			plen = 128;
 
-			nm_ip6_config_add_address (priv->ac_ip6_config, &address);
-		}
+		nm_ip6_config_reset_addresses_ndisc (priv->ac_ip6_config,
+		                                     rdata->addresses,
+		                                     rdata->addresses_n,
+		                                     plen,
+		                                     ifa_flags);
 	}
 
-	if (changed & NM_NDISC_CONFIG_ROUTES) {
-		/* Rebuild route list from neighbor discovery cache. */
-		nm_ip6_config_reset_routes (priv->ac_ip6_config);
-
-		for (i = 0; i < rdata->routes_n; i++) {
-			const NMNDiscRoute *discovered_route = &rdata->routes[i];
-			const NMPlatformIP6Route route = {
-				.network    = discovered_route->network,
-				.plen       = discovered_route->plen,
-				.gateway    = discovered_route->gateway,
-				.rt_source  = NM_IP_CONFIG_SOURCE_NDISC,
-				.metric     = nm_device_get_ip6_route_metric (self),
-			};
-
-			nm_ip6_config_add_route (priv->ac_ip6_config, &route);
-		}
+	if (NM_FLAGS_ANY (changed,   NM_NDISC_CONFIG_ROUTES
+	                           | NM_NDISC_CONFIG_GATEWAYS)) {
+		nm_ip6_config_reset_routes_ndisc (priv->ac_ip6_config,
+		                                  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) {
@@ -7465,8 +7594,10 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	}
 
 	/* Apply any manual configuration before starting RA */
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	if (!ip6_config_merge_and_apply (self, 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. */
 	switch (nm_ndisc_get_node_type (priv->ndisc)) {
@@ -7537,6 +7668,9 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 		priv->ac_ip6_config = NULL;
 	}
 
+	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
+
 	s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
 	g_assert (s_ip6);
 
@@ -7559,7 +7693,8 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	priv->ndisc_use_tempaddr = use_tempaddr;
 
 	if (   NM_IN_SET (use_tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
-	    && !nm_platform_check_support_kernel_extended_ifa_flags (nm_device_get_platform (self))) {
+	    && !nm_platform_check_kernel_support (nm_device_get_platform (self),
+	                                          NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
 		_LOGW (LOGD_IP6, "The kernel does not support extended IFA_FLAGS needed by NM for "
 		                 "IPv6 private addresses. This feature is not available");
 	}
@@ -7590,6 +7725,8 @@ addrconf6_cleanup (NMDevice *self)
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE);
 
 	g_clear_object (&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);
 }
 
@@ -7616,8 +7753,13 @@ save_ip6_properties (NMDevice *self)
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
+	if (!nm_device_get_ip_ifindex (self))
+		return;
+
 	for (i = 0; i < G_N_ELEMENTS (ip6_properties_to_save); i++) {
-		value = nm_platform_sysctl_get (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (ifname, ip6_properties_to_save[i])));
+		char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+		value = nm_platform_sysctl_get (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, ifname, ip6_properties_to_save[i])));
 		if (value) {
 			g_hash_table_insert (priv->ip6_saved_properties,
 			                     (char *) ip6_properties_to_save[i],
@@ -7657,7 +7799,8 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 	int ifindex = nm_device_get_ip_ifindex (self);
 	char *value;
 
-	if (!nm_platform_check_support_user_ipv6ll (nm_device_get_platform (self)))
+	if (!nm_platform_check_kernel_support (nm_device_get_platform (self),
+	                                       NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
 		return;
 
 	priv->nm_ipv6ll = enable;
@@ -7672,13 +7815,15 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 			        LOGD_IP6,
 			        "failed to %s userspace IPv6LL address handling (%s)",
 			        detail,
-			        nm_platform_error_to_string (plerr));
+			        nm_platform_error_to_string_a (plerr));
 		}
 
 		if (enable) {
+			char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
 			/* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */
 			value = nm_platform_sysctl_get (nm_device_get_platform (self),
-			                                NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), "disable_ipv6")));
+			                                NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), "disable_ipv6")));
 			if (g_strcmp0 (value, "0") == 0)
 				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
 			g_free (value);
@@ -7739,6 +7884,9 @@ _ip6_privacy_get (NMDevice *self)
 	if (ip6_privacy != NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN)
 		return ip6_privacy;
 
+	if (!nm_device_get_ip_ifindex (self))
+		return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;;
+
 	/* 3.) No valid default-value configured. Fallback to reading sysctl.
 	 *
 	 * Instead of reading static config files in /etc, just read the current sysctl value.
@@ -7775,29 +7923,23 @@ act_stage3_ip6_config_start (NMDevice *self,
 	const char *method;
 	NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
 	const char *ip6_privacy_str = "0";
-	GSList *slaves;
-	gboolean ready_slaves;
 
 	connection = nm_device_get_applied_connection (self);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
 	if (   connection_ip6_method_requires_carrier (connection, NULL)
-	    && priv->is_master
+	    && nm_device_is_master (self)
 	    && !priv->carrier) {
 		_LOGI (LOGD_IP6 | LOGD_DEVICE,
 		       "IPv6 config waiting until carrier is on");
 		return NM_ACT_STAGE_RETURN_IP_WAIT;
 	}
 
-	if (priv->is_master && ip6_requires_slaves (connection)) {
+	if (nm_device_is_master (self) && ip6_requires_slaves (connection)) {
 		/* If the master has no ready slaves, and depends on slaves for
 		 * a successful IPv6 attempt, then postpone IPv6 addressing.
 		 */
-		slaves = nm_device_master_get_slaves (self);
-		ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves);
-		g_slist_free (slaves);
-
-		if (ready_slaves == FALSE) {
+		if (!have_any_ready_slaves (self)) {
 			_LOGI (LOGD_DEVICE | LOGD_IP6,
 			       "IPv6 config waiting until slaves are ready");
 			return NM_ACT_STAGE_RETURN_IP_WAIT;
@@ -7810,7 +7952,8 @@ act_stage3_ip6_config_start (NMDevice *self,
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
-		if (!priv->master) {
+		if (   !priv->master
+		    && !nm_device_sys_iface_state_is_external (self)) {
 			gboolean old_nm_ipv6ll = priv->nm_ipv6ll;
 
 			/* When activating an IPv6 'ignore' connection we need to revert back
@@ -7847,7 +7990,8 @@ act_stage3_ip6_config_start (NMDevice *self,
 	 */
 	nm_platform_process_events (nm_device_get_platform (self));
 	g_clear_object (&priv->ext_ip6_config_captured);
-	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_platform (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);
@@ -7911,17 +8055,11 @@ nm_device_activate_stage3_ip4_start (NMDevice *self)
 
 	g_assert (priv->ip4_state == IP_WAIT);
 
-	/* Slaves stay in IP_CONFIG state until master is ready, and then
-	 * they go directly to SECONDARIES without configuring IPv4.
-	 */
-	if (nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request)))
-		return TRUE;
-
 	_set_ip_state (self, AF_INET, IP_CONF);
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		if (!ip4_config)
-			ip4_config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+			ip4_config = _ip4_config_new (self);
 		nm_device_activate_schedule_ip4_config_result (self, ip4_config);
 		g_object_unref (ip4_config);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
@@ -7958,17 +8096,11 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 
 	g_assert (priv->ip6_state == IP_WAIT);
 
-	/* Slaves stay in IP_CONFIG state until master is ready, and then
-	 * they go directly to SECONDARIES without configuring IPv6.
-	 */
-	if (nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request)))
-		return TRUE;
-
 	_set_ip_state (self, AF_INET6, IP_CONF);
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		if (!ip6_config)
-			ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+			ip6_config = _ip6_config_new (self);
 		/* Here we get a static IPv6 config, like for Shared where it's
 		 * autogenerated or from modems where it comes from ModemManager.
 		 */
@@ -8002,39 +8134,18 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 static void
 activate_stage3_ip_config_start (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActiveConnection *master;
-	NMDevice *master_device;
-
 	_set_ip_state (self, AF_INET, IP_WAIT);
 	_set_ip_state (self, AF_INET6, IP_WAIT);
 
+	_active_connection_set_state_flags (self,
+	                                    NM_ACTIVATION_STATE_FLAG_LAYER2_READY);
+
 	nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
 	/* Device should be up before we can do anything with it */
 	if (!nm_platform_link_is_up (nm_device_get_platform (self), nm_device_get_ip_ifindex (self)))
 		_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 
-	/* If the device is a slave, then we don't do any IP configuration but we
-	 * use the IP config stage to indicate to the master we're ready for
-	 * enslavement.  If the master is already activating, it will have tried to
-	 * enslave us when we changed state to IP_CONFIG, causing us to queue a
-	 * transition to SECONDARIES (or FAILED if the enslavement failed), with
-	 * our IP states set to IP_DONE either way.  If the master isn't yet
-	 * activating, then they'll still be in IP_WAIT.  Either way, we bail out
-	 * of IP config here.
-	 */
-	master = nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request));
-	if (master) {
-		master_device = nm_active_connection_get_device (master);
-		if (priv->ip4_state == IP_WAIT && priv->ip6_state == IP_WAIT) {
-			_LOGI (LOGD_DEVICE, "Activation: connection '%s' waiting on master '%s'",
-			       nm_connection_get_id (nm_device_get_applied_connection (self)),
-			       master_device ? nm_device_get_iface (master_device) : "(unknown)");
-		}
-		return;
-	}
-
 	/* IPv4 */
 	if (   nm_device_activate_ip4_state_in_wait (self)
 	    && !nm_device_activate_stage3_ip4_start (self))
@@ -8309,18 +8420,20 @@ start_sharing (NMDevice *self, NMIP4Config *config)
 	char str_addr[INET_ADDRSTRLEN + 1];
 	char str_mask[INET_ADDRSTRLEN + 1];
 	guint32 netmask, network;
-	const NMPlatformIP4Address *ip4_addr;
+	const NMPlatformIP4Address *ip4_addr = NULL;
 	const char *ip_iface;
 
 	g_return_val_if_fail (config != NULL, FALSE);
 
 	ip_iface = nm_device_get_ip_iface (self);
+	if (!ip_iface)
+		return FALSE;
 
-	ip4_addr = nm_ip4_config_get_address (config, 0);
+	ip4_addr = nm_ip4_config_get_first_address (config);
 	if (!ip4_addr || !ip4_addr->address)
 		return FALSE;
 
-	netmask = nm_utils_ip4_prefix_to_netmask (ip4_addr->plen);
+	netmask = _nm_utils_ip4_prefix_to_netmask (ip4_addr->plen);
 	if (!inet_ntop (AF_INET, &netmask, str_mask, sizeof (str_mask)))
 		return FALSE;
 
@@ -8420,7 +8533,7 @@ arp_announce (NMDevice *self)
 }
 
 static void
-activate_stage5_ip4_config_commit (NMDevice *self)
+activate_stage5_ip4_config_result (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
@@ -8442,7 +8555,7 @@ activate_stage5_ip4_config_commit (NMDevice *self)
 	}
 
 	/* NULL to use the existing priv->dev_ip4_config */
-	if (!ip4_config_merge_and_apply (self, NULL, TRUE)) {
+	if (!ip4_config_merge_and_apply (self, 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;
@@ -8492,7 +8605,7 @@ nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *conf
 	if (config)
 		priv->dev_ip4_config = g_object_ref (config);
 
-	activation_source_schedule (self, activate_stage5_ip4_config_commit, AF_INET);
+	activation_source_schedule (self, activate_stage5_ip4_config_result, AF_INET);
 }
 
 gboolean
@@ -8530,7 +8643,8 @@ dad6_get_pending_addresses (NMDevice *self)
 	                         priv->wwan_ip6_config };
 	const NMPlatformIP6Address *addr, *pl_addr;
 	NMIP6Config *dad6_config = NULL;
-	guint i, j, num;
+	NMDedupMultiIter ipconf_iter;
+	guint i;
 	int ifindex;
 
 	ifindex = nm_device_get_ip_ifindex (self);
@@ -8541,13 +8655,11 @@ dad6_get_pending_addresses (NMDevice *self)
 	 */
 	for (i = 0; i < G_N_ELEMENTS (confs); i++) {
 		if (confs[i]) {
-			num = nm_ip6_config_get_num_addresses (confs[i]);
-			for (j = 0; j < num; j++) {
-				addr = nm_ip6_config_get_address (confs[i], j);
+
+			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,
-				                                       addr->plen);
+				                                       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)
@@ -8556,7 +8668,7 @@ dad6_get_pending_addresses (NMDevice *self)
 					       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
 
 					if (!dad6_config)
-						dad6_config = nm_ip6_config_new (ifindex);
+						dad6_config = _ip6_config_new (self);
 
 					nm_ip6_config_add_address (dad6_config, pl_addr);
 				}
@@ -8584,6 +8696,8 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 
 	/* Interface must be IFF_UP before IP config can be applied */
 	ip_ifindex = nm_device_get_ip_ifindex (self);
+	g_return_if_fail (ip_ifindex);
+
 	if (!nm_platform_link_is_up (nm_device_get_platform (self), ip_ifindex) && !nm_device_sys_iface_state_is_external_or_assume (self)) {
 		nm_platform_link_set_up (nm_device_get_platform (self), ip_ifindex, NULL);
 		if (!nm_platform_link_is_up (nm_device_get_platform (self), ip_ifindex))
@@ -8756,16 +8870,15 @@ static void
 _update_ip4_address (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint32 addr;
+	const NMPlatformIP4Address *address;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	if (   priv->ip4_config
 	    && ip_config_valid (priv->state)
-	    && nm_ip4_config_get_num_addresses (priv->ip4_config)) {
-		addr = nm_ip4_config_get_address (priv->ip4_config, 0)->address;
-		if (addr != priv->ip4_address) {
-			priv->ip4_address = addr;
+	    && (address = nm_ip4_config_get_first_address (priv->ip4_config))) {
+		if (address->address != priv->ip4_address) {
+			priv->ip4_address = address->address;
 			_notify (self, PROP_IP4_ADDRESS);
 		}
 	}
@@ -8802,7 +8915,7 @@ delete_on_deactivate_link_delete (gpointer user_data)
 
 		if (!nm_device_unrealize (data->device, TRUE, &error))
 			_LOGD (LOGD_DEVICE, "delete_on_deactivate: unrealizing %d failed (%s)", data->ifindex, error->message);
-	} else
+	} else if (data->ifindex > 0)
 		nm_platform_link_delete (nm_device_get_platform (self), data->ifindex);
 
 	g_free (data);
@@ -8833,8 +8946,6 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	DeleteOnDeactivateData *data;
 
-	if (ifindex <= 0)
-		return;
 	if (!priv->nm_owned)
 		return;
 	if (priv->queued_act_request)
@@ -8905,7 +9016,7 @@ _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
 #if NM_MORE_ASSERTS > 10
 	/* Assert that the keys are unique. */
 	{
-		gs_unref_hashtable GHashTable *check_dups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+		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]))
@@ -8971,10 +9082,11 @@ nm_device_reactivate_ip4_config (NMDevice *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 = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+		priv->con_ip4_config = _ip4_config_new (self);
 		nm_ip4_config_merge_setting (priv->con_ip4_config,
 		                             s_ip4_new,
-		                             nm_device_get_ip4_route_metric (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
@@ -8992,7 +9104,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
 		} else {
-			if (!ip4_config_merge_and_apply (self, NULL, TRUE))
+			if (!ip4_config_merge_and_apply (self, TRUE))
 				_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
 		}
 	}
@@ -9013,10 +9125,11 @@ nm_device_reactivate_ip6_config (NMDevice *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 = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+		priv->con_ip6_config = _ip6_config_new (self);
 		nm_ip6_config_merge_setting (priv->con_ip6_config,
 		                             s_ip6_new,
-		                             nm_device_get_ip6_route_metric (self));
+		                             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
@@ -9096,7 +9209,27 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 	                         NM_SETTING_IP4_CONFIG_SETTING_NAME,
 	                         NM_SETTING_IP6_CONFIG_SETTING_NAME,
 	                         NM_SETTING_PROXY_SETTING_NAME)) {
-		/* accept all */
+		if (g_hash_table_contains (diffs, NM_SETTING_IP_CONFIG_ROUTE_TABLE)) {
+			/* changing the route-table setting is complicated, because it affects
+			 * how we sync the routes. Don't support changing it without full
+			 * re-activation.
+			 *
+			 * The problem is really that changing the setting also affects the sync
+			 * mode. So, switching from NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN to
+			 * NM_IP_ROUTE_TABLE_SYNC_MODE_FULL would somehow require us to get rid
+			 * of additional routes, but we don't know which routes were added by NM
+			 * and which should be removed.
+			 *
+			 * Note how nm_device_get_route_table() caches the value for the duration of the
+			 * activation. */
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+			             "Can't reapply changes to '%s.%s' setting",
+			             setting_name,
+			             NM_SETTING_IP_CONFIG_ROUTE_TABLE);
+			return FALSE;
+		}
 		return TRUE;
 	} else {
 		g_set_error (error,
@@ -9484,6 +9617,97 @@ impl_device_get_applied_connection (NMDevice *self,
 
 /*****************************************************************************/
 
+typedef struct {
+	gint64 timestamp_ms;
+	bool dirty;
+} IP6RoutesTemporaryNotAvailableData;
+
+static gboolean
+_rt6_temporary_not_available_timeout (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	priv->rt6_temporary_not_available_id = 0;
+	nm_device_activate_schedule_ip6_config_result (self);
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_rt6_temporary_not_available_set (NMDevice *self,
+                                  GPtrArray *temporary_not_available)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	IP6RoutesTemporaryNotAvailableData *data;
+	GHashTableIter iter;
+	gint64 now_ms, oldest_ms;
+	const gint64 MAX_AGE_MS = 20000;
+	guint i;
+	gboolean success = TRUE;
+
+	if (   !temporary_not_available
+	    || !temporary_not_available->len) {
+		/* nothing outstanding. Clear tracking the routes. */
+		g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+		nm_clear_g_source (&priv->rt6_temporary_not_available_id);
+		return success;
+	}
+
+	if (priv->rt6_temporary_not_available) {
+		g_hash_table_iter_init (&iter, priv->rt6_temporary_not_available);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data))
+			data->dirty = TRUE;
+	} else {
+		priv->rt6_temporary_not_available = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
+		                                                           (GEqualFunc) nmp_object_id_equal,
+		                                                           (GDestroyNotify) nmp_object_unref,
+		                                                           nm_g_slice_free_fcn (IP6RoutesTemporaryNotAvailableData));
+	}
+
+	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	oldest_ms = now_ms;
+
+	for (i = 0; i < temporary_not_available->len; i++) {
+		const NMPObject *o = temporary_not_available->pdata[i];
+
+		data = g_hash_table_lookup (priv->rt6_temporary_not_available, o);
+		if (data) {
+			if (!data->dirty)
+				continue;
+			data->dirty = FALSE;
+			nm_assert (data->timestamp_ms > 0 && data->timestamp_ms <= now_ms);
+			if (now_ms > data->timestamp_ms + MAX_AGE_MS) {
+				/* timeout. Could not add this address. */
+				_LOGW (LOGD_DEVICE, "failure to add IPv6 route: %s",
+				       nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+				success = FALSE;
+			} else
+				oldest_ms = MIN (data->timestamp_ms, oldest_ms);
+			continue;
+		}
+
+		data = g_slice_new0 (IP6RoutesTemporaryNotAvailableData);
+		data->timestamp_ms = now_ms;
+		g_hash_table_insert (priv->rt6_temporary_not_available, (gpointer) nmp_object_ref (o), data);
+	}
+
+	g_hash_table_iter_init (&iter, priv->rt6_temporary_not_available);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data)) {
+		if (data->dirty)
+			g_hash_table_iter_remove (&iter);
+	}
+
+	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
+	priv->rt6_temporary_not_available_id = g_timeout_add (oldest_ms + MAX_AGE_MS - now_ms,
+	                                                      _rt6_temporary_not_available_timeout,
+	                                                      self);
+
+	return success;
+}
+
+/*****************************************************************************/
+
 static void
 disconnect_cb (NMDevice *self,
                GDBusMethodInvocation *context,
@@ -9835,47 +10059,43 @@ nm_device_get_ip4_config (NMDevice *self)
 static gboolean
 nm_device_set_ip4_config (NMDevice *self,
                           NMIP4Config *new_config,
-                          guint32 default_route_metric,
                           gboolean commit,
-                          gboolean routes_full_sync)
+                          GPtrArray *ip4_dev_route_blacklist)
 {
 	NMDevicePrivate *priv;
 	NMIP4Config *old_config = NULL;
 	gboolean has_changes = FALSE;
 	gboolean success = TRUE;
-	gboolean def_route_changed;
-	int ip_ifindex, config_ifindex;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	_LOGD (LOGD_IP4, "ip4-config: update (commit=%d, routes-full-sync=%d, new-config=%p)",
-	       commit, routes_full_sync, new_config);
+	_LOGD (LOGD_IP4, "ip4-config: update (commit=%d, new-config=%p)",
+	       commit, new_config);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	ip_ifindex = nm_device_get_ip_ifindex (self);
+	nm_assert (   !new_config
+	           || (   new_config
+	               && ({
+	                    int ip_ifindex = nm_device_get_ip_ifindex (self);
 
-	if (new_config) {
-		config_ifindex = nm_ip4_config_get_ifindex (new_config);
-		if (config_ifindex > 0)
-			g_return_val_if_fail (ip_ifindex == config_ifindex, FALSE);
-	}
+	                    (   ip_ifindex > 0
+	                     && ip_ifindex == nm_ip4_config_get_ifindex (new_config));
+	                  })));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	old_config = priv->ip4_config;
 
 	/* Always commit to nm-platform to update lifetimes */
 	if (commit && new_config) {
-		gboolean assumed = nm_device_sys_iface_state_is_external_or_assume (self);
-
 		_commit_mtu (self, new_config);
-		/* For assumed devices we must not touch the kernel-routes, such as the device-route.
-		 * FIXME: this is wrong in case where "assumed" means "take-over-seamlessly". In this
-		 * case, we should manage the device route, for example on new DHCP lease. */
 		success = nm_ip4_config_commit (new_config,
 		                                nm_device_get_platform (self),
-		                                nm_netns_get_route_manager (priv->netns),
-		                                ip_ifindex,
-		                                routes_full_sync,
-		                                assumed ? (gint64) -1 : (gint64) default_route_metric);
+		                                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) {
@@ -9906,7 +10126,6 @@ nm_device_set_ip4_config (NMDevice *self,
 		g_clear_object (&priv->dev_ip4_config);
 	}
 
-	def_route_changed = nm_default_route_manager_ip4_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
 	concheck_periodic_update (self);
 
 	if (!nm_device_sys_iface_state_is_external_or_assume (self))
@@ -9940,9 +10159,6 @@ nm_device_set_ip4_config (NMDevice *self,
 		}
 
 		nm_device_queue_recheck_assume (self);
-	} else if (def_route_changed) {
-		_LOGD (LOGD_IP4, "ip4-config: default route changed");
-		g_signal_emit (self, signals[IP4_CONFIG_CHANGED], 0, priv->ip4_config, priv->ip4_config);
 	}
 
 	return success;
@@ -9986,11 +10202,16 @@ nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *co
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	nm_assert (!old || NM_IS_IP4_CONFIG (old));
+	nm_assert (!config || NM_IS_IP4_CONFIG (config));
+	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))
 		return;
 
 	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (self, NULL, TRUE))
+	if (!ip4_config_merge_and_apply (self, TRUE))
 		_LOGW (LOGD_IP4, "failed to set VPN routes for device");
 }
 
@@ -10007,47 +10228,53 @@ nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config)
 		priv->wwan_ip4_config = g_object_ref (config);
 
 	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (self, NULL, TRUE))
+	if (!ip4_config_merge_and_apply (self, TRUE))
 		_LOGW (LOGD_IP4, "failed to set WWAN IPv4 configuration");
 }
 
 static gboolean
 nm_device_set_ip6_config (NMDevice *self,
                           NMIP6Config *new_config,
-                          gboolean commit,
-                          gboolean routes_full_sync)
+                          gboolean commit)
 {
 	NMDevicePrivate *priv;
 	NMIP6Config *old_config = NULL;
 	gboolean has_changes = FALSE;
 	gboolean success = TRUE;
-	gboolean def_route_changed;
-	int ip_ifindex, config_ifindex;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	_LOGD (LOGD_IP6, "ip6-config: update (commit=%d, routes-full-sync=%d, new-config=%p)",
-	       commit, routes_full_sync, new_config);
+	_LOGD (LOGD_IP6, "ip6-config: update (commit=%d, new-config=%p)",
+	       commit, new_config);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	ip_ifindex = nm_device_get_ip_ifindex (self);
+	nm_assert (   !new_config
+	           || (   new_config
+	               && ({
+	                    int ip_ifindex = nm_device_get_ip_ifindex (self);
 
-	if (new_config) {
-		config_ifindex = nm_ip6_config_get_ifindex (new_config);
-		if (config_ifindex > 0)
-			g_return_val_if_fail (ip_ifindex == config_ifindex, FALSE);
-	}
+	                    (   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_netns_get_route_manager (priv->netns),
-		                                ip_ifindex,
-		                                routes_full_sync);
+		                                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) {
@@ -10077,8 +10304,6 @@ nm_device_set_ip6_config (NMDevice *self,
 		       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
 	}
 
-	def_route_changed = nm_default_route_manager_ip6_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-
 	if (has_changes) {
 		NMSettingsConnection *settings_connection;
 
@@ -10108,9 +10333,6 @@ nm_device_set_ip6_config (NMDevice *self,
 
 		if (priv->ndisc)
 			ndisc_set_router_config (priv->ndisc, self);
-	} else if (def_route_changed) {
-		_LOGD (LOGD_IP6, "ip6-config: default route changed");
-		g_signal_emit (self, signals[IP6_CONFIG_CHANGED], 0, priv->ip6_config, priv->ip6_config);
 	}
 
 	return success;
@@ -10121,6 +10343,11 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	nm_assert (!old || NM_IS_IP6_CONFIG (old));
+	nm_assert (!config || NM_IS_IP6_CONFIG (config));
+	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))
 		return;
 
@@ -10370,7 +10597,7 @@ nm_device_start_ip_check (NMDevice *self)
 	NMSettingConnection *s_con;
 	guint timeout = 0;
 	const char *ping_binary = NULL;
-	char buf[INET6_ADDRSTRLEN] = { 0 };
+	char buf[NM_UTILS_INET_ADDRSTRLEN];
 	NMLogDomain log_domain = LOGD_IP4;
 
 	/* Shouldn't be any active ping here, since IP_CHECK happens after the
@@ -10389,25 +10616,24 @@ nm_device_start_ip_check (NMDevice *self)
 	g_assert (s_con);
 	timeout = nm_setting_connection_get_gateway_ping_timeout (s_con);
 
+	buf[0] = '\0';
 	if (timeout) {
-		if (priv->ip4_config && priv->ip4_state == IP_DONE) {
-			guint gw = 0;
+		const NMPObject *gw;
 
-			ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
-			log_domain = LOGD_IP4;
-
-			gw = nm_ip4_config_get_gateway (priv->ip4_config);
-			if (gw && !inet_ntop (AF_INET, &gw, buf, sizeof (buf)))
-				buf[0] = '\0';
+		if (priv->ip4_config && priv->ip4_state == IP_DONE) {
+			gw = nm_ip4_config_best_default_route_get (priv->ip4_config);
+			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) {
-			const struct in6_addr *gw = NULL;
-
-			ping_binary = nm_utils_find_helper ("ping6", "/usr/bin/ping6", NULL);
-			log_domain = LOGD_IP6;
-
-			gw = nm_ip6_config_get_gateway (priv->ip6_config);
-			if (gw && !inet_ntop (AF_INET6, gw, buf, sizeof (buf)))
-				buf[0] = '\0';
+			gw = nm_ip6_config_best_default_route_get (priv->ip6_config);
+			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);
+				log_domain = LOGD_IP6;
+			}
 		}
 	}
 
@@ -10507,6 +10733,8 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 	 * a timeout is reached.
 	 */
 	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
+		gint64 now_ms, until_ms;
+
 		/* we start a grace period of 5 seconds during which we will schedule
 		 * a pending action whenever we have no carrier.
 		 *
@@ -10515,7 +10743,10 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 		nm_clear_g_source (&priv->carrier_wait_id);
 		if (!priv->carrier)
 			nm_device_add_pending_action (self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
-		priv->carrier_wait_id = g_timeout_add_seconds (5, carrier_wait_timeout, self);
+
+		now_ms = nm_utils_get_monotonic_timestamp_ms ();
+		until_ms = NM_MAX (now_ms + CARRIER_WAIT_TIME_MS, priv->carrier_wait_until_ms);
+		priv->carrier_wait_id = g_timeout_add (until_ms - now_ms, carrier_wait_timeout, self);
 	}
 
 	/* Can only get HW address of some devices when they are up */
@@ -10525,7 +10756,7 @@ 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, NULL, TRUE))
+		if (!ip4_config_merge_and_apply (self, TRUE))
 			_LOGW (LOGD_IP4, "failed applying IP4 config after bringing link up");
 	}
 	if (priv->ip6_state == IP_DONE) {
@@ -10608,20 +10839,28 @@ find_ip4_lease_config (NMDevice *self,
 	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),
-	                                               FALSE,
-	                                               nm_device_get_ip4_route_metric (self));
+	                                               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_address (lease_config, 0);
-		guint32 gateway = nm_ip4_config_get_gateway (lease_config);
+		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;
-		if (gateway != nm_ip4_config_get_gateway (ext_ip4_config))
+		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);
 	}
@@ -10641,23 +10880,24 @@ capture_lease_config (NMDevice *self,
 	NMSettingsConnection *const*connections;
 	guint i;
 	gboolean dhcp_used = FALSE;
+	NMDedupMultiIter ipconf_iter;
 
 	/* 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) {
-		for (i = 0; i < nm_ip4_config_get_num_addresses (ext_ip4_config); i++) {
-			const NMPlatformIP4Address *addr = nm_ip4_config_get_address (ext_ip4_config, i);
+		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) {
-		for (i = 0; i < nm_ip6_config_get_num_addresses (ext_ip6_config); i++) {
-			const NMPlatformIP6Address *addr = nm_ip6_config_get_address (ext_ip6_config, i);
+		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;
@@ -10695,184 +10935,161 @@ capture_lease_config (NMDevice *self,
 	}
 }
 
-static void
-_ip4_config_intersect (gpointer value, gpointer user_data)
-{
-	NMIP4Config *dst = (NMIP4Config *) value;
-	NMIP4Config *src = (NMIP4Config *) user_data;
-
-	nm_ip4_config_intersect (dst, src);
-}
-
-static void
-_ip4_config_subtract (gpointer value, gpointer user_data)
-{
-	NMIP4Config *dst = (NMIP4Config *) user_data;
-	NMIP4Config *src = (NMIP4Config *) value;
-
-	nm_ip4_config_subtract (dst, src);
-}
-
-static void
-update_ip4_config (NMDevice *self, gboolean initial)
+static gboolean
+update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolean intersect_configs)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex;
 	gboolean capture_resolv_conf;
+	GSList *iter;
 
-	/* 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 (   !initial
-	    && activation_source_is_scheduled (self,
-	                                       activate_stage5_ip4_config_commit,
-	                                       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");
-		return;
-	}
+	nm_assert_addr_family (addr_family);
 
 	ifindex = nm_device_get_ip_ifindex (self);
 	if (!ifindex)
-		return;
+		return FALSE;
 
 	capture_resolv_conf =    initial
 	                      && nm_dns_manager_get_resolv_conf_explicit (nm_dns_manager_get ());
 
-	/* IPv4 */
-	g_clear_object (&priv->ext_ip4_config);
-	priv->ext_ip4_config = nm_ip4_config_capture (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);
-		}
-
-		/* FIXME: ext_ip4_config does not contain routes with source==RTPROT_KERNEL.
-		 * Hence, we will wrongly remove device-routes with metric=0 if they were added by
-		 * the user on purpose. This should be fixed by also tracking and exposing
-		 * kernel routes. */
-
-		/* 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->dev_ip4_config)
-			nm_ip4_config_intersect (priv->dev_ip4_config, priv->ext_ip4_config);
+	if (addr_family == AF_INET) {
 
-		g_slist_foreach (priv->vpn4_configs, _ip4_config_intersect, priv->ext_ip4_config);
+		g_clear_object (&priv->ext_ip4_config);
+		priv->ext_ip4_config = 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);
+			}
 
-		if (priv->wwan_ip4_config)
-			nm_ip4_config_intersect (priv->wwan_ip4_config, priv->ext_ip4_config);
+			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,
+					                         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);
+			}
 
-		/* Remove parts from ext_ip4_config 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->dev_ip4_config)
-			nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config);
+			/* Remove parts from ext_ip4_config 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,
+				                        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 (priv->wwan_ip4_config) {
+				nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_config,
+				                        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);
+		}
 
-		g_slist_foreach (priv->vpn4_configs, _ip4_config_subtract, priv->ext_ip4_config);
+	} else {
+		nm_assert (addr_family == AF_INET6);
 
-		if (priv->wwan_ip4_config)
-			nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_config);
+		g_clear_object (&priv->ext_ip6_config);
+		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);
+
+			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,
+					                         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);
+			}
 
-		ip4_config_merge_and_apply (self, NULL, FALSE);
+			/* Remove parts from ext_ip6_config 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,
+				                        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 (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 (priv->wwan_ip6_config) {
+				nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_ip6_config,
+				                        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);
+		}
 	}
-}
-
-static void
-_ip6_config_intersect (gpointer value, gpointer user_data)
-{
-	NMIP6Config *dst = (NMIP6Config *) value;
-	NMIP6Config *src = (NMIP6Config *) user_data;
-
-	nm_ip6_config_intersect (dst, src);
-}
-
-static void
-_ip6_config_subtract (gpointer value, gpointer user_data)
-{
-	NMIP6Config *dst = (NMIP6Config *) user_data;
-	NMIP6Config *src = (NMIP6Config *) value;
 
-	nm_ip6_config_subtract (dst, src);
+	return TRUE;
 }
 
 static void
-update_ip6_config (NMDevice *self, gboolean initial)
+update_ip_config (NMDevice *self, int addr_family, gboolean initial)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ifindex;
-	gboolean capture_resolv_conf;
 
-	/* 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 (   !initial
-	    && 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");
-		return;
-	}
-
-	ifindex = nm_device_get_ip_ifindex (self);
-	if (!ifindex)
-		return;
+	nm_assert_addr_family (addr_family);
 
-	capture_resolv_conf =    initial
-	                      && nm_dns_manager_get_resolv_conf_explicit (nm_dns_manager_get ());
+	if (update_ext_ip_config (self, addr_family, initial, TRUE)) {
+		if (addr_family == AF_INET) {
+			if (priv->ext_ip4_config)
+				ip4_config_merge_and_apply (self, FALSE);
+		} else {
+			if (priv->ext_ip6_config_captured)
+				ip6_config_merge_and_apply (self, FALSE);
+		}
+	}
 
-	/* IPv6 */
-	g_clear_object (&priv->ext_ip6_config);
-	g_clear_object (&priv->ext_ip6_config_captured);
-	priv->ext_ip6_config_captured = nm_ip6_config_capture (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);
-
-		/* 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->ac_ip6_config)
-			nm_ip6_config_intersect (priv->ac_ip6_config, priv->ext_ip6_config);
-		if (priv->dhcp6.ip6_config)
-			nm_ip6_config_intersect (priv->dhcp6.ip6_config, priv->ext_ip6_config);
-		if (priv->wwan_ip6_config)
-			nm_ip6_config_intersect (priv->wwan_ip6_config, priv->ext_ip6_config);
-		g_slist_foreach (priv->vpn6_configs, _ip6_config_intersect, priv->ext_ip6_config);
-
-		/* Remove parts from ext_ip6_config to only contain the information that
-		 * was configured externally -- we already have the same configuration from
-		 * internal origins. */
-		if (priv->con_ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->con_ip6_config);
-		if (priv->ac_ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config);
-		if (priv->dhcp6.ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6.ip6_config);
-		if (priv->wwan_ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_ip6_config);
-		g_slist_foreach (priv->vpn6_configs, _ip6_config_subtract, priv->ext_ip6_config);
-
-		ip6_config_merge_and_apply (self, FALSE);
-	}
-
-	if (   priv->linklocal6_timeout_id
+	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
@@ -10885,8 +11102,8 @@ update_ip6_config (NMDevice *self, gboolean initial)
 void
 nm_device_capture_initial_config (NMDevice *self)
 {
-	update_ip4_config (self, TRUE);
-	update_ip6_config (self, TRUE);
+	update_ip_config (self, AF_INET,  TRUE);
+	update_ip_config (self, AF_INET6, TRUE);
 }
 
 static gboolean
@@ -10906,10 +11123,27 @@ queued_ip4_config_change (gpointer user_data)
 		return TRUE;
 
 	priv->queued_ip4_config_id = 0;
-	update_ip4_config (self, FALSE);
+
+	/* 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);
 
+	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 FALSE;
 }
 
@@ -10932,7 +11166,19 @@ queued_ip6_config_change (gpointer user_data)
 		return TRUE;
 
 	priv->queued_ip6_config_id = 0;
-	update_ip6_config (self, FALSE);
+
+	/* 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 (priv->state < NM_DEVICE_STATE_DEACTIVATING
 	    && nm_platform_link_get (nm_device_get_platform (self), priv->ifindex)) {
@@ -10977,6 +11223,8 @@ queued_ip6_config_change (gpointer user_data)
 			g_clear_object (&priv->dad6_ip6_config);
 			_set_ip_state (self, AF_INET6, IP_DONE);
 			check_ip_state (self, FALSE);
+			if (priv->rt6_temporary_not_available)
+				nm_device_activate_schedule_ip6_config_result (self);
 		}
 	}
 
@@ -11052,6 +11300,7 @@ NM_UTILS_FLAGS2STR_DEFINE (nm_unmanaged_flags2str, NMUnmanagedFlags,
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_USER_EXPLICIT, "user-explicit"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_BY_DEFAULT, "by-default"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_USER_SETTINGS, "user-settings"),
+	NM_UTILS_FLAGS2STR (NM_UNMANAGED_USER_CONF, "user-conf"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_USER_UDEV, "user-udev"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_EXTERNAL_DOWN, "external-down"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_IS_SLAVE, "is-slave"),
@@ -11144,11 +11393,19 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 
 	if (NM_FLAGS_ANY (mask, NM_UNMANAGED_USER_UDEV)) {
 		/* configuration from udev or nm-config overwrites the by-default flag
-		 * which is based on the device type. */
-		flags &= ~NM_UNMANAGED_BY_DEFAULT;
+		 * which is based on the device type.
+		 * configuration from udev overwrites external-down */
+		flags &= ~(  NM_UNMANAGED_BY_DEFAULT
+		           | NM_UNMANAGED_EXTERNAL_DOWN);
+	}
 
-		/* configuration from udev overwrites external-down */
-		flags &= ~NM_UNMANAGED_EXTERNAL_DOWN;
+	if (NM_FLAGS_ANY (mask, NM_UNMANAGED_USER_CONF)) {
+		/* configuration from NetworkManager.conf overwrites the by-default flag
+		 * which is based on the device type.
+		 * It also overwrites the udev configuration and external-down */
+		flags &= ~(   NM_UNMANAGED_BY_DEFAULT
+		           | NM_UNMANAGED_USER_UDEV
+		           | NM_UNMANAGED_EXTERNAL_DOWN);
 	}
 
 	if (   NM_FLAGS_HAS (mask, NM_UNMANAGED_IS_SLAVE)
@@ -11160,9 +11417,9 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 	if (NM_FLAGS_HAS (mask, NM_UNMANAGED_USER_EXPLICIT)) {
 		/* if the device is managed by user-decision, certain other flags
 		 * are ignored. */
-
 		flags &= ~(  NM_UNMANAGED_BY_DEFAULT
 		           | NM_UNMANAGED_USER_UDEV
+		           | NM_UNMANAGED_USER_CONF
 		           | NM_UNMANAGED_EXTERNAL_DOWN);
 	}
 
@@ -11469,6 +11726,35 @@ nm_device_set_unmanaged_by_user_udev (NMDevice *self)
 }
 
 void
+nm_device_set_unmanaged_by_user_conf (NMDevice *self)
+{
+	gboolean value;
+	NMUnmanFlagOp set_op;
+
+	value = nm_config_data_get_device_config_boolean (NM_CONFIG_GET_DATA,
+	                                                  NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
+	                                                  self,
+	                                                  -1,
+	                                                  TRUE);
+	switch (value) {
+	case TRUE:
+		set_op = NM_UNMAN_FLAG_OP_SET_MANAGED;
+		break;
+	case FALSE:
+		set_op = NM_UNMAN_FLAG_OP_SET_UNMANAGED;
+		break;
+	default:
+		set_op = NM_UNMAN_FLAG_OP_FORGET;
+		break;
+	}
+
+	nm_device_set_unmanaged_by_flags (self,
+	                                  NM_UNMANAGED_USER_CONF,
+	                                  set_op,
+	                                  NM_DEVICE_STATE_REASON_USER_REQUESTED);
+}
+
+void
 nm_device_set_unmanaged_by_quitting (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -11489,14 +11775,6 @@ nm_device_set_unmanaged_by_quitting (NMDevice *self)
 /*****************************************************************************/
 
 void
-nm_device_set_dhcp_timeout (NMDevice *self, guint32 timeout)
-{
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	NM_DEVICE_GET_PRIVATE (self)->dhcp_timeout = timeout;
-}
-
-void
 nm_device_set_dhcp_anycast_address (NMDevice *self, const char *addr)
 {
 	NMDevicePrivate *priv;
@@ -11766,6 +12044,12 @@ check_connection_available (NMDevice *self,
 		return TRUE;
 	}
 
+	/* master types are always available even without carrier.
+	 * Making connection non-available would un-enslave slaves which
+	 * is not desired. */
+	if (nm_device_is_master (self))
+		return TRUE;
+
 	return FALSE;
 }
 
@@ -12030,7 +12314,8 @@ nm_device_has_pending_action (NMDevice *self)
 	if (priv->pending_actions)
 		return TRUE;
 
-	if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
+	if (   nm_device_is_real (self)
+	    && nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 		/* as long as the platform link is not yet initialized, we have a pending
 		 * action. */
 		return TRUE;
@@ -12091,26 +12376,22 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
-		_update_default_route (self, AF_INET,  FALSE, FALSE);
-		_update_default_route (self, AF_INET6, FALSE, FALSE);
-	} else {
-		_update_default_route (self, AF_INET,  priv->default_route.v4_has, TRUE);
-		_update_default_route (self, AF_INET6, priv->default_route.v6_has, TRUE);
-	}
-	_update_default_route (self, AF_INET,  FALSE, TRUE);
-	_update_default_route (self, AF_INET6, FALSE, TRUE);
-
 	priv->v4_commit_first_time = TRUE;
 	priv->v6_commit_first_time = TRUE;
 
+	priv->v4_route_table_initalized = FALSE;
+	priv->v6_route_table_initalized = FALSE;
+
+	priv->default_route_metric_penalty_ip4_has = FALSE;
+	priv->default_route_metric_penalty_ip6_has = FALSE;
+
 	priv->linklocal6_dad_counter = 0;
 
 	/* 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, 0, TRUE, TRUE);
-	nm_device_set_ip6_config (self, NULL, TRUE, TRUE);
+	nm_device_set_ip4_config (self, NULL, TRUE, NULL);
+	nm_device_set_ip6_config (self, NULL, TRUE);
 	g_clear_object (&priv->proxy_config);
 	g_clear_object (&priv->con_ip4_config);
 	g_clear_object (&priv->dev_ip4_config);
@@ -12125,6 +12406,9 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	g_clear_object (&priv->ip6_config);
 	g_clear_object (&priv->dad6_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_slist_free_full (priv->vpn4_configs, g_object_unref);
 	priv->vpn4_configs = NULL;
 	g_slist_free_full (priv->vpn6_configs, g_object_unref);
@@ -12198,18 +12482,24 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	if (NM_DEVICE_GET_CLASS (self)->deactivate)
 		NM_DEVICE_GET_CLASS (self)->deactivate (self);
 
+	ifindex = nm_device_get_ip_ifindex (self);
+
 	if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
 		/* master: release slaves */
 		nm_device_master_release_slaves (self);
 
 		/* Take out any entries in the routing table and any IP address the device had. */
-		ifindex = nm_device_get_ip_ifindex (self);
 		if (ifindex > 0) {
-			nm_route_manager_route_flush (nm_netns_get_route_manager (priv->netns), ifindex);
-			nm_platform_address_flush (nm_device_get_platform (self), ifindex);
+			NMPlatform *platform = nm_device_get_platform (self);
+
+			nm_platform_ip_route_flush (platform, AF_UNSPEC, ifindex);
+			nm_platform_ip_address_flush (platform, AF_UNSPEC, ifindex);
 		}
 	}
 
+	if (ifindex > 0)
+		nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self), ifindex, NULL);
+
 	/* slave: mark no longer enslaved */
 	if (   priv->master
 	    && nm_platform_link_get_master (nm_device_get_platform (self), priv->ifindex) <= 0)
@@ -12241,8 +12531,10 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 		    && cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
 			_LOGT (LOGD_DEVICE, "mtu: reset device-mtu: %u, ipv6-mtu: %u, ifindex: %d",
 			       (guint) priv->mtu_initial, (guint) priv->ip6_mtu_initial, ifindex);
-			if (priv->mtu_initial)
+			if (priv->mtu_initial) {
 				nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, priv->mtu_initial);
+				priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+			}
 			if (priv->ip6_mtu_initial) {
 				char sbuf[64];
 
@@ -12267,15 +12559,13 @@ static char *
 find_dhcp4_address (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint i, n;
+	const NMPlatformIP4Address *a;
+	NMDedupMultiIter ipconf_iter;
 
 	if (!priv->ip4_config)
 		return NULL;
 
-	n = nm_ip4_config_get_num_addresses (priv->ip4_config);
-	for (i = 0; i < n; i++) {
-		const NMPlatformIP4Address *a = nm_ip4_config_get_address (priv->ip4_config, i);
-
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip4_config, &a) {
 		if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP)
 			return g_strdup (nm_utils_inet4_ntop (a->address, NULL));
 	}
@@ -12346,7 +12636,7 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		g_assert (s_ip4);
 
 		g_ptr_array_add (argv, g_strdup ("--priority4"));
-		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_ip4_route_metric (self)));
+		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_route_metric (self, AF_INET)));
 
 		g_ptr_array_add (argv, g_strdup ("--dhcp4"));
 		g_ptr_array_add (argv, g_strdup (dhcp4_address));
@@ -12388,7 +12678,7 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		g_assert (s_ip6);
 
 		g_ptr_array_add (argv, g_strdup ("--priority6"));
-		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_ip6_route_metric (self)));
+		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_route_metric (self, AF_INET6)));
 
 		g_ptr_array_add (argv, g_strdup ("--slaac"));
 
@@ -12555,7 +12845,7 @@ _set_state_full (NMDevice *self,
 	if (   (priv->state == state)
 	    && (   state != NM_DEVICE_STATE_UNAVAILABLE
 	        || !priv->firmware_missing)) {
-		_LOGD (LOGD_DEVICE, "state change: %s -> %s (reason '%s', internal state '%s'%s)",
+		_LOGD (LOGD_DEVICE, "state change: %s -> %s (reason '%s', sys-iface-state: '%s'%s)",
 		       nm_device_state_to_str (old_state),
 		       nm_device_state_to_str (state),
 		       reason_to_string (reason),
@@ -12564,7 +12854,7 @@ _set_state_full (NMDevice *self,
 		return;
 	}
 
-	_LOGI (LOGD_DEVICE, "state change: %s -> %s (reason '%s', internal state '%s')",
+	_LOGI (LOGD_DEVICE, "state change: %s -> %s (reason '%s', sys-iface-state: '%s')",
 	       nm_device_state_to_str (old_state),
 	       nm_device_state_to_str (state),
 	       reason_to_string (reason),
@@ -12593,6 +12883,10 @@ _set_state_full (NMDevice *self,
 	                        NM_DEVICE_SYS_IFACE_STATE_ASSUME))
 		nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
 
+	if (   state <= NM_DEVICE_STATE_DISCONNECTED
+	    || state >= NM_DEVICE_STATE_ACTIVATED)
+		priv->auth_retries = NM_DEVICE_AUTH_RETRIES_UNSET;
+
 	if (state > NM_DEVICE_STATE_DISCONNECTED)
 		nm_device_assume_state_reset (self);
 
@@ -12752,15 +13046,11 @@ _set_state_full (NMDevice *self,
 		if (   priv->queued_act_request
 		    && !priv->queued_act_request_is_waiting_for_carrier) {
 			NMActRequest *queued_req;
-			gboolean success;
 
 			queued_req = priv->queued_act_request;
 			priv->queued_act_request = NULL;
-			success = _device_activate (self, queued_req);
+			_device_activate (self, queued_req);
 			g_object_unref (queued_req);
-			if (success)
-				break;
-			/* fall through */
 		}
 		break;
 	case NM_DEVICE_STATE_ACTIVATED:
@@ -13380,7 +13670,7 @@ handle_fail:
 		_NMLOG (plerr == NM_PLATFORM_ERROR_NOT_FOUND ? LOGL_DEBUG : LOGL_WARN,
 		        LOGD_DEVICE, "set-hw-addr: failed to %s MAC address to %s (%s) (%s)",
 		        operation, addr, detail,
-		        nm_platform_error_to_string (plerr));
+		        nm_platform_error_to_string_a (plerr));
 	}
 
 	if (was_up) {
@@ -13683,6 +13973,12 @@ nm_device_get_initial_hw_address (NMDevice *self)
 gboolean
 nm_device_spec_match_list (NMDevice *self, const GSList *specs)
 {
+	return nm_device_spec_match_list_full (self, specs, FALSE);
+}
+
+int
+nm_device_spec_match_list_full (NMDevice *self, const GSList *specs, int no_match_value)
+{
 	NMDeviceClass *klass;
 	NMMatchSpecMatchType m;
 
@@ -13697,7 +13993,17 @@ nm_device_spec_match_list (NMDevice *self, const GSList *specs)
 	                          nm_device_get_driver_version (self),
 	                          nm_device_get_permanent_hw_address (self),
 	                          klass->get_s390_subchannels ? klass->get_s390_subchannels (self) : NULL);
-	return m == NM_MATCH_SPEC_MATCH;
+
+	switch (m) {
+	case NM_MATCH_SPEC_MATCH:
+		return TRUE;
+	case NM_MATCH_SPEC_NEG_MATCH:
+		return FALSE;
+	case NM_MATCH_SPEC_NO_MATCH:
+		return no_match_value;
+	}
+	nm_assert_not_reached ();
+	return no_match_value;
 }
 
 guint
@@ -13727,6 +14033,54 @@ nm_device_get_supplicant_timeout (NMDevice *self)
 	                                     SUPPLICANT_DEFAULT_TIMEOUT);
 }
 
+gboolean
+nm_device_auth_retries_try_next (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+	NMSettingConnection *s_con;
+	int auth_retries;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	auth_retries = priv->auth_retries;
+
+	if (G_UNLIKELY (auth_retries == NM_DEVICE_AUTH_RETRIES_UNSET)) {
+		auth_retries = -1;
+
+		s_con = NM_SETTING_CONNECTION (nm_device_get_applied_setting (self, NM_TYPE_SETTING_CONNECTION));
+		if (s_con)
+			auth_retries = nm_setting_connection_get_auth_retries (s_con);
+
+		if (auth_retries == -1) {
+			gs_free char *value = NULL;
+
+			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+			                                               "connection.auth-retries",
+			                                               self);
+			auth_retries = _nm_utils_ascii_str_to_int64 (value, 10, -1, G_MAXINT32, -1);
+		}
+
+		if (auth_retries == 0)
+			auth_retries = NM_DEVICE_AUTH_RETRIES_INFINITY;
+		else if (auth_retries == -1)
+			auth_retries = NM_DEVICE_AUTH_RETRIES_DEFAULT;
+		else
+			nm_assert (auth_retries > 0);
+
+		priv->auth_retries = auth_retries;
+	}
+
+	if (auth_retries == NM_DEVICE_AUTH_RETRIES_INFINITY)
+		return TRUE;
+	if (auth_retries <= 0) {
+		nm_assert (auth_retries == 0);
+		return FALSE;
+	}
+	priv->auth_retries--;
+	return TRUE;
+}
+
 /*****************************************************************************/
 
 static const char *
@@ -13742,7 +14096,7 @@ _activation_func_to_string (ActivationHandleFunc func)
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage3_ip_config_start);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip4_config_timeout);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip6_config_timeout);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip4_config_commit);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip4_config_result);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip6_config_commit);
 	g_return_val_if_reached ("unknown");
 }
@@ -13758,23 +14112,22 @@ nm_device_init (NMDevice *self)
 
 	self->_priv = priv;
 
+	c_list_init (&priv->slaves);
+
 	priv->netns = g_object_ref (NM_NETNS_GET);
 
+	priv->auth_retries = NM_DEVICE_AUTH_RETRIES_UNSET;
 	priv->type = NM_DEVICE_TYPE_UNKNOWN;
 	priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
 	priv->state = NM_DEVICE_STATE_UNMANAGED;
 	priv->state_reason = NM_DEVICE_STATE_REASON_NONE;
-	priv->dhcp_timeout = 0;
 	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->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+	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;
 
-	priv->default_route.v4_is_assumed = TRUE;
-	priv->default_route.v6_is_assumed = TRUE;
-
 	priv->v4_commit_first_time = TRUE;
 	priv->v6_commit_first_time = TRUE;
 }
@@ -13843,9 +14196,6 @@ constructed (GObject *object)
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self);
 
-	g_signal_connect (nm_netns_get_route_manager (priv->netns), NM_ROUTE_MANAGER_IP4_ROUTES_CHANGED,
-	                  G_CALLBACK (ip4_routes_changed_changed_cb), self);
-
 	priv->settings = g_object_ref (NM_SETTINGS_GET);
 	g_assert (priv->settings);
 
@@ -13886,9 +14236,6 @@ 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_signal_handlers_disconnect_by_func (nm_netns_get_route_manager (priv->netns),
-	                                      G_CALLBACK (ip4_routes_changed_changed_cb), self);
-
 	g_slist_free_full (priv->arping.dad_list, (GDestroyNotify) nm_arping_manager_destroy);
 	priv->arping.dad_list = NULL;
 
@@ -13904,7 +14251,7 @@ dispose (GObject *object)
 
 	_cleanup_generic_pre (self, CLEANUP_TYPE_KEEP);
 
-	g_warn_if_fail (priv->slaves == NULL);
+	g_warn_if_fail (c_list_is_empty (&priv->slaves));
 	g_assert (priv->master_ready_id == 0);
 
 	/* Let the kernel manage IPv6LL again */
@@ -14083,10 +14430,6 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->rfkill_type = g_value_get_uint (value);
 		break;
-	case PROP_IS_MASTER:
-		/* construct-only */
-		priv->is_master = g_value_get_boolean (value);
-		break;
 	case PROP_PERM_HW_ADDRESS:
 		/* construct-only */
 		priv->hw_addr_perm = g_value_dup_string (value);
@@ -14221,9 +14564,6 @@ get_property (GObject *object, guint prop_id,
 	case PROP_PHYSICAL_PORT_ID:
 		g_value_set_string (value, priv->physical_port_id);
 		break;
-	case PROP_IS_MASTER:
-		g_value_set_boolean (value, priv->is_master);
-		break;
 	case PROP_MASTER:
 		g_value_set_object (value, nm_device_get_master (self));
 		break;
@@ -14260,13 +14600,15 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_boolean (value, nm_device_is_real (self));
 		break;
 	case PROP_SLAVES: {
-		GSList *slave_iter;
+		CList *slave_iter;
 		char **slave_list;
-		guint i;
+		gsize i, n;
 
-		slave_list = g_new (char *, g_slist_length (priv->slaves) + 1);
-		for (slave_iter = priv->slaves, i = 0; slave_iter; slave_iter = slave_iter->next) {
-			SlaveInfo *info = slave_iter->data;
+		n = c_list_length (&priv->slaves);
+		slave_list = g_new (char *, n + 1);
+		i = 0;
+		c_list_for_each (slave_iter, &priv->slaves) {
+			SlaveInfo *info = c_list_entry (slave_iter, SlaveInfo, lst_slave);
 			const char *path;
 
 			if (!NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
@@ -14275,6 +14617,7 @@ get_property (GObject *object, guint prop_id,
 			if (path)
 				slave_list[i++] = g_strdup (path);
 		}
+		nm_assert (i <= n);
 		slave_list[i] = NULL;
 		g_value_take_boxed (value, slave_list);
 		break;
@@ -14323,7 +14666,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
 	klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
-	klass->have_any_ready_slaves = have_any_ready_slaves;
 
 	klass->get_type_description = get_type_description;
 	klass->get_autoconnect_allowed = get_autoconnect_allowed;
@@ -14484,11 +14826,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	                         NULL,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_IS_MASTER] =
-	    g_param_spec_boolean (NM_DEVICE_IS_MASTER, "", "",
-	                          FALSE,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_MASTER] =
 	    g_param_spec_object (NM_DEVICE_MASTER, "", "",
 	                         NM_TYPE_DEVICE,