summary refs log tree commit diff
path: root/src/devices
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
committerMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
commit90e8691111889a7b5f3c812f5a41f15a8a058913 (patch)
treef101a879eca27c34a9bfa5f3da52266b22539a36 /src/devices
parentbdb6eeb0670658255c2a4c3c501c0a27fa8cfe55 (diff)
New upstream version 1.9.90 upstream/1.9.90
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/adsl/nm-device-adsl.c17
-rw-r--r--src/devices/bluetooth/nm-bluez-common.h26
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c58
-rw-r--r--src/devices/bluetooth/nm-bluez-device.h3
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c37
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c131
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.h6
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c154
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c4
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c299
-rw-r--r--src/devices/bluetooth/nm-device-bt.c96
-rw-r--r--src/devices/bluetooth/nm-device-bt.h6
-rw-r--r--src/devices/nm-device-bond.c25
-rw-r--r--src/devices/nm-device-bridge.c123
-rw-r--r--src/devices/nm-device-bridge.h2
-rw-r--r--src/devices/nm-device-dummy.c2
-rw-r--r--src/devices/nm-device-ethernet.c129
-rw-r--r--src/devices/nm-device-factory.c149
-rw-r--r--src/devices/nm-device-factory.h28
-rw-r--r--src/devices/nm-device-infiniband.c10
-rw-r--r--src/devices/nm-device-ip-tunnel.c60
-rw-r--r--src/devices/nm-device-macsec.c39
-rw-r--r--src/devices/nm-device-macvlan.c73
-rw-r--r--src/devices/nm-device-ppp.c350
-rw-r--r--src/devices/nm-device-ppp.h30
-rw-r--r--src/devices/nm-device-private.h14
-rw-r--r--src/devices/nm-device-tun.c2
-rw-r--r--src/devices/nm-device-veth.c6
-rw-r--r--src/devices/nm-device-vlan.c105
-rw-r--r--src/devices/nm-device-vxlan.c42
-rw-r--r--src/devices/nm-device.c3107
-rw-r--r--src/devices/nm-device.h54
-rw-r--r--src/devices/nm-lldp-listener.c16
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.c156
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.h35
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c191
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.h35
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c201
-rw-r--r--src/devices/ovs/nm-device-ovs-port.h35
-rw-r--r--src/devices/ovs/nm-ovs-factory.c195
-rw-r--r--src/devices/ovs/nm-ovsdb.c1607
-rw-r--r--src/devices/ovs/nm-ovsdb.h50
-rw-r--r--src/devices/team/nm-device-team.c65
-rw-r--r--src/devices/tests/test-arping.c7
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c23
-rw-r--r--src/devices/wifi/nm-device-wifi.c516
-rw-r--r--src/devices/wifi/nm-device-wifi.h2
-rw-r--r--src/devices/wifi/nm-wifi-ap.c368
-rw-r--r--src/devices/wifi/nm-wifi-ap.h3
-rw-r--r--src/devices/wifi/nm-wifi-utils.c4
-rw-r--r--src/devices/wifi/tests/test-general.c32
-rw-r--r--src/devices/wwan/libnm-wwan.ver4
-rw-r--r--src/devices/wwan/nm-device-modem.c50
-rw-r--r--src/devices/wwan/nm-modem-broadband.c103
-rw-r--r--src/devices/wwan/nm-modem-manager.c813
-rw-r--r--src/devices/wwan/nm-modem-manager.h13
-rw-r--r--src/devices/wwan/nm-modem-ofono.c578
-rw-r--r--src/devices/wwan/nm-modem.c300
-rw-r--r--src/devices/wwan/nm-modem.h21
-rw-r--r--src/devices/wwan/nm-wwan-factory.c4
60 files changed, 7607 insertions, 3007 deletions
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index fe622bdf..e9bd41ae 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -227,7 +227,7 @@ br2684_assign_vcc (NMDeviceAdsl *self, NMSettingAdsl *s_adsl)
 	return TRUE;
 
 error:
-	close (priv->brfd);
+	nm_close (priv->brfd);
 	priv->brfd = -1;
 	return FALSE;
 }
@@ -474,6 +474,15 @@ act_stage3_ip4_config_start (NMDevice *device,
 	}
 
 	priv->ppp_manager = nm_ppp_manager_create (ppp_iface, &err);
+
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     nm_device_get_route_table (device, AF_INET, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET),
+		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET6));
+	}
+
 	if (   !priv->ppp_manager
 	    || !nm_ppp_manager_start (priv->ppp_manager, req,
 	                              nm_setting_adsl_get_username (s_adsl),
@@ -510,10 +519,8 @@ adsl_cleanup (NMDeviceAdsl *self)
 
 	g_signal_handlers_disconnect_by_func (nm_device_get_platform (NM_DEVICE (self)), G_CALLBACK (link_changed_cb), self);
 
-	if (priv->brfd >= 0) {
-		close (priv->brfd);
-		priv->brfd = -1;
-	}
+	nm_close (priv->brfd);
+	priv->brfd = -1;
 
 	nm_clear_g_source (&priv->nas_update_id);
 
diff --git a/src/devices/bluetooth/nm-bluez-common.h b/src/devices/bluetooth/nm-bluez-common.h
index 6e97c3f5..d72bea81 100644
--- a/src/devices/bluetooth/nm-bluez-common.h
+++ b/src/devices/bluetooth/nm-bluez-common.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_BLUEZ_COMMON_H__
@@ -24,21 +24,23 @@
 #define BLUETOOTH_CONNECT_DUN "dun"
 #define BLUETOOTH_CONNECT_NAP "nap"
 
-#define BLUEZ_SERVICE           "org.bluez"
+#define NM_BLUEZ_SERVICE           "org.bluez"
 
-#define BLUEZ_MANAGER_PATH      "/"
-#define OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager"
+#define NM_BLUEZ_MANAGER_PATH      "/"
+#define NM_OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager"
 
-#define BLUEZ5_ADAPTER_INTERFACE "org.bluez.Adapter1"
-#define BLUEZ5_DEVICE_INTERFACE  "org.bluez.Device1"
-#define BLUEZ5_NETWORK_INTERFACE "org.bluez.Network1"
+#define NM_BLUEZ5_ADAPTER_INTERFACE "org.bluez.Adapter1"
+#define NM_BLUEZ5_DEVICE_INTERFACE  "org.bluez.Device1"
+#define NM_BLUEZ5_NETWORK_INTERFACE "org.bluez.Network1"
+#define NM_BLUEZ5_NETWORK_SERVER_INTERFACE "org.bluez.NetworkServer1"
 
-#define BLUEZ4_MANAGER_INTERFACE "org.bluez.Manager"
-#define BLUEZ4_ADAPTER_INTERFACE "org.bluez.Adapter"
-#define BLUEZ4_DEVICE_INTERFACE  "org.bluez.Device"
-#define BLUEZ4_SERIAL_INTERFACE  "org.bluez.Serial"
-#define BLUEZ4_NETWORK_INTERFACE "org.bluez.Network"
+#define NM_BLUEZ4_MANAGER_INTERFACE "org.bluez.Manager"
+#define NM_BLUEZ4_ADAPTER_INTERFACE "org.bluez.Adapter"
+#define NM_BLUEZ4_DEVICE_INTERFACE  "org.bluez.Device"
+#define NM_BLUEZ4_SERIAL_INTERFACE  "org.bluez.Serial"
+#define NM_BLUEZ4_NETWORK_INTERFACE "org.bluez.Network"
 
 #define NM_BLUEZ_MANAGER_BDADDR_ADDED "bdaddr-added"
+#define NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED "network-server-added"
 
 #endif  /* NM_BLUEZ_COMMON_H */
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index 41ef74ca..182527d9 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -84,6 +84,7 @@ typedef struct {
 	char *name;
 	guint32 capabilities;
 	gboolean connected;
+	gboolean paired;
 
 	char *b4_iface;
 #if WITH_BLUEZ5_DUN
@@ -278,10 +279,11 @@ check_emit_usable (NMBluezDevice *self)
 	/* only expect the supported capabilities set. */
 	nm_assert ((priv->capabilities & ~(NM_BT_CAPABILITY_NAP | NM_BT_CAPABILITY_DUN)) == NM_BT_CAPABILITY_NONE );
 
-	new_usable = (priv->initialized && priv->capabilities && priv->name &&
-	              ((priv->bluez_version == 4) ||
-	               (priv->bluez_version == 5 && priv->adapter5 && priv->adapter_powered) ) &&
-	              priv->dbus_connection && priv->address && priv->adapter_address);
+	new_usable = (   priv->initialized && priv->capabilities
+	              && priv->name && priv->paired
+	              && (   (priv->bluez_version == 4)
+	                  || (priv->bluez_version == 5 && priv->adapter5 && priv->adapter_powered))
+	              && priv->dbus_connection && priv->address && priv->adapter_address);
 
 	if (!new_usable)
 		goto END;
@@ -343,6 +345,10 @@ connection_compatible (NMBluezDevice *self, NMConnection *connection)
 		return FALSE;
 
 	bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
+
+	if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_NAP))
+		return FALSE;
+
 	if (   g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN)
 	    && !(priv->capabilities & NM_BT_CAPABILITY_DUN))
 		return FALSE;
@@ -465,7 +471,7 @@ nm_bluez_device_disconnect (NMBluezDevice *self)
 			if (!priv->b4_iface)
 				goto out;
 			args = g_variant_new ("(s)", priv->b4_iface),
-			dbus_iface = BLUEZ4_SERIAL_INTERFACE;
+			dbus_iface = NM_BLUEZ4_SERIAL_INTERFACE;
 		} else if (priv->bluez_version == 5) {
 #if WITH_BLUEZ5_DUN
 			nm_bluez5_dun_cleanup (priv->b5_dun_context);
@@ -475,16 +481,16 @@ nm_bluez_device_disconnect (NMBluezDevice *self)
 		}
 	} else if (priv->connection_bt_type == NM_BT_CAPABILITY_NAP) {
 		if (priv->bluez_version == 4)
-			dbus_iface = BLUEZ4_NETWORK_INTERFACE;
+			dbus_iface = NM_BLUEZ4_NETWORK_INTERFACE;
 		else if (priv->bluez_version == 5)
-			dbus_iface = BLUEZ5_NETWORK_INTERFACE;
+			dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE;
 		else
 			g_assert_not_reached ();
 	} else
 		g_assert_not_reached ();
 
 	g_dbus_connection_call (priv->dbus_connection,
-	                        BLUEZ_SERVICE,
+	                        NM_BLUEZ_SERVICE,
 	                        priv->path,
 	                        dbus_iface,
 	                        "Disconnect",
@@ -577,13 +583,13 @@ nm_bluez_device_connect_async (NMBluezDevice *self,
 	if (connection_bt_type == NM_BT_CAPABILITY_NAP) {
 		connect_type = BLUETOOTH_CONNECT_NAP;
 		if (priv->bluez_version == 4)
-			dbus_iface = BLUEZ4_NETWORK_INTERFACE;
+			dbus_iface = NM_BLUEZ4_NETWORK_INTERFACE;
 		else if (priv->bluez_version == 5)
-			dbus_iface = BLUEZ5_NETWORK_INTERFACE;
+			dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE;
 	} else if (connection_bt_type == NM_BT_CAPABILITY_DUN) {
 		connect_type = BLUETOOTH_CONNECT_DUN;
 		if (priv->bluez_version == 4)
-			dbus_iface = BLUEZ4_SERIAL_INTERFACE;
+			dbus_iface = NM_BLUEZ4_SERIAL_INTERFACE;
 		else if (priv->bluez_version == 5) {
 #if WITH_BLUEZ5_DUN
 			if (priv->b5_dun_context == NULL)
@@ -602,7 +608,7 @@ nm_bluez_device_connect_async (NMBluezDevice *self,
 		g_assert_not_reached ();
 
 	g_dbus_connection_call (priv->dbus_connection,
-	                        BLUEZ_SERVICE,
+	                        NM_BLUEZ_SERVICE,
 	                        priv->path,
 	                        dbus_iface,
 	                        "Connect",
@@ -795,6 +801,17 @@ _take_variant_property_connected (NMBluezDevice *self, GVariant *v)
 		g_variant_unref (v);
 }
 
+static void
+_take_variant_property_paired (NMBluezDevice *self, GVariant *v)
+{
+	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
+
+	if (VARIANT_IS_OF_TYPE_BOOLEAN (v))
+		priv->paired = g_variant_get_boolean (v);
+
+	if (v)
+		g_variant_unref (v);
+}
 
 static void
 adapter5_on_properties_changed (GDBusProxy *proxy,
@@ -864,6 +881,8 @@ _take_one_variant_property (NMBluezDevice *self, const char *property, GVariant
 			_take_variant_property_address (self, v);
 		else if (!g_strcmp0 (property, "Connected"))
 			_take_variant_property_connected (self, v);
+		else if (!g_strcmp0 (property, "Paired"))
+			_take_variant_property_paired (self, v);
 		else if (!g_strcmp0 (property, "Name"))
 			_take_variant_property_name (self, v);
 		else if (!g_strcmp0 (property, "UUIDs"))
@@ -963,6 +982,7 @@ query_properties (NMBluezDevice *self)
 		g_object_freeze_notify (G_OBJECT (self));
 		_take_variant_property_address   (self, g_dbus_proxy_get_cached_property (priv->proxy, "Address"));
 		_take_variant_property_connected (self, g_dbus_proxy_get_cached_property (priv->proxy, "Connected"));
+		_take_variant_property_paired    (self, g_dbus_proxy_get_cached_property (priv->proxy, "Paired"));
 		_take_variant_property_name      (self, g_dbus_proxy_get_cached_property (priv->proxy, "Name"));
 		_take_variant_property_uuids     (self, g_dbus_proxy_get_cached_property (priv->proxy, "UUIDs"));
 		g_object_thaw_notify (G_OBJECT (self));
@@ -972,9 +992,9 @@ query_properties (NMBluezDevice *self)
 			g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 			                          G_DBUS_PROXY_FLAGS_NONE,
 			                          NULL,
-			                          BLUEZ_SERVICE,
+			                          NM_BLUEZ_SERVICE,
 			                          g_variant_get_string (v, NULL),
-			                          BLUEZ5_ADAPTER_INTERFACE,
+			                          NM_BLUEZ5_ADAPTER_INTERFACE,
 			                          NULL,
 			                          (GAsyncReadyCallback) adapter5_on_acquired,
 			                          g_object_ref (self));
@@ -1134,17 +1154,17 @@ nm_bluez_device_new (const char *path,
 
 	switch (priv->bluez_version) {
 	case 4:
-		interface_name = BLUEZ4_DEVICE_INTERFACE;
+		interface_name = NM_BLUEZ4_DEVICE_INTERFACE;
 		break;
 	case 5:
-		interface_name = BLUEZ5_DEVICE_INTERFACE;
+		interface_name = NM_BLUEZ5_DEVICE_INTERFACE;
 		break;
 	}
 
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_NONE,
 	                          NULL,
-	                          BLUEZ_SERVICE,
+	                          NM_BLUEZ_SERVICE,
 	                          priv->path,
 	                          interface_name,
 	                          NULL,
@@ -1199,7 +1219,7 @@ dispose (GObject *object)
 	if (to_delete) {
 		nm_log_dbg (LOGD_BT, "bluez[%s] removing Bluetooth connection for NAP device: '%s' (%s)", priv->path,
 		            nm_connection_get_id (to_delete), nm_connection_get_uuid (to_delete));
-		nm_settings_connection_delete (NM_SETTINGS_CONNECTION (to_delete), NULL, NULL);
+		nm_settings_connection_delete (NM_SETTINGS_CONNECTION (to_delete), NULL);
 		g_object_unref (to_delete);
 	}
 
@@ -1274,7 +1294,7 @@ nm_bluez_device_class_init (NMBluezDeviceClass *config_class)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[INITIALIZED] = g_signal_new ("initialized",
+	signals[INITIALIZED] = g_signal_new (NM_BLUEZ_DEVICE_INITIALIZED,
 	                                     G_OBJECT_CLASS_TYPE (object_class),
 	                                     G_SIGNAL_RUN_LAST,
 	                                     0,
diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h
index e56d5d24..f8a1872f 100644
--- a/src/devices/bluetooth/nm-bluez-device.h
+++ b/src/devices/bluetooth/nm-bluez-device.h
@@ -39,6 +39,7 @@
 #define NM_BLUEZ_DEVICE_CONNECTED    "connected"
 
 /* Signals */
+#define NM_BLUEZ_DEVICE_INITIALIZED  "initialized"
 #define NM_BLUEZ_DEVICE_REMOVED      "removed"
 
 typedef struct _NMBluezDevice NMBluezDevice;
@@ -61,8 +62,6 @@ const char *nm_bluez_device_get_address (NMBluezDevice *self);
 
 const char *nm_bluez_device_get_name (NMBluezDevice *self);
 
-guint32 nm_bluez_device_get_class (NMBluezDevice *self);
-
 guint32 nm_bluez_device_get_capabilities (NMBluezDevice *self);
 
 gboolean nm_bluez_device_get_connected (NMBluezDevice *self);
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 2f0afa16..96e80245 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -26,6 +26,7 @@
 #include <gmodule.h>
 
 #include "devices/nm-device-factory.h"
+#include "devices/nm-device-bridge.h"
 #include "nm-setting-bluetooth.h"
 #include "settings/nm-settings.h"
 #include "nm-bluez4-manager.h"
@@ -146,7 +147,7 @@ cleanup_checking (NMBluezManager *self, gboolean do_unwatch_name)
 
 
 static void
-manager_bdaddr_added_cb (NMBluez4Manager *bluez_mgr,
+manager_bdaddr_added_cb (GObject *manager,
                          NMBluezDevice *bt_device,
                          const char *bdaddr,
                          const char *name,
@@ -180,6 +181,13 @@ manager_bdaddr_added_cb (NMBluez4Manager *bluez_mgr,
 }
 
 static void
+manager_network_server_added_cb (GObject *manager,
+                                 gpointer user_data)
+{
+	nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (user_data), NULL);
+}
+
+static void
 setup_version_number (NMBluezManager *self, int bluez_version)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
@@ -228,6 +236,10 @@ setup_bluez5 (NMBluezManager *self)
 	                  NM_BLUEZ_MANAGER_BDADDR_ADDED,
 	                  G_CALLBACK (manager_bdaddr_added_cb),
 	                  self);
+	g_signal_connect (manager,
+	                  NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED,
+	                  G_CALLBACK (manager_network_server_added_cb),
+	                  self);
 
 	nm_bluez5_manager_query_devices (manager);
 }
@@ -264,7 +276,7 @@ check_bluez_and_try_setup_final_step (NMBluezManager *self, int bluez_version, c
 		cleanup_checking (self, FALSE);
 		if (!priv->watch_name_id) {
 			priv->watch_name_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
-			                                        BLUEZ_SERVICE,
+			                                        NM_BLUEZ_SERVICE,
 			                                        G_BUS_NAME_WATCHER_FLAGS_NONE,
 			                                        watch_name_on_appeared,
 			                                        NULL,
@@ -317,7 +329,7 @@ check_bluez_and_try_setup_do_introspect (GObject *source_object,
 	/* might not be the best approach to detect the version, but it's good enough in practice. */
 	if (strstr (xml_data, "org.freedesktop.DBus.ObjectManager"))
 		bluez_version = 5;
-	else if (strstr (xml_data, BLUEZ4_MANAGER_INTERFACE))
+	else if (strstr (xml_data, NM_BLUEZ4_MANAGER_INTERFACE))
 		bluez_version = 4;
 	else
 		reason = "unexpected introspect result";
@@ -380,7 +392,7 @@ check_bluez_and_try_setup (NMBluezManager *self)
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 	                          NULL,
-	                          BLUEZ_SERVICE,
+	                          NM_BLUEZ_SERVICE,
 	                          "/",
 	                          DBUS_INTERFACE_INTROSPECTABLE,
 	                          priv->async_cancellable,
@@ -406,6 +418,20 @@ create_device (NMDeviceFactory *factory,
 	return NULL;
 }
 
+static gboolean
+match_connection (NMDeviceFactory *factory,
+                  NMConnection *connection)
+{
+	const char *type = nm_connection_get_connection_type (connection);
+
+	nm_assert (nm_streq (type, NM_SETTING_BLUETOOTH_SETTING_NAME));
+
+	if (_nm_connection_get_setting_bluetooth_for_nap (connection))
+		return FALSE;    /* handled by the bridge factory */
+
+	return TRUE;
+}
+
 /*****************************************************************************/
 
 static void
@@ -427,7 +453,7 @@ dispose (GObject *object)
 		g_clear_object (&priv->manager4);
 	}
 	if (priv->manager5) {
-		g_signal_handlers_disconnect_by_func (priv->manager5, manager_bdaddr_added_cb, self);
+		g_signal_handlers_disconnect_by_data (priv->manager5, self);
 		g_clear_object (&priv->manager5);
 	}
 
@@ -450,5 +476,6 @@ nm_bluez_manager_class_init (NMBluezManagerClass *klass)
 
 	factory_class->get_supported_types = get_supported_types;
 	factory_class->create_device = create_device;
+	factory_class->match_connection = match_connection;
 	factory_class->start = start;
 }
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index c0c1be30..0f19f998 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include "nm-dbus-interface.h"
+#include "nm-utils/nm-hash-utils.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
 #include "nm-core-internal.h"
@@ -49,6 +50,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	char *path;
 	GDBusProxy *proxy;
+	GCancellable *proxy_cancellable;
 	gboolean initialized;
 
 	char *address;
@@ -73,6 +75,11 @@ G_DEFINE_TYPE (NMBluez4Adapter, nm_bluez4_adapter, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_BT
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez4-adapter", __VA_ARGS__)
+
+/*****************************************************************************/
+
 static void device_do_remove (NMBluez4Adapter *self, NMBluezDevice *device);
 
 /*****************************************************************************/
@@ -119,8 +126,8 @@ nm_bluez4_adapter_get_devices (NMBluez4Adapter *self)
 static void
 emit_device_removed (NMBluez4Adapter *self, NMBluezDevice *device)
 {
-	nm_log_dbg (LOGD_BT, "(%s): bluez device now unusable",
-	            nm_bluez_device_get_path (device));
+	_LOGD ("(%s): bluez device now unusable",
+	       nm_bluez_device_get_path (device));
 	g_signal_emit (self, signals[DEVICE_REMOVED], 0, device);
 }
 
@@ -130,9 +137,9 @@ device_usable (NMBluezDevice *device, GParamSpec *pspec, gpointer user_data)
 	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
 
 	if (nm_bluez_device_get_usable (device)) {
-		nm_log_dbg (LOGD_BT, "(%s): bluez device now usable (device address is %s)",
-		            nm_bluez_device_get_path (device),
-		            nm_bluez_device_get_address (device));
+		_LOGD ("(%s): bluez device now usable (device address is %s)",
+		       nm_bluez_device_get_path (device),
+		       nm_bluez_device_get_address (device));
 		g_signal_emit (self, signals[DEVICE_ADDED], 0, device);
 	} else
 		emit_device_removed (self, device);
@@ -143,9 +150,9 @@ device_initialized (NMBluezDevice *device, gboolean success, gpointer user_data)
 {
 	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device %s",
-	            nm_bluez_device_get_path (device),
-	            success ? "initialized" : "failed to initialize");
+	_LOGD ("(%s): bluez device %s",
+	       nm_bluez_device_get_path (device),
+	       success ? "initialized" : "failed to initialize");
 	if (!success)
 		device_do_remove (self, device);
 }
@@ -174,11 +181,11 @@ device_created (GDBusProxy *proxy, const char *path, gpointer user_data)
 	NMBluezDevice *device;
 
 	device = nm_bluez_device_new (path, priv->address, priv->settings, 4);
-	g_signal_connect (device, "initialized", G_CALLBACK (device_initialized), self);
-	g_signal_connect (device, "notify::usable", G_CALLBACK (device_usable), self);
+	g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self);
+	g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self);
 	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
 
-	nm_log_dbg (LOGD_BT, "(%s): new bluez device found", path);
+	_LOGD ("(%s): new bluez device found", path);
 }
 
 static void
@@ -188,7 +195,7 @@ device_removed (GDBusProxy *proxy, const char *path, gpointer user_data)
 	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 	NMBluezDevice *device;
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device removed", path);
+	_LOGD ("(%s): bluez device removed", path);
 
 	device = g_hash_table_lookup (priv->devices, path);
 	if (device)
@@ -198,19 +205,28 @@ device_removed (GDBusProxy *proxy, const char *path, gpointer user_data)
 static void
 get_properties_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
 {
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-	GError *err = NULL;
+	NMBluez4Adapter *self;
+	NMBluez4AdapterPrivate *priv;
+	gs_free_error GError *error = NULL;
 	GVariant *ret, *properties;
 	char **devices;
 	int i;
 
 	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(a{sv})"), &err);
+	                                  G_VARIANT_TYPE ("(a{sv})"), &error);
+
+	if (   !ret
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_BLUEZ4_ADAPTER (user_data);
+	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->proxy_cancellable);
+
 	if (!ret) {
-		g_dbus_error_strip_remote_error (err);
-		nm_log_warn (LOGD_BT, "bluez error getting adapter properties: %s", err->message);
-		g_error_free (err);
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("bluez error getting adapter properties: %s", error->message);
 		goto done;
 	}
 
@@ -233,15 +249,43 @@ done:
 }
 
 static void
-query_properties (NMBluez4Adapter *self)
+_proxy_new_cb (GObject *source_object,
+               GAsyncResult *result,
+               gpointer user_data)
 {
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
+	NMBluez4Adapter *self;
+	NMBluez4AdapterPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("bluez error creating D-Bus proxy: %s", error->message);
+		g_clear_object (&priv->proxy_cancellable);
+		g_signal_emit (self, signals[INITIALIZED], 0, priv->initialized);
+		return;
+	}
+
+	priv->proxy = proxy;
+
+	_nm_dbus_signal_connect (priv->proxy, "DeviceCreated", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (device_created), self);
+	_nm_dbus_signal_connect (priv->proxy, "DeviceRemoved", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (device_removed), self);
 
 	g_dbus_proxy_call (priv->proxy, "GetProperties",
 	                   NULL,
 	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   NULL,
-	                   get_properties_cb, self);
+	                   priv->proxy_cancellable,
+	                   get_properties_cb,
+	                   self);
 }
 
 /*****************************************************************************/
@@ -297,7 +341,7 @@ nm_bluez4_adapter_init (NMBluez4Adapter *self)
 {
 	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 
-	priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal,
+	priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                       NULL, NULL);
 }
 
@@ -316,19 +360,17 @@ nm_bluez4_adapter_new (const char *path, NMSettings *settings)
 
 	priv->settings = g_object_ref (settings);
 
-	priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-	                                             G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                                             NULL,
-	                                             BLUEZ_SERVICE,
-	                                             priv->path,
-	                                             BLUEZ4_ADAPTER_INTERFACE,
-	                                             NULL, NULL);
-	_nm_dbus_signal_connect (priv->proxy, "DeviceCreated", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (device_created), self);
-	_nm_dbus_signal_connect (priv->proxy, "DeviceRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (device_removed), self);
+	priv->proxy_cancellable = g_cancellable_new ();
 
-	query_properties (self);
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+	                          NULL,
+	                          NM_BLUEZ_SERVICE,
+	                          priv->path,
+	                          NM_BLUEZ4_ADAPTER_INTERFACE,
+	                          priv->proxy_cancellable,
+	                          _proxy_new_cb,
+	                          self);
 	return self;
 }
 
@@ -339,21 +381,28 @@ dispose (GObject *object)
 	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 	NMBluezDevice *device;
 
+	nm_clear_g_cancellable (&priv->proxy_cancellable);
+
 	while ((device = g_hash_table_find (priv->devices, _find_all, NULL)))
 		device_do_remove (self, device);
 
+	if (priv->proxy) {
+		g_signal_handlers_disconnect_by_data (priv->proxy, self);
+		g_clear_object (&priv->proxy);
+	}
+
 	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->dispose (object);
 }
 
 static void
 finalize (GObject *object)
 {
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE ((NMBluez4Adapter *) object);
+	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (object);
+	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 
 	g_hash_table_destroy (priv->devices);
 	g_free (priv->address);
 	g_free (priv->path);
-	g_object_unref (priv->proxy);
 
 	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->finalize (object);
 
@@ -384,7 +433,7 @@ nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[INITIALIZED] = g_signal_new ("initialized",
+	signals[INITIALIZED] = g_signal_new (NM_BLUEZ4_ADAPTER_INITIALIZED,
 	                                     G_OBJECT_CLASS_TYPE (object_class),
 	                                     G_SIGNAL_RUN_LAST,
 	                                     0,
@@ -392,7 +441,7 @@ nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class)
 	                                     g_cclosure_marshal_VOID__BOOLEAN,
 	                                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
 
-	signals[DEVICE_ADDED] = g_signal_new ("device-added",
+	signals[DEVICE_ADDED] = g_signal_new (NM_BLUEZ4_ADAPTER_DEVICE_ADDED,
 	                                      G_OBJECT_CLASS_TYPE (object_class),
 	                                      G_SIGNAL_RUN_LAST,
 	                                      0,
@@ -400,7 +449,7 @@ nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class)
 	                                      g_cclosure_marshal_VOID__OBJECT,
 	                                      G_TYPE_NONE, 1, G_TYPE_OBJECT);
 
-	signals[DEVICE_REMOVED] = g_signal_new ("device-removed",
+	signals[DEVICE_REMOVED] = g_signal_new (NM_BLUEZ4_ADAPTER_DEVICE_REMOVED,
 	                                        G_OBJECT_CLASS_TYPE (object_class),
 	                                        G_SIGNAL_RUN_LAST,
 	                                        0,
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.h b/src/devices/bluetooth/nm-bluez4-adapter.h
index e240ec2c..0aa4ff91 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.h
+++ b/src/devices/bluetooth/nm-bluez4-adapter.h
@@ -30,9 +30,15 @@
 #define NM_IS_BLUEZ4_ADAPTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ4_ADAPTER))
 #define NM_BLUEZ4_ADAPTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterClass))
 
+/* Properties */
 #define NM_BLUEZ4_ADAPTER_PATH    "path"
 #define NM_BLUEZ4_ADAPTER_ADDRESS "address"
 
+/* Signals */
+#define NM_BLUEZ4_ADAPTER_INITIALIZED      "initialized"
+#define NM_BLUEZ4_ADAPTER_DEVICE_ADDED     "device-added"
+#define NM_BLUEZ4_ADAPTER_DEVICE_REMOVED   "device-removed"
+
 typedef struct _NMBluez4Adapter NMBluez4Adapter;
 typedef struct _NMBluez4AdapterClass NMBluez4AdapterClass;
 
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
index a9079a2f..1fe02f18 100644
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ b/src/devices/bluetooth/nm-bluez4-manager.c
@@ -47,6 +47,7 @@ typedef struct {
 	NMSettings *settings;
 
 	GDBusProxy *proxy;
+	GCancellable *proxy_cancellable;
 
 	NMBluez4Adapter *adapter;
 } NMBluez4ManagerPrivate;
@@ -66,6 +67,11 @@ G_DEFINE_TYPE (NMBluez4Manager, nm_bluez4_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_BT
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez4-manager", __VA_ARGS__)
+
+/*****************************************************************************/
+
 static void
 emit_bdaddr_added (NMBluez4Manager *self, NMBluezDevice *device)
 {
@@ -119,8 +125,10 @@ adapter_initialized (NMBluez4Adapter *adapter, gboolean success, gpointer user_d
 			emit_bdaddr_added (self, NM_BLUEZ_DEVICE (iter->data));
 		g_slist_free (devices);
 
-		g_signal_connect (adapter, "device-added", G_CALLBACK (device_added), self);
-		g_signal_connect (adapter, "device-removed", G_CALLBACK (device_removed), self);
+		g_signal_connect (adapter, NM_BLUEZ4_ADAPTER_DEVICE_ADDED,
+		                  G_CALLBACK (device_added), self);
+		g_signal_connect (adapter, NM_BLUEZ4_ADAPTER_DEVICE_REMOVED,
+		                  G_CALLBACK (device_removed), self);
 	} else {
 		g_object_unref (priv->adapter);
 		priv->adapter = NULL;
@@ -169,49 +177,70 @@ default_adapter_changed (GDBusProxy *proxy, const char *path, NMBluez4Manager *s
 	/* Add the new default adapter */
 	if (path) {
 		priv->adapter = nm_bluez4_adapter_new (path, priv->settings);
-		g_signal_connect (priv->adapter, "initialized", G_CALLBACK (adapter_initialized), self);
+		g_signal_connect (priv->adapter, NM_BLUEZ4_ADAPTER_INITIALIZED,
+		                  G_CALLBACK (adapter_initialized), self);
 	}
 }
 
 static void
 default_adapter_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
 {
-	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data);
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	GVariant *ret;
-	GError *err = NULL;
+	NMBluez4Manager *self;
+	NMBluez4ManagerPrivate *priv;
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	const char *default_adapter;
 
 	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(o)"), &err);
-	if (ret) {
-		const char *default_adapter;
+	                                  G_VARIANT_TYPE ("(o)"), &error);
+	if (   !ret
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
 
-		g_variant_get (ret, "(&o)", &default_adapter);
-		default_adapter_changed (priv->proxy, default_adapter, self);
-		g_variant_unref (ret);
-	} else {
+	self = NM_BLUEZ4_MANAGER (user_data);
+	priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->proxy_cancellable);
+
+	if (!ret) {
 		/* Ignore "No such adapter" errors; just means bluetooth isn't active */
-		if (   !_nm_dbus_error_has_name (err, "org.bluez.Error.NoSuchAdapter")
-		    && !_nm_dbus_error_has_name (err, "org.freedesktop.systemd1.LoadFailed")
-		    && !g_error_matches (err, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
-			g_dbus_error_strip_remote_error (err);
-			nm_log_warn (LOGD_BT, "bluez error getting default adapter: %s",
-			             err->message);
+		if (   !_nm_dbus_error_has_name (error, "org.bluez.Error.NoSuchAdapter")
+		    && !_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")
+		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
+			g_dbus_error_strip_remote_error (error);
+			_LOGW ("bluez error getting default adapter: %s",
+			       error->message);
 		}
-		g_error_free (err);
+		return;
 	}
+
+	g_variant_get (ret, "(&o)", &default_adapter);
+	default_adapter_changed (priv->proxy, default_adapter, self);
 }
 
 static void
-query_default_adapter (NMBluez4Manager *self)
+name_owner_changed (NMBluez4Manager *self)
 {
 	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+	gs_free char *owner = NULL;
+
+	nm_clear_g_cancellable (&priv->proxy_cancellable);
+
+	owner = g_dbus_proxy_get_name_owner (priv->proxy);
+	if (!owner) {
+		/* Throwing away the adapter removes all devices too */
+		g_clear_object (&priv->adapter);
+		return;
+	}
+
+	priv->proxy_cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_call (priv->proxy, "DefaultAdapter",
 	                   NULL,
 	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   NULL,
-	                   default_adapter_cb, self);
+	                   priv->proxy_cancellable,
+	                   default_adapter_cb,
+	                   self);
 }
 
 static void
@@ -219,34 +248,35 @@ name_owner_changed_cb (GObject *object,
                        GParamSpec *pspec,
                        gpointer user_data)
 {
-	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data);
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	char *owner;
-
-	owner = g_dbus_proxy_get_name_owner (priv->proxy);
-	if (owner) {
-		query_default_adapter (self);
-		g_free (owner);
-	} else {
-		/* Throwing away the adapter removes all devices too */
-		g_clear_object (&priv->adapter);
-	}
+	name_owner_changed (user_data);
 }
 
-/*****************************************************************************/
-
 static void
-nm_bluez4_manager_init (NMBluez4Manager *self)
+_proxy_new_cb (GObject *source_object,
+               GAsyncResult *result,
+               gpointer user_data)
 {
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+	NMBluez4Manager *self;
+	NMBluez4ManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("bluez error creating D-Bus proxy: %s", error->message);
+		g_clear_object (&priv->proxy_cancellable);
+		return;
+	}
+
+	priv->proxy = proxy;
 
-	priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-	                                             G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                                             NULL,
-	                                             BLUEZ_SERVICE,
-	                                             BLUEZ_MANAGER_PATH,
-	                                             BLUEZ4_MANAGER_INTERFACE,
-	                                             NULL, NULL);
 	_nm_dbus_signal_connect (priv->proxy, "AdapterRemoved", G_VARIANT_TYPE ("(o)"),
 	                         G_CALLBACK (adapter_removed), self);
 	_nm_dbus_signal_connect (priv->proxy, "DefaultAdapterChanged", G_VARIANT_TYPE ("(o)"),
@@ -254,7 +284,27 @@ nm_bluez4_manager_init (NMBluez4Manager *self)
 	g_signal_connect (priv->proxy, "notify::g-name-owner",
 	                  G_CALLBACK (name_owner_changed_cb), self);
 
-	query_default_adapter (self);
+	name_owner_changed (self);
+}
+
+/*****************************************************************************/
+
+static void
+nm_bluez4_manager_init (NMBluez4Manager *self)
+{
+	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+
+	priv->proxy_cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+	                          NULL,
+	                          NM_BLUEZ_SERVICE,
+	                          NM_BLUEZ_MANAGER_PATH,
+	                          NM_BLUEZ4_MANAGER_INTERFACE,
+	                          priv->proxy_cancellable,
+	                          _proxy_new_cb,
+	                          self);
 }
 
 NMBluez4Manager *
@@ -275,7 +325,13 @@ dispose (GObject *object)
 	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (object);
 	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
 
-	g_clear_object (&priv->proxy);
+	nm_clear_g_cancellable (&priv->proxy_cancellable);
+
+	if (priv->proxy) {
+		g_signal_handlers_disconnect_by_data (priv->proxy, self);
+		g_clear_object (&priv->proxy);
+	}
+
 	g_clear_object (&priv->adapter);
 
 	G_OBJECT_CLASS (nm_bluez4_manager_parent_class)->dispose (object);
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index aba3a0dd..ca09b276 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -386,11 +386,11 @@ nm_bluez5_dun_cleanup (NMBluez5DunContext *context)
 			ioctl (context->rfcomm_fd, RFCOMMRELEASEDEV, &req);
 			context->rfcomm_id = -1;
 		}
-		close (context->rfcomm_fd);
+		nm_close (context->rfcomm_fd);
 		context->rfcomm_fd = -1;
 	}
 
-	close (context->rfcomm_tty_fd);
+	nm_close (context->rfcomm_tty_fd);
 	context->rfcomm_tty_fd = -1;
 }
 
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index 88759301..8c93f2a5 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -16,7 +16,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2013 Red Hat, Inc.
+ * Copyright (C) 2007 - 2017 Red Hat, Inc.
  * Copyright (C) 2013 Intel Corporation.
  */
 
@@ -30,14 +30,17 @@
 
 #include "nm-core-internal.h"
 
+#include "nm-utils/c-list.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
+#include "devices/nm-device-bridge.h"
 #include "settings/nm-settings.h"
 
 /*****************************************************************************/
 
 enum {
 	BDADDR_ADDED,
+	NETWORK_SERVER_ADDED,
 	LAST_SIGNAL,
 };
 
@@ -49,10 +52,13 @@ typedef struct {
 	GDBusProxy *proxy;
 
 	GHashTable *devices;
+
+	CList network_servers;
 } NMBluez5ManagerPrivate;
 
 struct _NMBluez5Manager {
 	GObject parent;
+	NMBtVTableNetworkServer network_server_vtable;
 	NMBluez5ManagerPrivate _priv;
 };
 
@@ -64,6 +70,15 @@ G_DEFINE_TYPE (NMBluez5Manager, nm_bluez5_manager, G_TYPE_OBJECT)
 
 #define NM_BLUEZ5_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez5Manager, NM_IS_BLUEZ5_MANAGER)
 
+#define NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE(self) (&(self)->network_server_vtable)
+#define NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER(vtable) \
+	NM_BLUEZ5_MANAGER(((char *)(vtable)) - offsetof (struct _NMBluez5Manager, network_server_vtable))
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN LOGD_BT
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez5", __VA_ARGS__)
+
 /*****************************************************************************/
 
 static void device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self);
@@ -71,6 +86,182 @@ static void device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Man
 
 /*****************************************************************************/
 
+typedef struct {
+	char *path;
+	char *addr;
+	NMDevice *device;
+	CList lst_ns;
+} NetworkServer;
+
+static NetworkServer *
+_find_network_server (NMBluez5Manager *self, const char *path, NMDevice *device)
+{
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NetworkServer *network_server;
+
+	nm_assert (path || NM_IS_DEVICE (device));
+
+	c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) {
+		if (path && !nm_streq (network_server->path, path))
+			continue;
+		if (device && network_server->device != device)
+			continue;
+		return network_server;
+	}
+	return NULL;
+}
+
+static NetworkServer *
+_find_network_server_for_addr (NMBluez5Manager *self, const char *addr)
+{
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NetworkServer *network_server;
+
+	c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) {
+		/* The address lookups need a server not assigned to a device
+		 * and tolerate an empty address as a wildcard for "any". */
+		if (   !network_server->device
+		    && (!addr || nm_streq (network_server->addr, addr)))
+			return network_server;
+	}
+	return NULL;
+}
+
+static void
+_network_server_unregister (NMBluez5Manager *self, NetworkServer *network_server)
+{
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+
+	if (!network_server->device) {
+		/* Not connected. */
+		return;
+	}
+
+	_LOGI ("NAP: unregistering %s from %s",
+	       nm_device_get_iface (network_server->device),
+	       network_server->addr);
+
+	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
+	                        NM_BLUEZ_SERVICE,
+	                        network_server->path,
+	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
+	                        "Unregister",
+	                        g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP),
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1, NULL, NULL, NULL);
+
+	g_clear_object (&network_server->device);
+}
+
+static void
+_network_server_free (NMBluez5Manager *self, NetworkServer *network_server)
+{
+	_network_server_unregister (self, network_server);
+	c_list_unlink (&network_server->lst_ns);
+	g_free (network_server->path);
+	g_free (network_server->addr);
+	g_slice_free (NetworkServer, network_server);
+}
+
+static gboolean
+network_server_is_available (const NMBtVTableNetworkServer *vtable,
+                             const char *addr)
+{
+	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
+
+	return !!_find_network_server_for_addr (self, addr);
+}
+
+static gboolean
+network_server_register_bridge (const NMBtVTableNetworkServer *vtable,
+                                const char *addr,
+                                NMDevice *device)
+{
+	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NetworkServer *network_server = _find_network_server_for_addr (self, addr);
+
+	nm_assert (NM_IS_DEVICE (device));
+	nm_assert (!_find_network_server (self, NULL, device));
+
+	if (!network_server) {
+		/* The device checked that a network server is available, before
+		 * starting the activation, but for some reason it no longer is.
+		 * Indicate that the activation should not proceed. */
+		_LOGI ("NAP: %s is not available for %s", addr, nm_device_get_iface (device));
+		return FALSE;
+	}
+
+	_LOGI ("NAP: registering %s on %s", nm_device_get_iface (device), network_server->addr);
+
+	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
+	                        NM_BLUEZ_SERVICE,
+	                        network_server->path,
+	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
+	                        "Register",
+	                        g_variant_new ("(ss)", BLUETOOTH_CONNECT_NAP, nm_device_get_iface (device)),
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1, NULL, NULL, NULL);
+
+	network_server->device = g_object_ref (device);
+
+	return TRUE;
+}
+
+static gboolean
+network_server_unregister_bridge (const NMBtVTableNetworkServer *vtable,
+                                  NMDevice *device)
+{
+	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
+	NetworkServer *network_server = _find_network_server (self, NULL, device);
+
+	if (network_server)
+		_network_server_unregister (self, network_server);
+
+	return TRUE;
+}
+
+static void
+network_server_removed (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
+{
+	NetworkServer *network_server;
+
+	network_server = _find_network_server (self, path, NULL);
+	if (!network_server)
+		return;
+
+	if (network_server->device) {
+		nm_device_queue_state (network_server->device, NM_DEVICE_STATE_DISCONNECTED,
+		                       NM_DEVICE_STATE_REASON_BT_FAILED);
+	}
+	_LOGI ("NAP: removed interface %s", network_server->addr);
+	_network_server_free (self, network_server);
+}
+
+static void
+network_server_added (GDBusProxy *proxy, const gchar *path, const char *addr, NMBluez5Manager *self)
+{
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NetworkServer *network_server;
+
+	/* If BlueZ messes up and announces a single network server twice,
+	 * make sure we get rid of the older instance first. */
+	network_server_removed (proxy, path, self);
+
+	network_server = g_slice_new0 (NetworkServer);
+	network_server->path = g_strdup (path);
+	network_server->addr = g_strdup (addr);
+	c_list_link_before (&priv->network_servers, &network_server->lst_ns);
+
+	_LOGI ("NAP: added interface %s", addr);
+
+	g_signal_emit (self, signals[NETWORK_SERVER_ADDED], 0);
+}
+
+/*****************************************************************************/
+
 static void
 emit_bdaddr_added (NMBluez5Manager *self, NMBluezDevice *device)
 {
@@ -125,14 +316,14 @@ device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self)
 {
 	gboolean usable = nm_bluez_device_get_usable (device);
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device now %s",
-	            nm_bluez_device_get_path (device),
-	            usable ? "usable" : "unusable");
+	_LOGD ("(%s): bluez device now %s",
+	       nm_bluez_device_get_path (device),
+	       usable ? "usable" : "unusable");
 
 	if (usable) {
-		nm_log_dbg (LOGD_BT, "(%s): bluez device address %s",
-				    nm_bluez_device_get_path (device),
-				    nm_bluez_device_get_address (device));
+		_LOGD ("(%s): bluez device address %s",
+		       nm_bluez_device_get_path (device),
+		       nm_bluez_device_get_address (device));
 		emit_bdaddr_added (self, device);
 	} else
 		g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED);
@@ -143,9 +334,9 @@ device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *se
 {
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device %s",
-	            nm_bluez_device_get_path (device),
-	            success ? "initialized" : "failed to initialize");
+	_LOGD ("(%s): bluez device %s",
+	       nm_bluez_device_get_path (device),
+	       success ? "initialized" : "failed to initialize");
 	if (!success)
 		g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device));
 }
@@ -157,11 +348,11 @@ device_added (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
 	NMBluezDevice *device;
 
 	device = nm_bluez_device_new (path, NULL, priv->settings, 5);
-	g_signal_connect (device, "initialized", G_CALLBACK (device_initialized), self);
-	g_signal_connect (device, "notify::usable", G_CALLBACK (device_usable), self);
+	g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self);
+	g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self);
 	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
 
-	nm_log_dbg (LOGD_BT, "(%s): new bluez device found", path);
+	_LOGD ("(%s): new bluez device found", path);
 }
 
 static void
@@ -170,7 +361,7 @@ device_removed (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 	NMBluezDevice *device;
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device removed", path);
+	_LOGD ("(%s): bluez device removed", path);
 
 	device = g_hash_table_lookup (priv->devices, path);
 	if (device) {
@@ -186,8 +377,16 @@ object_manager_interfaces_added (GDBusProxy      *proxy,
                                  GVariant        *dict,
                                  NMBluez5Manager *self)
 {
-	if (g_variant_lookup (dict, BLUEZ5_DEVICE_INTERFACE, "a{sv}", NULL))
+	if (g_variant_lookup (dict, NM_BLUEZ5_DEVICE_INTERFACE, "a{sv}", NULL))
 		device_added (proxy, path, self);
+	if (g_variant_lookup (dict, NM_BLUEZ5_NETWORK_SERVER_INTERFACE, "a{sv}", NULL)) {
+		gs_unref_variant GVariant *adapter = g_variant_lookup_value (dict, NM_BLUEZ5_ADAPTER_INTERFACE, G_VARIANT_TYPE_DICTIONARY);
+		const char *address;
+
+		if (   adapter
+		    && g_variant_lookup (adapter, "Address", "&s", &address))
+			network_server_added (proxy, path, address, self);
+	}
 }
 
 static void
@@ -196,8 +395,10 @@ object_manager_interfaces_removed (GDBusProxy       *proxy,
                                    const char      **ifaces,
                                    NMBluez5Manager  *self)
 {
-	if (ifaces && g_strv_contains (ifaces, BLUEZ5_DEVICE_INTERFACE))
+	if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_DEVICE_INTERFACE))
 		device_removed (proxy, path, self);
+	if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_NETWORK_SERVER_INTERFACE))
+		network_server_removed (proxy, path, self);
 }
 
 static void
@@ -215,20 +416,17 @@ get_managed_objects_cb (GDBusProxy *proxy,
 	                                      &error);
 	if (!variant) {
 		if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
-			nm_log_warn (LOGD_BT, "Couldn't get managed objects: not running Bluez5?");
+			_LOGW ("Couldn't get managed objects: not running Bluez5?");
 		else {
 			g_dbus_error_strip_remote_error (error);
-			nm_log_warn (LOGD_BT, "Couldn't get managed objects: %s", error->message);
+			_LOGW ("Couldn't get managed objects: %s", error->message);
 		}
 		g_clear_error (&error);
 		return;
 	}
 	g_variant_iter_init (&i, g_variant_get_child_value (variant, 0));
 	while ((g_variant_iter_next (&i, "{&o*}", &path, &ifaces))) {
-		if (g_variant_lookup_value (ifaces, BLUEZ5_DEVICE_INTERFACE,
-		                            G_VARIANT_TYPE_DICTIONARY)) {
-			device_added (proxy, path, self);
-		}
+		object_manager_interfaces_added (proxy, path, ifaces, self);
 		g_variant_unref (ifaces);
 	}
 
@@ -248,7 +446,7 @@ on_proxy_acquired (GObject *object,
 	priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
 
 	if (!priv->proxy) {
-		nm_log_warn (LOGD_BT, "Couldn't acquire object manager proxy: %s", error->message);
+		_LOGW ("Couldn't acquire object manager proxy: %s", error->message);
 		g_clear_error (&error);
 		return;
 	}
@@ -281,9 +479,9 @@ bluez_connect (NMBluez5Manager *self)
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_NONE,
 	                          NULL,
-	                          BLUEZ_SERVICE,
-	                          BLUEZ_MANAGER_PATH,
-	                          OBJECT_MANAGER_INTERFACE,
+	                          NM_BLUEZ_SERVICE,
+	                          NM_BLUEZ_MANAGER_PATH,
+	                          NM_OBJECT_MANAGER_INTERFACE,
 	                          NULL,
 	                          (GAsyncReadyCallback) on_proxy_acquired,
 	                          self);
@@ -306,33 +504,26 @@ name_owner_changed_cb (GObject *object,
 	}
 }
 
-static void
-bluez_cleanup (NMBluez5Manager *self, gboolean do_signal)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_func (priv->proxy, G_CALLBACK (name_owner_changed_cb), self);
-		g_clear_object (&priv->proxy);
-	}
-
-	if (do_signal)
-		remove_all_devices (self);
-	else
-		g_hash_table_remove_all (priv->devices);
-}
-
 /*****************************************************************************/
 
 static void
 nm_bluez5_manager_init (NMBluez5Manager *self)
 {
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NMBtVTableNetworkServer *network_server_vtable = NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE (self);
 
 	bluez_connect (self);
 
-	priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal,
+	priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                       NULL, g_object_unref);
+
+	c_list_init (&priv->network_servers);
+
+	nm_assert (!nm_bt_vtable_network_server);
+	network_server_vtable->is_available = network_server_is_available;
+	network_server_vtable->register_bridge = network_server_register_bridge;
+	network_server_vtable->unregister_bridge = network_server_unregister_bridge;
+	nm_bt_vtable_network_server = network_server_vtable;
 }
 
 NMBluez5Manager *
@@ -351,8 +542,18 @@ static void
 dispose (GObject *object)
 {
 	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object);
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	CList *iter, *safe;
 
-	bluez_cleanup (self, FALSE);
+	c_list_for_each_safe (iter, safe, &priv->network_servers)
+		_network_server_free (self, c_list_entry (iter, NetworkServer, lst_ns));
+
+	if (priv->proxy) {
+		g_signal_handlers_disconnect_by_func (priv->proxy, G_CALLBACK (name_owner_changed_cb), self);
+		g_clear_object (&priv->proxy);
+	}
+
+	g_hash_table_remove_all (priv->devices);
 
 	G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->dispose (object);
 }
@@ -360,7 +561,8 @@ dispose (GObject *object)
 static void
 finalize (GObject *object)
 {
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE ((NMBluez5Manager *) object);
+	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object);
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 
 	g_hash_table_destroy (priv->devices);
 
@@ -384,4 +586,11 @@ nm_bluez5_manager_class_init (NMBluez5ManagerClass *klass)
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING,
 	                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
+
+	signals[NETWORK_SERVER_ADDED] =
+	    g_signal_new (NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL, NULL,
+	                  G_TYPE_NONE, 0);
 }
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 4ee71489..0d46be8f 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -40,15 +40,14 @@
 #include "nm-bt-error.h"
 #include "platform/nm-platform.h"
 
+#include "devices/wwan/nm-modem-manager.h"
+#include "devices/wwan/nm-modem.h"
+
 #include "introspection/org.freedesktop.NetworkManager.Device.Bluetooth.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceBt);
 
-#define MM_DBUS_SERVICE   "org.freedesktop.ModemManager1"
-#define MM_DBUS_PATH      "/org/freedesktop/ModemManager1"
-#define MM_DBUS_INTERFACE "org.freedesktop.ModemManager1"
-
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
@@ -65,7 +64,8 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
-	GDBusProxy *mm_proxy;
+	NMModemManager *modem_manager;
+
 	gboolean mm_running;
 
 	NMBluezDevice *bt_device;
@@ -647,7 +647,7 @@ component_added (NMDevice *device, GObject *component)
 	NMDeviceState state;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	if (!NM_IS_MODEM (component))
+	if (!component || !NM_IS_MODEM (component))
 		return FALSE;
 	modem = NM_MODEM (component);
 
@@ -967,9 +967,12 @@ is_available (NMDevice *dev, NMDeviceCheckDevAvailableFlags flags)
 }
 
 static void
-set_mm_running (NMDeviceBt *self, gboolean running)
+set_mm_running (NMDeviceBt *self)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	gboolean running;
+
+	running = (nm_modem_manager_name_owner_get (priv->modem_manager) != NULL);
 
 	if (priv->mm_running != running) {
 		_LOGD (LOGD_BT, "ModemManager now %s",
@@ -983,18 +986,11 @@ set_mm_running (NMDeviceBt *self, gboolean running)
 }
 
 static void
-mm_name_owner_changed (GObject *object,
-                       GParamSpec *pspec,
-                       NMDeviceBt *self)
+mm_name_owner_changed_cb (GObject *object,
+                          GParamSpec *pspec,
+                          gpointer user_data)
 {
-	char *owner;
-
-	owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (object));
-	if (owner) {
-		set_mm_running (self, TRUE);
-		g_free (owner);
-	} else
-		set_mm_running (self, FALSE);
+	set_mm_running (user_data);
 }
 
 /*****************************************************************************/
@@ -1039,7 +1035,8 @@ set_property (GObject *object, guint prop_id,
 	case PROP_BT_DEVICE:
 		/* construct-only */
 		priv->bt_device = g_value_dup_object (value);
-		g_signal_connect (priv->bt_device, "removed", G_CALLBACK (bluez_device_removed), object);
+		if (!priv->bt_device)
+			g_return_if_reached ();
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1052,46 +1049,42 @@ set_property (GObject *object, guint prop_id,
 static void
 nm_device_bt_init (NMDeviceBt *self)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	priv->mm_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-	                                                G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
-	                                                    G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
-	                                                    G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
-	                                                NULL,
-	                                                MM_DBUS_SERVICE,
-	                                                MM_DBUS_PATH,
-	                                                MM_DBUS_INTERFACE,
-	                                                NULL, &error);
-	if (priv->mm_proxy) {
-		g_signal_connect (priv->mm_proxy, "notify::g-name-owner",
-		                  G_CALLBACK (mm_name_owner_changed),
-		                  self);
-		mm_name_owner_changed (G_OBJECT (priv->mm_proxy), NULL, self);
-	} else {
-		_LOGW (LOGD_MB, "Could not create proxy for '%s': %s",
-		       MM_DBUS_SERVICE, error->message);
-		g_clear_error (&error);
-	}
 }
 
 static void
 constructed (GObject *object)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
+	NMDeviceBt *self = NM_DEVICE_BT (object);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 	const char *my_hwaddr;
 
 	G_OBJECT_CLASS (nm_device_bt_parent_class)->constructed (object);
 
+	priv->modem_manager = g_object_ref (nm_modem_manager_get ());
+
+	nm_modem_manager_name_owner_ref (priv->modem_manager);
+
+	g_signal_connect (priv->modem_manager,
+	                  "notify::"NM_MODEM_MANAGER_NAME_OWNER,
+	                  G_CALLBACK (mm_name_owner_changed_cb),
+	                  self);
+
+	if (priv->bt_device) {
+		/* Watch for BT device property changes */
+		g_signal_connect (priv->bt_device, "notify::" NM_BLUEZ_DEVICE_CONNECTED,
+		                  G_CALLBACK (bluez_connected_changed),
+		                  object);
+		g_signal_connect (priv->bt_device, NM_BLUEZ_DEVICE_REMOVED,
+		                  G_CALLBACK (bluez_device_removed), object);
+	}
+
 	my_hwaddr = nm_device_get_hw_address (NM_DEVICE (object));
-	g_assert (my_hwaddr);
-	priv->bdaddr = g_strdup (my_hwaddr);
+	if (my_hwaddr)
+		priv->bdaddr = g_strdup (my_hwaddr);
+	else
+		g_warn_if_reached ();
 
-	/* Watch for BT device property changes */
-	g_signal_connect (priv->bt_device, "notify::" NM_BLUEZ_DEVICE_CONNECTED,
-	                  G_CALLBACK (bluez_connected_changed),
-	                  object);
+	set_mm_running (self);
 }
 
 NMDevice *
@@ -1129,9 +1122,10 @@ dispose (GObject *object)
 
 	g_signal_handlers_disconnect_matched (priv->bt_device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
 
-	if (priv->mm_proxy) {
-		g_signal_handlers_disconnect_by_func (priv->mm_proxy, G_CALLBACK (mm_name_owner_changed), object);
-		g_clear_object (&priv->mm_proxy);
+	if (priv->modem_manager) {
+		g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), object);
+		nm_modem_manager_name_owner_unref (priv->modem_manager);
+		g_clear_object (&priv->modem_manager);
 	}
 
 	modem_cleanup (NM_DEVICE_BT (object));
diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/devices/bluetooth/nm-device-bt.h
index 9bcf6ca8..b90dbd2a 100644
--- a/src/devices/bluetooth/nm-device-bt.h
+++ b/src/devices/bluetooth/nm-device-bt.h
@@ -24,8 +24,6 @@
 #include "devices/nm-device.h"
 #include "nm-bluez-device.h"
 
-#include "devices/wwan/nm-modem.h"
-
 #define NM_TYPE_DEVICE_BT                   (nm_device_bt_get_type ())
 #define NM_DEVICE_BT(obj)                   (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BT, NMDeviceBt))
 #define NM_DEVICE_BT_CLASS(klass)           (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_BT, NMDeviceBtClass))
@@ -52,8 +50,10 @@ NMDevice *nm_device_bt_new (NMBluezDevice *bt_device,
 
 guint32 nm_device_bt_get_capabilities (NMDeviceBt *device);
 
+struct _NMModem;
+
 gboolean nm_device_bt_modem_added (NMDeviceBt *device,
-                                   NMModem *modem,
+                                   struct _NMModem *modem,
                                    const char *driver);
 
 #endif /* __NETWORKMANAGER_DEVICE_BT_H__ */
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index d9c2ca64..910dd0bf 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -58,24 +58,6 @@ get_generic_capabilities (NMDevice *dev)
 }
 
 static gboolean
-is_available (NMDevice *dev, NMDeviceCheckDevAvailableFlags flags)
-{
-	return TRUE;
-}
-
-static gboolean
-check_connection_available (NMDevice *device,
-                            NMConnection *connection,
-                            NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
-{
-	/* Connections are always available because the carrier state is determined
-	 * by the slave carrier states, not the bonds's state.
-	 */
-	return TRUE;
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMSettingBond *s_bond;
@@ -501,7 +483,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create bond interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 	return TRUE;
@@ -622,6 +604,7 @@ reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_n
 static void
 nm_device_bond_init (NMDeviceBond * self)
 {
+	nm_assert (nm_device_is_master (NM_DEVICE (self)));
 }
 
 static void
@@ -631,10 +614,9 @@ nm_device_bond_class_init (NMDeviceBondClass *klass)
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_BOND_SETTING_NAME, NM_LINK_TYPE_BOND)
 
+	parent_class->is_master = TRUE;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->is_available = is_available;
 	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->check_connection_available = check_connection_available;
 	parent_class->complete_connection = complete_connection;
 
 	parent_class->update_connection = update_connection;
@@ -671,7 +653,6 @@ create_device (NMDeviceFactory *factory,
 	                                  NM_DEVICE_TYPE_DESC, "Bond",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BOND,
 	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_BOND,
-	                                  NM_DEVICE_IS_MASTER, TRUE,
 	                                  NULL);
 }
 
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 01c4eb22..74689aef 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -49,6 +49,10 @@ G_DEFINE_TYPE (NMDeviceBridge, nm_device_bridge, NM_TYPE_DEVICE)
 
 /*****************************************************************************/
 
+const NMBtVTableNetworkServer *nm_bt_vtable_network_server = NULL;
+
+/*****************************************************************************/
+
 static NMDeviceCapabilities
 get_generic_capabilities (NMDevice *dev)
 {
@@ -56,20 +60,23 @@ get_generic_capabilities (NMDevice *dev)
 }
 
 static gboolean
-is_available (NMDevice *dev, NMDeviceCheckDevAvailableFlags flags)
-{
-	return TRUE;
-}
-
-static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
                             const char *specific_object)
 {
-	/* Connections are always available because the carrier state is determined
-	 * by the bridge port carrier states, not the bridge's state.
-	 */
+	NMSettingBluetooth *s_bt;
+
+	if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_available (device, connection, flags, specific_object))
+		return FALSE;
+
+	s_bt = _nm_connection_get_setting_bluetooth_for_nap (connection);
+	if (s_bt) {
+		return    nm_bt_vtable_network_server
+		       && nm_bt_vtable_network_server->is_available (nm_bt_vtable_network_server,
+		                                                     nm_setting_bluetooth_get_bdaddr (s_bt));
+	}
+
 	return TRUE;
 }
 
@@ -83,9 +90,17 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		return FALSE;
 
 	s_bridge = nm_connection_get_setting_bridge (connection);
-	if (!s_bridge || !nm_connection_is_type (connection, NM_SETTING_BRIDGE_SETTING_NAME))
+	if (!s_bridge)
 		return FALSE;
 
+	if (!nm_connection_is_type (connection, NM_SETTING_BRIDGE_SETTING_NAME)) {
+		if (   nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME)
+		    && _nm_connection_get_setting_bluetooth_for_nap (connection)) {
+			/* a bluetooth NAP connection is handled by the bridge */
+		} else
+			return FALSE;
+	}
+
 	mac_address = nm_setting_bridge_get_mac_address (s_bridge);
 	if (mac_address && nm_device_is_real (device)) {
 		const char *hw_addr;
@@ -141,6 +156,7 @@ static const Option master_options[] = {
 	{ NM_SETTING_BRIDGE_HELLO_TIME, "hello_time", TRUE, TRUE },
 	{ NM_SETTING_BRIDGE_MAX_AGE, "max_age", TRUE, TRUE },
 	{ NM_SETTING_BRIDGE_AGEING_TIME, "ageing_time", TRUE, TRUE },
+	{ NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, "group_fwd_mask", TRUE, FALSE },
 	{ NM_SETTING_BRIDGE_MULTICAST_SNOOPING, "multicast_snooping", FALSE, FALSE },
 	{ NULL, NULL }
 };
@@ -324,6 +340,40 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMConnection *connection;
+	NMSettingBluetooth *s_bt;
+
+	connection = nm_device_get_applied_connection (device);
+
+	s_bt = _nm_connection_get_setting_bluetooth_for_nap (connection);
+	if (s_bt) {
+		if (   !nm_bt_vtable_network_server
+		    || !nm_bt_vtable_network_server->register_bridge (nm_bt_vtable_network_server,
+		                                                      nm_setting_bluetooth_get_bdaddr (s_bt),
+		                                                      device)) {
+			/* The HCI we could use is no longer present. */
+			*out_failure_reason = NM_DEVICE_STATE_REASON_REMOVED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	if (nm_bt_vtable_network_server) {
+		/* always call unregister. It does nothing if the device
+		 * isn't registered as a hotspot bridge. */
+		nm_bt_vtable_network_server->unregister_bridge (nm_bt_vtable_network_server,
+		                                                device);
+	}
+}
+
 static gboolean
 enslave_slave (NMDevice *device,
                NMDevice *slave,
@@ -384,20 +434,32 @@ create_and_realize (NMDevice *device,
 	NMSettingBridge *s_bridge;
 	const char *iface = nm_device_get_iface (device);
 	const char *hwaddr;
+	gs_free char *hwaddr_cloned = NULL;
 	guint8 mac_address[NM_UTILS_HWADDR_LEN_MAX];
 	NMPlatformError plerr;
 
-	g_assert (iface);
+	nm_assert (iface);
 
 	s_bridge = nm_connection_get_setting_bridge (connection);
-	g_assert (s_bridge);
+	nm_assert (s_bridge);
+
 	hwaddr = nm_setting_bridge_get_mac_address (s_bridge);
+	if (   !hwaddr
+	    && nm_device_hw_addr_get_cloned (device, connection, FALSE,
+	                                     &hwaddr_cloned, NULL, NULL)) {
+		/* The cloned MAC address might by dynamic, for example with stable-id="${RANDOM}".
+		 * It's a bit odd that we first create the device with one dynamic address,
+		 * and later on may reset it to another. That is, because we don't cache
+		 * the dynamic address in @device, like we do during nm_device_hw_addr_set_cloned(). */
+		hwaddr = hwaddr_cloned;
+	}
+
 	if (hwaddr) {
 		if (!nm_utils_hwaddr_aton (hwaddr, mac_address, ETH_ALEN)) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 			             "Invalid hardware address '%s'",
 			             hwaddr);
-			return FALSE;
+			g_return_val_if_reached (FALSE);
 		}
 	}
 
@@ -411,7 +473,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create bridge interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -423,6 +485,7 @@ create_and_realize (NMDevice *device,
 static void
 nm_device_bridge_init (NMDeviceBridge * self)
 {
+	nm_assert (nm_device_is_master (NM_DEVICE (self)));
 }
 
 static void
@@ -432,8 +495,8 @@ nm_device_bridge_class_init (NMDeviceBridgeClass *klass)
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_BRIDGE_SETTING_NAME, NM_LINK_TYPE_BRIDGE)
 
+	parent_class->is_master = TRUE;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->is_available = is_available;
 	parent_class->check_connection_compatible = check_connection_compatible;
 	parent_class->check_connection_available = check_connection_available;
 	parent_class->complete_connection = complete_connection;
@@ -443,6 +506,8 @@ nm_device_bridge_class_init (NMDeviceBridgeClass *klass)
 
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->act_stage1_prepare = act_stage1_prepare;
+	parent_class->act_stage2_config = act_stage2_config;
+	parent_class->deactivate = deactivate;
 	parent_class->enslave_slave = enslave_slave;
 	parent_class->release_slave = release_slave;
 	parent_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
@@ -470,12 +535,36 @@ create_device (NMDeviceFactory *factory,
 	                                  NM_DEVICE_TYPE_DESC, "Bridge",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BRIDGE,
 	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_BRIDGE,
-	                                  NM_DEVICE_IS_MASTER, TRUE,
 	                                  NULL);
 }
 
+static gboolean
+match_connection (NMDeviceFactory *factory,
+                  NMConnection *connection)
+{
+	const char *type = nm_connection_get_connection_type (connection);
+
+	if (nm_streq (type, NM_SETTING_BRIDGE_SETTING_NAME))
+		return TRUE;
+
+	nm_assert (nm_streq (type, NM_SETTING_BLUETOOTH_SETTING_NAME));
+
+	if (!_nm_connection_get_setting_bluetooth_for_nap (connection))
+		return FALSE;
+
+	if (!g_type_from_name ("NMBluezManager")) {
+		/* bluetooth NAP connections are handled by bridge factory. However,
+		 * it needs help from the bluetooth plugin, so if the plugin is not loaded,
+		 * we claim not to support it. */
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (BRIDGE, Bridge, bridge,
 	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_BRIDGE)
-	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_BRIDGE_SETTING_NAME),
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BLUETOOTH_SETTING_NAME),
 	factory_class->create_device = create_device;
+	factory_class->match_connection = match_connection;
 );
diff --git a/src/devices/nm-device-bridge.h b/src/devices/nm-device-bridge.h
index f0fa1f4b..44b4ed72 100644
--- a/src/devices/nm-device-bridge.h
+++ b/src/devices/nm-device-bridge.h
@@ -35,4 +35,6 @@ typedef struct _NMDeviceBridgeClass NMDeviceBridgeClass;
 
 GType nm_device_bridge_get_type (void);
 
+extern const NMBtVTableNetworkServer *nm_bt_vtable_network_server;
+
 #endif /* __NETWORKMANAGER_DEVICE_BRIDGE_H__ */
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index dce4f7bc..085c44e6 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -112,7 +112,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create dummy interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 8a04d401..7807100f 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -108,14 +108,14 @@ typedef struct _NMDeviceEthernetPrivate {
 
 	/* PPPoE */
 	NMPPPManager *ppp_manager;
-	NMIP4Config  *pending_ip4_config;
 	gint32        last_pppoe_time;
 	guint         pppoe_wait_id;
 
 	/* DCB */
 	DcbWait       dcb_wait;
 	guint         dcb_timeout_id;
-	gulong        dcb_carrier_id;
+
+	bool          dcb_handle_carrier_changes:1;
 } NMDeviceEthernetPrivate;
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceEthernet,
@@ -255,22 +255,6 @@ _update_s390_subchannels (NMDeviceEthernet *self)
 }
 
 static void
-reset_8021x_autoconnect_retries (NMDevice *device)
-{
-	NMActRequest *req;
-	NMSettingsConnection *connection;
-
-	req = nm_device_get_act_request (device);
-	if (   req
-	    && nm_device_get_applied_setting (device, NM_TYPE_SETTING_802_1X)) {
-		connection = nm_act_request_get_settings_connection (req);
-		g_return_if_fail (connection);
-		/* Reset autoconnect retries on success, failure, or when deactivating */
-		nm_settings_connection_reset_autoconnect_retries (connection);
-	}
-}
-
-static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -278,12 +262,6 @@ device_state_changed (NMDevice *device,
 {
 	if (new_state > NM_DEVICE_STATE_ACTIVATED)
 		wired_secrets_cancel (NM_DEVICE_ETHERNET (device));
-
-	if (NM_IN_SET (new_state,
-	               NM_DEVICE_STATE_ACTIVATED,
-	               NM_DEVICE_STATE_FAILED,
-	               NM_DEVICE_STATE_DISCONNECTED))
-		reset_8021x_autoconnect_retries (device);
 }
 
 static void
@@ -294,7 +272,7 @@ nm_device_ethernet_init (NMDeviceEthernet *self)
 	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetPrivate);
 	self->_priv = priv;
 
-	priv->s390_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	priv->s390_options = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 }
 
 static NMDeviceCapabilities
@@ -680,25 +658,20 @@ handle_auth_or_fail (NMDeviceEthernet *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
+	NMDeviceEthernetPrivate *priv;
 	const char *setting_name;
 	NMConnection *applied_connection;
-	NMSettingsConnection *settings_connection;
-	int tries_left;
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-	settings_connection = nm_act_request_get_settings_connection (req);
+	priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 
-	tries_left = nm_settings_connection_get_autoconnect_retries (settings_connection);
-	if (tries_left == 0)
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
-	if (tries_left > 0)
-		nm_settings_connection_set_autoconnect_retries (settings_connection, tries_left - 1);
-
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
 	nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
 
+	applied_connection = nm_act_request_get_applied_connection (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (setting_name) {
 		wired_secrets_get_secrets (self, setting_name,
@@ -1000,6 +973,7 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 static NMActStageReturn
 pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *out_failure_reason)
 {
+	NMDevice *device = NM_DEVICE (self);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	NMSettingPppoe *s_pppoe;
 	NMActRequest *req;
@@ -1011,9 +985,17 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *out_
 	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
 	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
-	priv->ppp_manager = nm_ppp_manager_create (nm_device_get_iface (NM_DEVICE (self)),
+	priv->ppp_manager = nm_ppp_manager_create (nm_device_get_iface (device),
 	                                           &err);
 
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     nm_device_get_route_table (device, AF_INET, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET),
+		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET6));
+	}
+
 	if (   !priv->ppp_manager
 	    || !nm_ppp_manager_start (priv->ppp_manager, req,
 	                              nm_setting_pppoe_get_username (s_pppoe),
@@ -1133,7 +1115,7 @@ dcb_state (NMDevice *device, gboolean timeout)
 			_LOGD (LOGD_DCB, "dcb_state() enabling DCB");
 			nm_clear_g_source (&priv->dcb_timeout_id);
 			if (!dcb_enable (device)) {
-				nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+				priv->dcb_handle_carrier_changes = FALSE;
 				nm_device_state_changed (device,
 				                         NM_DEVICE_STATE_FAILED,
 				                         NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED);
@@ -1157,7 +1139,7 @@ dcb_state (NMDevice *device, gboolean timeout)
 			_LOGD (LOGD_DCB, "dcb_state() preconfig up configuring DCB");
 			nm_clear_g_source (&priv->dcb_timeout_id);
 			if (!dcb_configure (device)) {
-				nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+				priv->dcb_handle_carrier_changes = FALSE;
 				nm_device_state_changed (device,
 				                         NM_DEVICE_STATE_FAILED,
 				                         NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED);
@@ -1180,7 +1162,7 @@ dcb_state (NMDevice *device, gboolean timeout)
 		if (timeout || carrier) {
 			_LOGD (LOGD_DCB, "dcb_state() postconfig up starting IP");
 			nm_clear_g_source (&priv->dcb_timeout_id);
-			nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+			priv->dcb_handle_carrier_changes = FALSE;
 			priv->dcb_wait = DCB_WAIT_UNKNOWN;
 			nm_device_activate_schedule_stage3_ip_config_start (device);
 		}
@@ -1190,20 +1172,6 @@ dcb_state (NMDevice *device, gboolean timeout)
 	}
 }
 
-static void
-dcb_carrier_changed (NMDevice *device, GParamSpec *pspec, gpointer unused)
-{
-	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-
-	g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
-
-	if (priv->dcb_timeout_id) {
-		_LOGD (LOGD_DCB, "carrier_changed() calling dcb_state()");
-		dcb_state (device, FALSE);
-	}
-}
-
 /*****************************************************************************/
 
 static gboolean
@@ -1262,7 +1230,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
 
 	nm_clear_g_source (&priv->dcb_timeout_id);
-	nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+	priv->dcb_handle_carrier_changes = FALSE;
 
 	/* 802.1x has to run before any IP configuration since the 802.1x auth
 	 * process opens the port up for normal traffic.
@@ -1296,13 +1264,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 			priv->dcb_timeout_id = g_timeout_add_seconds (4, dcb_carrier_timeout, device);
 		}
 
-		/* Watch carrier independently of NMDeviceClass::carrier_changed so
-		 * we get instant notifications of disconnection that aren't deferred.
-		 */
-		priv->dcb_carrier_id = g_signal_connect (device,
-		                                         "notify::" NM_DEVICE_CARRIER,
-		                                         G_CALLBACK (dcb_carrier_changed),
-		                                         NULL);
+		priv->dcb_handle_carrier_changes = TRUE;
 		ret = NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
@@ -1367,16 +1329,8 @@ deactivate (NMDevice *device)
 	NMSettingDcb *s_dcb;
 	GError *error = NULL;
 
-	/* Clear wired secrets tries when deactivating */
-	reset_8021x_autoconnect_retries (device);
-
 	nm_clear_g_source (&priv->pppoe_wait_id);
 
-	if (priv->pending_ip4_config) {
-		g_object_unref (priv->pending_ip4_config);
-		priv->pending_ip4_config = NULL;
-	}
-
 	if (priv->ppp_manager) {
 		nm_ppp_manager_stop_sync (priv->ppp_manager);
 		g_clear_object (&priv->ppp_manager);
@@ -1386,7 +1340,7 @@ deactivate (NMDevice *device)
 
 	priv->dcb_wait = DCB_WAIT_UNKNOWN;
 	nm_clear_g_source (&priv->dcb_timeout_id);
-	nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+	priv->dcb_handle_carrier_changes = FALSE;
 
 	/* Tear down DCB/FCoE if it was enabled */
 	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
@@ -1579,7 +1533,7 @@ update_connection (NMDevice *device, NMConnection *connection)
 }
 
 static void
-get_link_speed (NMDevice *device)
+link_speed_update (NMDevice *device)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
@@ -1591,16 +1545,28 @@ get_link_speed (NMDevice *device)
 		return;
 
 	priv->speed = speed;
-	_notify (self, PROP_SPEED);
-
 	_LOGD (LOGD_PLATFORM | LOGD_ETHER, "speed is now %d Mb/s", speed);
+	_notify (self, PROP_SPEED);
 }
 
 static void
 carrier_changed_notify (NMDevice *device, gboolean carrier)
 {
+	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+
+	if (priv->dcb_handle_carrier_changes) {
+		nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
+
+		if (priv->dcb_timeout_id) {
+			_LOGD (LOGD_DCB, "carrier_changed() calling dcb_state()");
+			dcb_state (device, FALSE);
+		}
+	}
+
 	if (carrier)
-		get_link_speed (device);
+		link_speed_update (device);
+
 	NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->carrier_changed_notify (device, carrier);
 }
 
@@ -1682,7 +1648,6 @@ dispose (GObject *object)
 	nm_clear_g_source (&priv->pppoe_wait_id);
 
 	nm_clear_g_source (&priv->dcb_timeout_id);
-	nm_clear_g_signal_handler (self, &priv->dcb_carrier_id);
 
 	G_OBJECT_CLASS (nm_device_ethernet_parent_class)->dispose (object);
 }
@@ -1810,8 +1775,24 @@ create_device (NMDeviceFactory *factory,
 	                                  NULL);
 }
 
+static gboolean
+match_connection (NMDeviceFactory *factory, NMConnection *connection)
+{
+	const char *type = nm_connection_get_connection_type (connection);
+	NMSettingPppoe *s_pppoe;
+
+	if (nm_streq (type, NM_SETTING_WIRED_SETTING_NAME))
+		return TRUE;
+
+	nm_assert (nm_streq (type, NM_SETTING_PPPOE_SETTING_NAME));
+	s_pppoe = nm_connection_get_setting_pppoe (connection);
+
+	return !nm_setting_pppoe_get_parent (s_pppoe);
+}
+
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (ETHERNET, Ethernet, ethernet,
 	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_ETHERNET)
 	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_PPPOE_SETTING_NAME),
 	factory_class->create_device = create_device;
+	factory_class->match_connection = match_connection;
 );
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index f512b8b2..97f011c5 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -30,6 +30,8 @@
 
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nm-setting-bluetooth.h"
 
 #define PLUGIN_PREFIX "libnm-device-plugin-"
 
@@ -55,13 +57,12 @@ nm_device_factory_emit_component_added (NMDeviceFactory *factory, GObject *compo
 	gboolean consumed = FALSE;
 
 	g_return_val_if_fail (NM_IS_DEVICE_FACTORY (factory), FALSE);
-	g_return_val_if_fail (G_IS_OBJECT (component), FALSE);
 
 	g_signal_emit (factory, signals[COMPONENT_ADDED], 0, component, &consumed);
 	return consumed;
 }
 
-void
+static void
 nm_device_factory_get_supported_types (NMDeviceFactory *factory,
                                        const NMLinkType **out_link_types,
                                        const char *const**out_setting_types)
@@ -91,56 +92,26 @@ nm_device_factory_create_device (NMDeviceFactory *factory,
                                  GError **error)
 {
 	NMDeviceFactoryClass *klass;
-	const NMLinkType *link_types = NULL;
-	const char *const*setting_types = NULL;
-	int i;
 	NMDevice *device;
 	gboolean ignore = FALSE;
 
 	g_return_val_if_fail (factory, NULL);
 	g_return_val_if_fail (iface && *iface, NULL);
-	g_return_val_if_fail (plink || connection, NULL);
-	g_return_val_if_fail (!plink || !connection, NULL);
-
-	nm_device_factory_get_supported_types (factory, &link_types, &setting_types);
-
-	NM_SET_OUT (out_ignore, FALSE);
-
 	if (plink) {
+		g_return_val_if_fail (!connection, NULL);
 		g_return_val_if_fail (strcmp (iface, plink->name) == 0, NULL);
-
-		for (i = 0; link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) {
-			if (plink->type == link_types[i])
-				break;
-		}
-
-		if (link_types[i] == NM_LINK_TYPE_UNKNOWN) {
-			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
-			             "Device factory %s does not support link type %s (%d)",
-			             G_OBJECT_TYPE_NAME (factory),
-			             plink->kind, plink->type);
-			return NULL;
-		}
-	} else if (connection) {
-		for (i = 0; setting_types && setting_types[i]; i++) {
-			if (nm_connection_is_type (connection, setting_types[i]))
-				break;
-		}
-
-		if (!setting_types[i]) {
-			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
-			             "Device factory %s does not support connection type %s",
-			             G_OBJECT_TYPE_NAME (factory),
-			             nm_connection_get_connection_type (connection));
-			return NULL;
-		}
-	}
+		nm_assert (factory == nm_device_factory_manager_find_factory_for_link_type (plink->type));
+	} else if (connection)
+		nm_assert (factory == nm_device_factory_manager_find_factory_for_connection (connection));
+	else
+		g_return_val_if_reached (NULL);
 
 	klass = NM_DEVICE_FACTORY_GET_CLASS (factory);
 	if (!klass->create_device) {
 		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
 		             "Device factory %s cannot manage new devices",
 		             G_OBJECT_TYPE_NAME (factory));
+		NM_SET_OUT (out_ignore, FALSE);
 		return NULL;
 	}
 
@@ -253,51 +224,35 @@ _cleanup (void)
 	g_clear_pointer (&factories_by_setting, g_hash_table_unref);
 }
 
-static NMDeviceFactory *
-find_factory (const NMLinkType *needle_link_types,
-              const char *const*needle_setting_types)
-{
-	NMDeviceFactory *found;
-	guint i;
-
-	g_return_val_if_fail (factories_by_link, NULL);
-	g_return_val_if_fail (factories_by_setting, NULL);
-
-	/* NMLinkType search */
-	for (i = 0; needle_link_types && needle_link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) {
-		found = g_hash_table_lookup (factories_by_link, GUINT_TO_POINTER (needle_link_types[i]));
-		if (found)
-			return found;
-	}
-
-	/* NMSetting name search */
-	for (i = 0; needle_setting_types && needle_setting_types[i]; i++) {
-		found = g_hash_table_lookup (factories_by_setting, needle_setting_types[i]);
-		if (found)
-			return found;
-	}
-
-	return NULL;
-}
-
 NMDeviceFactory *
 nm_device_factory_manager_find_factory_for_link_type (NMLinkType link_type)
 {
-	const NMLinkType ltypes[2] = { link_type, NM_LINK_TYPE_NONE };
+	g_return_val_if_fail (factories_by_link, NULL);
 
-	if (link_type == NM_LINK_TYPE_UNKNOWN)
-		return NULL;
-	g_return_val_if_fail (link_type > NM_LINK_TYPE_UNKNOWN, NULL);
-	return find_factory (ltypes, NULL);
+	return g_hash_table_lookup (factories_by_link, GUINT_TO_POINTER (link_type));
 }
 
 NMDeviceFactory *
 nm_device_factory_manager_find_factory_for_connection (NMConnection *connection)
 {
-	const char *const stypes[2] = { nm_connection_get_connection_type (connection), NULL };
+	NMDeviceFactoryClass *klass;
+	NMDeviceFactory *factory;
+	const char *type;
+	GSList *list;
 
-	g_assert (stypes[0]);
-	return find_factory (NULL, stypes);
+	g_return_val_if_fail (factories_by_setting, NULL);
+
+	type = nm_connection_get_connection_type (connection);
+	list = g_hash_table_lookup (factories_by_setting, type);
+
+	for (; list; list = g_slist_next (list)) {
+		factory = list->data;
+		klass = NM_DEVICE_FACTORY_GET_CLASS (factory);
+		if (!klass->match_connection || klass->match_connection (factory, connection))
+			return factory;
+	}
+
+	return NULL;
 }
 
 void
@@ -318,9 +273,11 @@ nm_device_factory_manager_for_each_factory (NMDeviceFactoryManagerFactoryFunc ca
 
 	if (factories_by_setting) {
 		g_hash_table_iter_init (&iter, factories_by_setting);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &factory)) {
-			if (!g_slist_find (list, factory))
-				list = g_slist_prepend (list, factory);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &list_iter)) {
+			for (; list_iter; list_iter = g_slist_next (list_iter)) {
+				if (!g_slist_find (list, list_iter->data))
+					list = g_slist_prepend (list, list_iter->data);
+			}
 		}
 	}
 
@@ -332,36 +289,33 @@ nm_device_factory_manager_for_each_factory (NMDeviceFactoryManagerFactoryFunc ca
 
 static gboolean
 _add_factory (NMDeviceFactory *factory,
-              gboolean check_duplicates,
               const char *path,
               NMDeviceFactoryManagerFactoryFunc callback,
               gpointer user_data)
 {
-	NMDeviceFactory *found = NULL;
 	const NMLinkType *link_types = NULL;
 	const char *const*setting_types = NULL;
+	GSList *list, *list2;
 	int i;
 
 	g_return_val_if_fail (factories_by_link, FALSE);
 	g_return_val_if_fail (factories_by_setting, FALSE);
 
 	nm_device_factory_get_supported_types (factory, &link_types, &setting_types);
-	if (check_duplicates) {
-		found = find_factory (link_types, setting_types);
-		if (found) {
-			nm_log_warn (LOGD_PLATFORM, "Loading device plugin failed: multiple plugins "
-			             "for same type (using '%s' instead of '%s')",
-			             (char *) g_object_get_qdata (G_OBJECT (found), plugin_path_quark ()),
-			             path);
-			return FALSE;
-		}
-	}
 
 	g_object_set_qdata_full (G_OBJECT (factory), plugin_path_quark (), g_strdup (path), g_free);
 	for (i = 0; link_types && link_types[i] > NM_LINK_TYPE_UNKNOWN; i++)
 		g_hash_table_insert (factories_by_link, GUINT_TO_POINTER (link_types[i]), g_object_ref (factory));
-	for (i = 0; setting_types && setting_types[i]; i++)
-		g_hash_table_insert (factories_by_setting, (char *) setting_types[i], g_object_ref (factory));
+	for (i = 0; setting_types && setting_types[i]; i++) {
+		list = g_hash_table_lookup (factories_by_setting, (char *) setting_types[i]);
+		if (list) {
+			list2 = g_slist_append (list, g_object_ref (factory));
+			nm_assert (list == list2);
+		} else {
+			list = g_slist_append (list, g_object_ref (factory));
+			g_hash_table_insert (factories_by_setting, (char *) setting_types[i], list);
+		}
+	}
 
 	callback (factory, user_data);
 
@@ -377,7 +331,13 @@ _load_internal_factory (GType factory_gtype,
 	NMDeviceFactory *factory;
 
 	factory = (NMDeviceFactory *) g_object_new (factory_gtype, NULL);
-	_add_factory (factory, FALSE, "internal", callback, user_data);
+	_add_factory (factory, "internal", callback, user_data);
+}
+
+static void
+factories_list_unref (GSList *list)
+{
+	g_slist_free_full (list, g_object_unref);
 }
 
 void
@@ -392,7 +352,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 	g_return_if_fail (factories_by_setting == NULL);
 
 	factories_by_link = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
-	factories_by_setting = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+	factories_by_setting = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, (GDestroyNotify) factories_list_unref);
 
 #define _ADD_INTERNAL(get_type_fcn) \
 	G_STMT_START { \
@@ -409,6 +369,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 	_ADD_INTERNAL (nm_ip_tunnel_device_factory_get_type);
 	_ADD_INTERNAL (nm_macsec_device_factory_get_type);
 	_ADD_INTERNAL (nm_macvlan_device_factory_get_type);
+	_ADD_INTERNAL (nm_ppp_device_factory_get_type);
 	_ADD_INTERNAL (nm_tun_device_factory_get_type);
 	_ADD_INTERNAL (nm_veth_device_factory_get_type);
 	_ADD_INTERNAL (nm_vlan_device_factory_get_type);
@@ -452,7 +413,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 		}
 		g_clear_error (&error);
 
-		_add_factory (factory, TRUE, g_module_name (plugin), callback, user_data);
+		_add_factory (factory, g_module_name (plugin), callback, user_data);
 
 		g_object_unref (factory);
 	}
diff --git a/src/devices/nm-device-factory.h b/src/devices/nm-device-factory.h
index ff105da7..33b596e6 100644
--- a/src/devices/nm-device-factory.h
+++ b/src/devices/nm-device-factory.h
@@ -72,11 +72,19 @@ typedef struct {
 	void (*start)                 (NMDeviceFactory *factory);
 
 	/**
+	 * match_connection:
+	 * @connection: the #NMConnection
+	 *
+	 * Check if the factory supports the given connection.
+	 */
+	gboolean (*match_connection)  (NMDeviceFactory *factory, NMConnection *connection);
+
+	/**
 	 * get_connection_parent:
 	 * @factory: the #NMDeviceFactory
 	 * @connection: the #NMConnection to return the parent name for, if supported
 	 *
-	 * Given a connection, returns the a parent interface name, parent connection
+	 * Given a connection, returns the parent interface name, parent connection
 	 * UUID, or parent device permanent hardware address for @connection.
 	 *
 	 * Returns: the parent interface name, parent connection UUID, parent
@@ -140,11 +148,15 @@ typedef struct {
 	 * @factory: the #NMDeviceFactory
 	 * @component: a new component which existing devices may wish to claim
 	 *
-	 * The factory emits this signal when it finds a new component.  For example,
-	 * the WWAN factory may indicate that a new modem is available, which an
-	 * existing Bluetooth device may wish to claim.  If no device claims the
-	 * component, the plugin is allowed to create a new #NMDevice instance for
-	 * that component and emit the "device-added" signal.
+	 * The factory emits this signal when an appearance of some component
+	 * native to it could be interesting to some of the already existing devices.
+	 * The devices then indicate if they took interest in claiming the component.
+	 *
+	 * For example, the WWAN factory may indicate that a new modem is available,
+	 * which an existing Bluetooth device may wish to claim. It emits a signal
+	 * passing the modem instance around to see if any device claims it.
+	 * If no device claims the component, the plugin is allowed to create a new
+	 * #NMDevice instance for that component and emit the "device-added" signal.
 	 *
 	 * Returns: %TRUE if the component was claimed by a device, %FALSE if not
 	 */
@@ -174,10 +186,6 @@ typedef NMDeviceFactory * (*NMDeviceFactoryCreateFunc) (GError **error);
 
 /*****************************************************************************/
 
-void       nm_device_factory_get_supported_types (NMDeviceFactory *factory,
-                                                  const NMLinkType **out_link_types,
-                                                  const char *const**out_setting_types);
-
 const char *nm_device_factory_get_connection_parent (NMDeviceFactory *factory,
                                                      NMConnection *connection);
 
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 7e041270..09ad2855 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -269,13 +269,13 @@ create_and_realize (NMDevice *device,
 	}
 
 	if (!parent) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "InfiniBand partitions can not be created without a parent interface");
 		return FALSE;
 	}
 
 	if (!NM_IS_DEVICE_INFINIBAND (parent)) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "Parent interface %s must be an InfiniBand interface",
 		             nm_device_get_iface (parent));
 		return FALSE;
@@ -283,7 +283,7 @@ create_and_realize (NMDevice *device,
 
 	priv->parent_ifindex = nm_device_get_ifindex (parent);
 	if (priv->parent_ifindex <= 0) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "failed to get InfiniBand parent %s ifindex",
 		             nm_device_get_iface (parent));
 		return FALSE;
@@ -295,7 +295,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create InfiniBand P_Key interface '%s' for '%s': %s",
 		             nm_device_get_iface (device),
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -324,7 +324,7 @@ unrealize (NMDevice *device, GError **error)
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to remove InfiniBand P_Key interface '%s': %s",
 		             nm_device_get_iface (device),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 2f505ef4..af3cfe4c 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -442,40 +442,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-match_parent (NMDevice *dev_parent, const char *setting_parent)
-{
-	g_return_val_if_fail (setting_parent, FALSE);
-
-	if (!dev_parent)
-		return FALSE;
-
-	if (nm_utils_is_uuid (setting_parent)) {
-		NMActRequest *parent_req;
-		NMConnection *parent_connection;
-
-		/* If the parent is a UUID, the connection matches if our parent
-		 * device has that connection activated.
-		 */
-		parent_req = nm_device_get_act_request (dev_parent);
-		if (!parent_req)
-			return FALSE;
-
-		parent_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (parent_req));
-		if (!parent_connection)
-			return FALSE;
-
-		if (g_strcmp0 (setting_parent, nm_connection_get_uuid (parent_connection)) != 0)
-			return FALSE;
-	} else {
-		/* interface name */
-		if (g_strcmp0 (setting_parent, nm_device_get_ip_iface (dev_parent)) != 0)
-			return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
@@ -496,10 +462,8 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 	if (nm_device_is_real (device)) {
 		/* Check parent interface; could be an interface name or a UUID */
 		parent = nm_setting_ip_tunnel_get_parent (s_ip_tunnel);
-		if (parent) {
-			if (!match_parent (nm_device_parent_get_device (device), parent))
-				return FALSE;
-		}
+		if (parent && !nm_device_match_parent (device, parent))
+			return FALSE;
 
 		if (!address_equal_pp (priv->addr_family,
 		                       nm_setting_ip_tunnel_get_local (s_ip_tunnel),
@@ -647,7 +611,7 @@ create_and_realize (NMDevice *device,
 			             "Failed to create GRE interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string (plerr));
+			             nm_platform_error_to_string_a (plerr));
 			return FALSE;
 		}
 		break;
@@ -670,10 +634,10 @@ create_and_realize (NMDevice *device,
 		plerr = nm_platform_link_sit_add (nm_device_get_platform (device), iface, &lnk_sit, out_plink);
 		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
-					"Failed to create SIT interface '%s' for '%s': %s",
-					iface,
-					nm_connection_get_id (connection),
-					nm_platform_error_to_string (plerr));
+			             "Failed to create SIT interface '%s' for '%s': %s",
+			             iface,
+			             nm_connection_get_id (connection),
+			             nm_platform_error_to_string_a (plerr));
 			return FALSE;
 		}
 		break;
@@ -696,10 +660,10 @@ create_and_realize (NMDevice *device,
 		plerr = nm_platform_link_ipip_add (nm_device_get_platform (device), iface, &lnk_ipip, out_plink);
 		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
-					"Failed to create IPIP interface '%s' for '%s': %s",
-					iface,
-					nm_connection_get_id (connection),
-					nm_platform_error_to_string (plerr));
+			             "Failed to create IPIP interface '%s' for '%s': %s",
+			             iface,
+			             nm_connection_get_id (connection),
+			             nm_platform_error_to_string_a (plerr));
 			return FALSE;
 		}
 		break;
@@ -728,7 +692,7 @@ create_and_realize (NMDevice *device,
 			             "Failed to create IPIP interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string (plerr));
+			             nm_platform_error_to_string_a (plerr));
 			return FALSE;
 		}
 		break;
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 8add3f6f..95587278 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -477,25 +477,20 @@ handle_auth_or_fail (NMDeviceMacsec *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
+	NMDeviceMacsecPrivate *priv;
 	const char *setting_name;
-	int tries_left;
 	NMConnection *applied_connection;
-	NMSettingsConnection *settings_connection;
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-	settings_connection = nm_act_request_get_settings_connection (req);
+	priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
 
-	tries_left = nm_settings_connection_get_autoconnect_retries (settings_connection);
-	if (tries_left == 0)
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
-	if (tries_left > 0)
-		nm_settings_connection_set_autoconnect_retries (settings_connection, tries_left - 1);
-
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
 	nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
 
+	applied_connection = nm_act_request_get_applied_connection (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (setting_name) {
 		macsec_secrets_get_secrets (self, setting_name,
@@ -692,7 +687,7 @@ create_and_realize (NMDevice *device,
 	g_assert (s_macsec);
 
 	if (!parent) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "MACsec devices can not be created without a parent interface");
 		return FALSE;
 	}
@@ -720,7 +715,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create macsec interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -739,21 +734,6 @@ link_changed (NMDevice *device,
 
 
 static void
-reset_autoconnect_retries (NMDevice *device)
-{
-	NMActRequest *req;
-	NMSettingsConnection *connection;
-
-	req = nm_device_get_act_request (device);
-	if (req) {
-		connection = nm_act_request_get_settings_connection (req);
-		g_return_if_fail (connection);
-		/* Reset autoconnect retries on success, failure, or when deactivating */
-		nm_settings_connection_reset_autoconnect_retries (connection);
-	}
-}
-
-static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -761,11 +741,6 @@ device_state_changed (NMDevice *device,
 {
 	if (new_state > NM_DEVICE_STATE_ACTIVATED)
 		macsec_secrets_cancel (NM_DEVICE_MACSEC (device));
-
-	if (   new_state == NM_DEVICE_STATE_ACTIVATED
-	    || new_state == NM_DEVICE_STATE_FAILED
-	    || new_state == NM_DEVICE_STATE_DISCONNECTED)
-		reset_autoconnect_retries (device);
 }
 
 /******************************************************************/
@@ -822,7 +797,7 @@ get_property (GObject *object, guint prop_id,
 }
 
 static void
-nm_device_macsec_init (NMDeviceMacsec * self)
+nm_device_macsec_init (NMDeviceMacsec *self)
 {
 }
 
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index cea2b984..2a461543 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -234,7 +234,7 @@ create_and_realize (NMDevice *device,
 	parent_ifindex = parent ? nm_device_get_ifindex (parent) : 0;
 
 	if (parent_ifindex <= 0) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "MACVLAN devices can not be created without a parent interface");
 		g_return_val_if_fail (!parent, FALSE);
 		return FALSE;
@@ -258,7 +258,7 @@ create_and_realize (NMDevice *device,
 		             lnk.tap ? "macvtap" : "macvlan",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -286,69 +286,6 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 
 /*****************************************************************************/
 
-
-static gboolean
-match_parent (NMDeviceMacvlan *self, const char *parent)
-{
-	NMDevice *parent_device;
-
-	g_return_val_if_fail (parent != NULL, FALSE);
-
-	parent_device = nm_device_parent_get_device (NM_DEVICE (self));
-	if (!parent_device)
-		return FALSE;
-
-	if (nm_utils_is_uuid (parent)) {
-		NMActRequest *parent_req;
-		NMConnection *parent_connection;
-
-		/* If the parent is a UUID, the connection matches if our parent
-		 * device has that connection activated.
-		 */
-
-		parent_req = nm_device_get_act_request (parent_device);
-		if (!parent_req)
-			return FALSE;
-
-		parent_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (parent_req));
-		if (!parent_connection)
-			return FALSE;
-
-		if (g_strcmp0 (parent, nm_connection_get_uuid (parent_connection)) != 0)
-			return FALSE;
-	} else {
-		/* interface name */
-		if (g_strcmp0 (parent, nm_device_get_ip_iface (parent_device)) != 0)
-			return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
-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 *device, NMConnection *connection)
 {
@@ -378,11 +315,11 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		/* Check parent interface; could be an interface name or a UUID */
 		parent = nm_setting_macvlan_get_parent (s_macvlan);
 		if (parent) {
-			if (!match_parent (NM_DEVICE_MACVLAN (device), parent))
+			if (!nm_device_match_parent (device, parent))
 				return FALSE;
 		} else {
 			/* Parent could be a MAC address in an NMSettingWired */
-			if (!match_hwaddr (device, connection, TRUE))
+			if (!nm_device_match_hwaddr (device, connection, TRUE))
 				return FALSE;
 		}
 	}
@@ -419,7 +356,7 @@ complete_connection (NMDevice *device,
 	 * settings, then there's not enough information to complete the setting.
 	 */
 	if (   !nm_setting_macvlan_get_parent (s_macvlan)
-	    && !match_hwaddr (device, connection, TRUE)) {
+	    && !nm_device_match_hwaddr (device, connection, TRUE)) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "The 'macvlan' setting had no interface name, parent, or hardware address.");
 		return FALSE;
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
new file mode 100644
index 00000000..8b3968d5
--- /dev/null
+++ b/src/devices/nm-device-ppp.c
@@ -0,0 +1,350 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-ppp.h"
+
+#include "nm-act-request.h"
+#include "nm-device-factory.h"
+#include "nm-device-private.h"
+#include "nm-manager.h"
+#include "nm-setting-pppoe.h"
+#include "platform/nm-platform.h"
+#include "ppp/nm-ppp-manager.h"
+#include "ppp/nm-ppp-manager-call.h"
+#include "ppp/nm-ppp-status.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.Ppp.h"
+
+#include "nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDevicePpp);
+
+/*****************************************************************************/
+
+typedef struct _NMDevicePppPrivate {
+	NMPPPManager *ppp_manager;
+	NMIP4Config  *pending_ip4_config;
+	char         *pending_ifname;
+} NMDevicePppPrivate;
+
+struct _NMDevicePpp {
+	NMDevice parent;
+	NMDevicePppPrivate _priv;
+};
+
+struct _NMDevicePppClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDevicePpp, nm_device_ppp, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_PPP_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDevicePpp, NM_IS_DEVICE_PPP)
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection)
+{
+	NMSettingPppoe *s_pppoe;
+
+	if (!NM_DEVICE_CLASS (nm_device_ppp_parent_class)->check_connection_compatible (device, connection))
+		return FALSE;
+
+	if (!nm_streq0 (nm_connection_get_connection_type (connection),
+	                NM_SETTING_PPPOE_SETTING_NAME))
+		return FALSE;
+
+	s_pppoe = nm_connection_get_setting_pppoe (connection);
+	nm_assert (s_pppoe);
+
+	return !!nm_setting_pppoe_get_parent (s_pppoe);
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *device)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static void
+ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+
+	switch (status) {
+	case NM_PPP_STATUS_DISCONNECT:
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_DISCONNECT);
+		break;
+	case NM_PPP_STATUS_DEAD:
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
+		break;
+	case NM_PPP_STATUS_RUNNING:
+		nm_device_activate_schedule_stage3_ip_config_start (device);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+ppp_ip4_config (NMPPPManager *ppp_manager,
+                const char *iface,
+                NMIP4Config *config,
+                gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	gboolean renamed;
+
+	_LOGT (LOGD_DEVICE | LOGD_PPP, "received IPv4 config from pppd");
+
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
+		if (nm_device_activate_ip4_state_in_conf (device)) {
+			if (!nm_device_take_over_link (device, iface, &renamed)) {
+				nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+				                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+				return;
+			}
+			if (renamed)
+				nm_manager_remove_device (nm_manager_get (), iface, NM_DEVICE_TYPE_PPP);
+
+			nm_device_activate_schedule_ip4_config_result (device, config);
+			return;
+		}
+	} else {
+		if (priv->pending_ip4_config)
+			g_object_unref (priv->pending_ip4_config);
+		priv->pending_ip4_config = g_object_ref (config);
+		g_free (priv->pending_ifname);
+		priv->pending_ifname = g_strdup (iface);
+	}
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	NMSettingPppoe *s_pppoe;
+	NMActRequest *req;
+	GError *error = NULL;
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
+	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
+
+	g_clear_object (&priv->pending_ip4_config);
+	nm_clear_g_free (&priv->pending_ifname);
+
+	priv->ppp_manager = nm_ppp_manager_create (nm_setting_pppoe_get_parent (s_pppoe), &error);
+
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     nm_device_get_route_table (device, AF_INET, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET),
+		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET6));
+	}
+
+	if (   !priv->ppp_manager
+	    || !nm_ppp_manager_start (priv->ppp_manager, req,
+	                              nm_setting_pppoe_get_username (s_pppoe),
+	                              30, 0, &error)) {
+		_LOGW (LOGD_DEVICE | LOGD_PPP, "PPPoE failed to start: %s", error->message);
+		g_error_free (error);
+
+		g_clear_object (&priv->ppp_manager);
+
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
+	                  G_CALLBACK (ppp_state_changed),
+	                  self);
+	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
+	                  G_CALLBACK (ppp_ip4_config),
+	                  self);
+
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+static NMActStageReturn
+act_stage3_ip4_config_start (NMDevice *device,
+                             NMIP4Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	gboolean renamed;
+
+	if (priv->pending_ip4_config) {
+		if  (!nm_device_take_over_link (device, priv->pending_ifname, &renamed))
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		if (renamed)
+			nm_manager_remove_device (nm_manager_get (), priv->pending_ifname, NM_DEVICE_TYPE_PPP);
+		if (out_config)
+			*out_config = g_steal_pointer (&priv->pending_ip4_config);
+		else
+			g_clear_object (&priv->pending_ip4_config);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	/* Wait IPCP termination */
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	int parent_ifindex;
+
+	if (!parent) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+		             "PPP devices can not be created without a parent interface");
+		return FALSE;
+	}
+
+	parent_ifindex = nm_device_get_ifindex (parent);
+	g_warn_if_fail (parent_ifindex > 0);
+
+	nm_device_parent_set_ifindex (device, parent_ifindex);
+
+	/* The interface is created later */
+
+	return TRUE;
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+
+	if (priv->ppp_manager) {
+		nm_ppp_manager_stop_sync (priv->ppp_manager);
+		g_clear_object (&priv->ppp_manager);
+	}
+}
+
+static void
+nm_device_ppp_init (NMDevicePpp *self)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDevicePpp *self = NM_DEVICE_PPP (object);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+
+	g_clear_object (&priv->pending_ip4_config);
+	nm_clear_g_free (&priv->pending_ifname);
+
+	G_OBJECT_CLASS (nm_device_ppp_parent_class)->dispose (object);
+}
+
+static void
+nm_device_ppp_class_init (NMDevicePppClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
+
+	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_PPPOE_SETTING_NAME, NM_LINK_TYPE_PPP)
+
+	object_class->dispose = dispose;
+
+	parent_class->act_stage2_config = act_stage2_config;
+	parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	parent_class->check_connection_compatible = check_connection_compatible;
+	parent_class->create_and_realize = create_and_realize;
+	parent_class->deactivate = deactivate;
+	parent_class->get_generic_capabilities = get_generic_capabilities;
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_PPP_SKELETON,
+	                                        NULL);
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_PPP_DEVICE_FACTORY (nm_ppp_device_factory_get_type ())
+#define NM_PPP_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PPP_DEVICE_FACTORY, NMPppDeviceFactory))
+
+static NMDevice *
+create_device (NMDeviceFactory *factory,
+               const char *iface,
+               const NMPlatformLink *plink,
+               NMConnection *connection,
+               gboolean *out_ignore)
+{
+	return (NMDevice *) g_object_new (NM_TYPE_DEVICE_PPP,
+	                                  NM_DEVICE_IFACE, iface,
+	                                  NM_DEVICE_TYPE_DESC, "Ppp",
+	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_PPP,
+	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_PPP,
+	                                  NULL);
+}
+
+static gboolean
+match_connection (NMDeviceFactory *factory, NMConnection *connection)
+{
+	NMSettingPppoe *s_pppoe;
+
+	s_pppoe = nm_connection_get_setting_pppoe (connection);
+	nm_assert (s_pppoe);
+
+	return !!nm_setting_pppoe_get_parent (s_pppoe);
+}
+
+static const char *
+get_connection_parent (NMDeviceFactory *factory, NMConnection *connection)
+{
+	NMSettingPppoe *s_pppoe;
+
+	nm_assert (nm_connection_is_type (connection, NM_SETTING_PPPOE_SETTING_NAME));
+
+	s_pppoe = nm_connection_get_setting_pppoe (connection);
+	nm_assert (s_pppoe);
+
+	return nm_setting_pppoe_get_parent (s_pppoe);
+}
+
+static char *
+get_connection_iface (NMDeviceFactory *factory,
+                      NMConnection *connection,
+                      const char *parent_iface)
+{
+	nm_assert (nm_connection_is_type (connection, NM_SETTING_PPPOE_SETTING_NAME));
+
+	if (!parent_iface)
+		return NULL;
+
+	return g_strdup (nm_connection_get_interface_name (connection));
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL (PPP, Ppp, ppp,
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_PPP)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_PPPOE_SETTING_NAME),
+	factory_class->get_connection_parent = get_connection_parent;
+	factory_class->get_connection_iface = get_connection_iface;
+	factory_class->create_device = create_device;
+	factory_class->match_connection = match_connection;
+);
diff --git a/src/devices/nm-device-ppp.h b/src/devices/nm-device-ppp.h
new file mode 100644
index 00000000..aaa18b9b
--- /dev/null
+++ b/src/devices/nm-device-ppp.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_PPP_H__
+#define __NETWORKMANAGER_DEVICE_PPP_H__
+
+#define NM_TYPE_DEVICE_PPP              (nm_device_ppp_get_type ())
+#define NM_DEVICE_PPP(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_PPP, NMDevicePpp))
+#define NM_DEVICE_PPP_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_PPP, NMDevicePppClass))
+#define NM_IS_DEVICE_PPP(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_PPP))
+#define NM_IS_DEVICE_PPP_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_PPP))
+#define NM_DEVICE_PPP_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_PPP, NMDevicePppClass))
+
+typedef struct _NMDevicePpp NMDevicePpp;
+typedef struct _NMDevicePppClass NMDevicePppClass;
+
+GType nm_device_ppp_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_PPP_H__ */
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 9eccafdc..f1486c54 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -57,6 +57,8 @@ gboolean nm_device_bring_up (NMDevice *self, gboolean wait, gboolean *no_firmwar
 
 void nm_device_take_down (NMDevice *self, gboolean block);
 
+gboolean nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed);
+
 gboolean nm_device_hw_addr_set (NMDevice *device,
                                 const char *addr,
                                 const char *detail,
@@ -83,7 +85,6 @@ gboolean nm_device_activate_ip6_state_in_conf (NMDevice *device);
 gboolean nm_device_activate_ip6_state_in_wait (NMDevice *device);
 gboolean nm_device_activate_ip6_state_done (NMDevice *device);
 
-void nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout);
 void nm_device_set_dhcp_anycast_address (NMDevice *device, const char *addr);
 
 gboolean nm_device_dhcp4_renew (NMDevice *device, gboolean release);
@@ -91,10 +92,6 @@ gboolean nm_device_dhcp6_renew (NMDevice *device, gboolean release);
 
 void nm_device_recheck_available_connections (NMDevice *device);
 
-gboolean nm_device_get_enslaved (NMDevice *device);
-
-NMDevice *nm_device_master_get_slave_by_ifindex (NMDevice *dev, int ifindex);
-
 void nm_device_master_check_slave_physical_port (NMDevice *self, NMDevice *slave,
                                                  NMLogDomain log_domain);
 
@@ -121,6 +118,8 @@ gint64 nm_device_get_configured_mtu_from_connection_default (NMDevice *self,
 
 guint32 nm_device_get_configured_mtu_for_wired (NMDevice *self, gboolean *out_is_user_config);
 
+void nm_device_commit_mtu (NMDevice *self);
+
 /*****************************************************************************/
 
 #define NM_DEVICE_CLASS_DECLARE_TYPES(klass, conn_type, ...) \
@@ -135,4 +134,9 @@ gboolean _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setti
 #define nm_device_hash_check_invalid_keys(hash, setting_name, error, ...) \
 	_nm_device_hash_check_invalid_keys (hash, setting_name, error, ((const char *[]) { __VA_ARGS__, NULL }))
 
+gboolean nm_device_match_parent (NMDevice *device, const char *parent);
+gboolean nm_device_match_hwaddr (NMDevice *device,
+                                 NMConnection *connection,
+                                 gboolean fail_if_no_hwaddr);
+
 #endif	/* NM_DEVICE_PRIVATE_H */
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index b4af4416..a7d7c0bf 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -244,7 +244,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create TUN/TAP interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index 11916c59..a8c4bcc8 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -37,12 +37,8 @@ _LOG_DECLARE_SELF(NMDeviceVeth);
 
 /*****************************************************************************/
 
-typedef struct {
-} NMDeviceVethPrivate;
-
 struct _NMDeviceVeth {
 	NMDeviceEthernet parent;
-	NMDeviceVethPrivate _priv;
 };
 
 struct _NMDeviceVethClass {
@@ -57,8 +53,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceVeth,
 
 G_DEFINE_TYPE (NMDeviceVeth, nm_device_veth, NM_TYPE_DEVICE_ETHERNET)
 
-#define NM_DEVICE_VETH_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceVeth, NM_IS_DEVICE_VETH)
-
 /*****************************************************************************/
 
 static void
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index a74da8f2..e30dae74 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -51,6 +51,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceVlan,
 typedef struct {
 	gulong parent_state_id;
 	gulong parent_hwaddr_id;
+	gulong parent_mtu_id;
 	guint vlan_id;
 } NMDeviceVlanPrivate;
 
@@ -86,6 +87,17 @@ parent_state_changed (NMDevice *parent,
 }
 
 static void
+parent_mtu_maybe_changed (NMDevice *parent,
+                          GParamSpec *pspec,
+                          gpointer user_data)
+{
+	/* the MTU of a VLAN device is limited by the parent's MTU.
+	 *
+	 * When the parent's MTU changes, try to re-set the MTU. */
+	nm_device_commit_mtu (user_data);
+}
+
+static void
 parent_hwaddr_maybe_changed (NMDevice *parent,
                              GParamSpec *pspec,
                              gpointer user_data)
@@ -143,6 +155,7 @@ parent_changed_notify (NMDevice *device,
 	 *  parent_changed_notify(). */
 	nm_clear_g_signal_handler (old_parent, &priv->parent_state_id);
 	nm_clear_g_signal_handler (old_parent, &priv->parent_hwaddr_id);
+	nm_clear_g_signal_handler (old_parent, &priv->parent_mtu_id);
 
 	if (new_parent) {
 		priv->parent_state_id = g_signal_connect (new_parent,
@@ -154,6 +167,10 @@ parent_changed_notify (NMDevice *device,
 		                                           G_CALLBACK (parent_hwaddr_maybe_changed), device);
 		parent_hwaddr_maybe_changed (new_parent, NULL, self);
 
+		priv->parent_mtu_id = g_signal_connect (new_parent, "notify::" NM_DEVICE_MTU,
+		                                        G_CALLBACK (parent_mtu_maybe_changed), device);
+		parent_mtu_maybe_changed (new_parent, NULL, self);
+
 		/* Set parent-dependent unmanaged flag */
 		nm_device_set_unmanaged_by_flags (device,
 		                                  NM_UNMANAGED_PARENT,
@@ -231,11 +248,20 @@ create_and_realize (NMDevice *device,
 	g_assert (s_vlan);
 
 	if (!parent) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "VLAN devices can not be created without a parent interface");
 		return FALSE;
 	}
 
+	parent_ifindex = nm_device_get_ifindex (parent);
+	if (parent_ifindex <= 0) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+		             "cannot retrieve ifindex of interface %s (%s)",
+		             nm_device_get_iface (parent),
+		             nm_device_get_type_desc (parent));
+		return FALSE;
+	}
+
 	if (!nm_device_supports_vlans (parent)) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 		             "no support for VLANs on interface %s of type %s",
@@ -244,9 +270,6 @@ create_and_realize (NMDevice *device,
 		return FALSE;
 	}
 
-	parent_ifindex = nm_device_get_ifindex (parent);
-	g_warn_if_fail (parent_ifindex > 0);
-
 	vlan_id = nm_setting_vlan_get_id (s_vlan);
 
 	plerr = nm_platform_link_vlan_add (nm_device_get_platform (device),
@@ -260,7 +283,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create VLAN interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -309,68 +332,6 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 /*****************************************************************************/
 
 static gboolean
-match_parent (NMDeviceVlan *self, const char *parent)
-{
-	NMDevice *parent_device;
-
-	g_return_val_if_fail (parent != NULL, FALSE);
-
-	parent_device = nm_device_parent_get_device (NM_DEVICE (self));
-	if (!parent_device)
-		return FALSE;
-
-	if (nm_utils_is_uuid (parent)) {
-		NMActRequest *parent_req;
-		NMConnection *parent_connection;
-
-		/* If the parent is a UUID, the connection matches if our parent
-		 * device has that connection activated.
-		 */
-
-		parent_req = nm_device_get_act_request (parent_device);
-		if (!parent_req)
-			return FALSE;
-
-		parent_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (parent_req));
-		if (!parent_connection)
-			return FALSE;
-
-		if (g_strcmp0 (parent, nm_connection_get_uuid (parent_connection)) != 0)
-			return FALSE;
-	} else {
-		/* interface name */
-		if (g_strcmp0 (parent, nm_device_get_ip_iface (parent_device)) != 0)
-			return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
-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 *device, NMConnection *connection)
 {
 	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE ((NMDeviceVlan *) device);
@@ -392,11 +353,11 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		/* Check parent interface; could be an interface name or a UUID */
 		parent = nm_setting_vlan_get_parent (s_vlan);
 		if (parent) {
-			if (!match_parent (NM_DEVICE_VLAN (device), parent))
+			if (!nm_device_match_parent (device, parent))
 				return FALSE;
 		} else {
 			/* Parent could be a MAC address in an NMSettingWired */
-			if (!match_hwaddr (device, connection, TRUE))
+			if (!nm_device_match_hwaddr (device, connection, TRUE))
 				return FALSE;
 		}
 	}
@@ -445,7 +406,7 @@ complete_connection (NMDevice *device,
 	 * settings, then there's not enough information to complete the setting.
 	 */
 	if (   !nm_setting_vlan_get_parent (s_vlan)
-	    && !match_hwaddr (device, connection, TRUE)) {
+	    && !nm_device_match_hwaddr (device, connection, TRUE)) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "The 'vlan' setting had no interface name, parent, or hardware address.");
 		return FALSE;
@@ -538,8 +499,10 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	/* Change MAC address to parent's one if needed */
 	parent_device = nm_device_parent_get_device (device);
-	if (parent_device)
+	if (parent_device) {
 		parent_hwaddr_maybe_changed (parent_device, NULL, device);
+		parent_mtu_maybe_changed (parent_device, NULL, device);
+	}
 
 	s_vlan = (NMSettingVlan *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_VLAN);
 	if (s_vlan) {
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index d0b88874..d9efe840 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -223,7 +223,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create VXLAN interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -231,43 +231,6 @@ create_and_realize (NMDevice *device,
 }
 
 static gboolean
-match_parent (NMDeviceVxlan *self, const char *parent)
-{
-	NMDevice *parent_device;
-
-	g_return_val_if_fail (parent != NULL, FALSE);
-
-	parent_device = nm_device_parent_get_device (NM_DEVICE (self));
-	if (!parent_device)
-		return FALSE;
-
-	if (nm_utils_is_uuid (parent)) {
-		NMActRequest *parent_req;
-		NMConnection *parent_connection;
-
-		/* If the parent is a UUID, the connection matches if our parent
-		 * device has that connection activated.
-		 */
-		parent_req = nm_device_get_act_request (parent_device);
-		if (!parent_req)
-			return FALSE;
-
-		parent_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (parent_req));
-		if (!parent_connection)
-			return FALSE;
-
-		if (g_strcmp0 (parent, nm_connection_get_uuid (parent_connection)) != 0)
-			return FALSE;
-	} else {
-		/* interface name */
-		if (g_strcmp0 (parent, nm_device_get_ip_iface (parent_device)) != 0)
-			return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
 address_matches (const char *str, in_addr_t addr4, struct in6_addr *addr6)
 {
 	in_addr_t new_addr4 = 0;
@@ -302,8 +265,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 
 	if (nm_device_is_real (device)) {
 		parent = nm_setting_vxlan_get_parent (s_vxlan);
-		if (   parent
-		    && !match_parent (NM_DEVICE_VXLAN (device), parent))
+		if (parent && !nm_device_match_parent (device, parent))
 			return FALSE;
 
 		if (priv->props.id != nm_setting_vxlan_get_id (s_vxlan))
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,
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 6d17d7c9..350a17b2 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -120,7 +120,6 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 #define NM_DEVICE_TYPE_DESC        "type-desc"      /* Internal only */
 #define NM_DEVICE_RFKILL_TYPE      "rfkill-type"    /* Internal only */
 #define NM_DEVICE_IFINDEX          "ifindex"        /* Internal only */
-#define NM_DEVICE_IS_MASTER        "is-master"      /* Internal only */
 #define NM_DEVICE_MASTER           "master"         /* Internal only */
 #define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */
 
@@ -196,6 +195,10 @@ typedef struct {
 	const char *connection_type;
 	const NMLinkType *link_types;
 
+	/* Whether the device type is a master-type. This depends purely on the
+	 * type (NMDeviceClass), not the actual device instance. */
+	bool is_master:1;
+
 	void (*state_changed) (NMDevice *device,
 	                       NMDeviceState new_state,
 	                       NMDeviceState old_state,
@@ -364,9 +367,6 @@ typedef struct {
 	                                   NMDevice *slave,
 	                                   gboolean configure);
 
-	gboolean        (* have_any_ready_slaves) (NMDevice *self,
-	                                           const GSList *slaves);
-
 	void            (* parent_changed_notify) (NMDevice *self,
 	                                           int old_ifindex,
 	                                           NMDevice *old_parent,
@@ -378,10 +378,10 @@ typedef struct {
 	 * @self: the #NMDevice
 	 * @component: the component (device, modem, etc) which was added
 	 *
-	 * Notifies @self that a new component was added to the Manager.  This
-	 * may include any kind of %GObject subclass, and the device is expected
-	 * to match only specific components they care about, like %NMModem objects
-	 * or %NMDevice objects.
+	 * Notifies @self that a new component that a device might be interested
+	 * in was detected by some device factory. It may include an object of
+	 * %GObject subclass to help the devices decide whether it claims that
+	 * particular object itself and the emitting factory should not.
 	 *
 	 * Returns: %TRUE if the component was claimed exclusively and no further
 	 * devices should be notified of the new component.  %FALSE to indicate
@@ -406,6 +406,9 @@ typedef struct {
 	void            (* reapply_connection) (NMDevice *self,
 	                                        NMConnection *con_old,
 	                                        NMConnection *con_new);
+
+	guint32         (* get_dhcp_timeout) (NMDevice *self,
+	                                      int addr_family);
 } NMDeviceClass;
 
 typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
@@ -416,6 +419,7 @@ typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
 
 GType nm_device_get_type (void);
 
+struct _NMDedupMultiIndex *nm_device_get_multi_index (NMDevice *self);
 NMNetns *nm_device_get_netns (NMDevice *self);
 NMPlatform *nm_device_get_platform (NMDevice *self);
 
@@ -443,9 +447,8 @@ NMDeviceType    nm_device_get_device_type       (NMDevice *dev);
 NMLinkType      nm_device_get_link_type         (NMDevice *dev);
 NMMetered       nm_device_get_metered           (NMDevice *dev);
 
-int             nm_device_get_priority          (NMDevice *dev);
-guint32         nm_device_get_ip4_route_metric  (NMDevice *dev);
-guint32         nm_device_get_ip6_route_metric  (NMDevice *dev);
+guint32         nm_device_get_route_table       (NMDevice *self, int addr_family, gboolean fallback_main);
+guint32         nm_device_get_route_metric      (NMDevice *dev, int addr_family);
 
 const char *    nm_device_get_hw_address        (NMDevice *dev);
 const char *    nm_device_get_permanent_hw_address (NMDevice *self);
@@ -493,6 +496,8 @@ NMSetting *     nm_device_get_applied_setting   (NMDevice *dev, GType setting_ty
 
 void            nm_device_removed               (NMDevice *self, gboolean unconfigure_ip_config);
 
+gboolean        nm_device_ignore_carrier_by_default (NMDevice *self);
+
 gboolean        nm_device_is_available          (NMDevice *dev, NMDeviceCheckDevAvailableFlags flags);
 gboolean        nm_device_has_carrier           (NMDevice *dev);
 
@@ -522,6 +527,7 @@ gboolean nm_device_check_slave_connection_compatible (NMDevice *device, NMConnec
 gboolean nm_device_unmanage_on_quit (NMDevice *self);
 
 gboolean nm_device_spec_match_list (NMDevice *device, const GSList *specs);
+int      nm_device_spec_match_list_full (NMDevice *self, const GSList *specs, int no_match_value);
 
 gboolean nm_device_is_activating (NMDevice *dev);
 gboolean nm_device_autoconnect_allowed (NMDevice *self);
@@ -561,6 +567,10 @@ void nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device);
  *   the settings plugins, such as NM_CONTROLLED=no in ifcfg-rh), it cannot
  *   be overruled and is authorative. That is because users may depend on
  *   dropping a ifcfg-rh file to ensure the device is unmanaged.
+ * @NM_UNMANAGED_USER_CONF: %TRUE when unmanaged by user decision via
+ *   the NetworkManager.conf ("unmanaged" in the [device] section).
+ *   Contray to @NM_UNMANAGED_USER_SETTINGS, this can be overwritten via
+ *   D-Bus.
  * @NM_UNMANAGED_BY_DEFAULT: %TRUE for certain device types where we unmanage
  *   them by default
  * @NM_UNMANAGED_USER_UDEV: %TRUE when unmanaged by user decision (via UDev rule)
@@ -585,6 +595,7 @@ typedef enum { /*< skip >*/
 	/* These flags can be non-effective and be overwritten
 	 * by other flags. */
 	NM_UNMANAGED_BY_DEFAULT    = (1LL <<  8),
+	NM_UNMANAGED_USER_CONF     = (1LL <<  9),
 	NM_UNMANAGED_USER_UDEV     = (1LL << 10),
 	NM_UNMANAGED_EXTERNAL_DOWN = (1LL << 11),
 	NM_UNMANAGED_IS_SLAVE      = (1LL << 12),
@@ -615,6 +626,7 @@ void nm_device_set_unmanaged_by_flags_queue (NMDevice *self,
                                              NMDeviceStateReason reason);
 void nm_device_set_unmanaged_by_user_settings (NMDevice *self);
 void nm_device_set_unmanaged_by_user_udev (NMDevice *self);
+void nm_device_set_unmanaged_by_user_conf (NMDevice *self);
 void nm_device_set_unmanaged_by_quitting (NMDevice *device);
 
 gboolean nm_device_is_nm_owned (NMDevice *device);
@@ -648,6 +660,9 @@ gboolean nm_device_unrealize          (NMDevice *device,
                                        gboolean remove_resources,
                                        GError **error);
 
+void nm_device_update_from_platform_link (NMDevice *self,
+                                          const NMPlatformLink *plink);
+
 gboolean nm_device_get_autoconnect (NMDevice *device);
 void nm_device_set_autoconnect_intern (NMDevice *device, gboolean autoconnect);
 void nm_device_emit_recheck_auto_activate (NMDevice *device);
@@ -694,8 +709,8 @@ gboolean nm_device_owns_iface (NMDevice *device, const char *iface);
 
 NMConnection *nm_device_new_default_connection (NMDevice *self);
 
-const NMPlatformIP4Route *nm_device_get_ip4_default_route (NMDevice *self, gboolean *out_is_assumed);
-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);
 
 void nm_device_spawn_iface_helper (NMDevice *self);
 
@@ -720,6 +735,9 @@ void nm_device_update_initial_hw_address (NMDevice *self);
 void nm_device_update_permanent_hw_address (NMDevice *self, gboolean force_freeze);
 void nm_device_update_dynamic_ip_setup (NMDevice *self);
 guint nm_device_get_supplicant_timeout (NMDevice *self);
+
+gboolean nm_device_auth_retries_try_next (NMDevice *self);
+
 gboolean nm_device_hw_addr_get_cloned (NMDevice *self,
                                        NMConnection *connection,
                                        gboolean is_wifi,
@@ -735,6 +753,16 @@ void nm_device_check_connectivity (NMDevice *self,
                                    gpointer user_data);
 NMConnectivityState nm_device_get_connectivity_state (NMDevice *self);
 
+typedef struct _NMBtVTableNetworkServer NMBtVTableNetworkServer;
+struct _NMBtVTableNetworkServer {
+	gboolean (*is_available) (const NMBtVTableNetworkServer *vtable,
+	                          const char *addr);
+	gboolean (*register_bridge) (const NMBtVTableNetworkServer *vtable,
+	                             const char *addr,
+	                             NMDevice *device);
+	gboolean (*unregister_bridge) (const NMBtVTableNetworkServer *vtable,
+	                               NMDevice *device);
+};
 
 const char *nm_device_state_to_str (NMDeviceState state);
 
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index bfd631f0..2ed2a7d9 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -274,13 +274,15 @@ static guint
 lldp_neighbor_id_hash (gconstpointer ptr)
 {
 	const LldpNeighbor *neigh = ptr;
-	guint hash;
-
-	hash =   23423423u  + ((guint) (neigh->chassis_id ? g_str_hash (neigh->chassis_id) : 12321u));
-	hash = (hash * 33u) + ((guint) (neigh->port_id ? g_str_hash (neigh->port_id) : 34342343u));
-	hash = (hash * 33u) + ((guint) neigh->chassis_id_type);
-	hash = (hash * 33u) + ((guint) neigh->port_id_type);
-	return hash;
+	NMHashState h;
+
+	nm_hash_init (&h, 23423423u);
+	nm_hash_update_str0 (&h, neigh->chassis_id);
+	nm_hash_update_str0 (&h, neigh->port_id);
+	nm_hash_update_vals (&h,
+	                     neigh->chassis_id_type,
+	                     neigh->port_id_type);
+	return nm_hash_complete (&h);
 }
 
 static int
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/devices/ovs/nm-device-ovs-bridge.c
new file mode 100644
index 00000000..53ea2b82
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-bridge.c
@@ -0,0 +1,156 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-ovs-bridge.h"
+#include "nm-device-ovs-port.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-bridge.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.OvsBridge.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceOvsBridge);
+
+/*****************************************************************************/
+
+struct _NMDeviceOvsBridge {
+	NMDevice parent;
+};
+
+struct _NMDeviceOvsBridgeClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceOvsBridge, nm_device_ovs_bridge, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "ovs-bridge";
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	/* The actual backing resources will be created on enslavement by the port
+	 * when it can identify the port and the bridge. */
+
+	return TRUE;
+}
+
+static gboolean
+unrealize (NMDevice *device, GError **error)
+{
+	return TRUE;
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *device)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection)
+{
+	const char *connection_type;
+
+	if (!NM_DEVICE_CLASS (nm_device_ovs_bridge_parent_class)->check_connection_compatible (device, connection))
+		return FALSE;
+
+	connection_type = nm_connection_get_connection_type (connection);
+	if (!nm_streq0 (connection_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME))
+		return FALSE;
+
+	return TRUE;
+}
+
+static NMActStageReturn
+act_stage3_ip4_config_start (NMDevice *device,
+                             NMIP4Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static NMActStageReturn
+act_stage3_ip6_config_start (NMDevice *device,
+                             NMIP6Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static gboolean
+enslave_slave (NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+{
+	if (!configure)
+		return TRUE;
+
+	if (!NM_IS_DEVICE_OVS_PORT (slave))
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+release_slave (NMDevice *device, NMDevice *slave, gboolean configure)
+{
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ovs_bridge_init (NMDeviceOvsBridge *self)
+{
+}
+
+static void
+nm_device_ovs_bridge_class_init (NMDeviceOvsBridgeClass *klass)
+{
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	device_class->connection_type = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
+	device_class->is_master = TRUE;
+	device_class->get_type_description = get_type_description;
+	device_class->create_and_realize = create_and_realize;
+	device_class->unrealize = unrealize;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->enslave_slave = enslave_slave;
+	device_class->release_slave = release_slave;
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_OVS_BRIDGE_SKELETON,
+	                                        NULL);
+}
diff --git a/src/devices/ovs/nm-device-ovs-bridge.h b/src/devices/ovs/nm-device-ovs-bridge.h
new file mode 100644
index 00000000..631b4754
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-bridge.h
@@ -0,0 +1,35 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__
+#define __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__
+
+#define NM_TYPE_DEVICE_OVS_BRIDGE            (nm_device_ovs_bridge_get_type ())
+#define NM_DEVICE_OVS_BRIDGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridge))
+#define NM_DEVICE_OVS_BRIDGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridgeClass))
+#define NM_IS_DEVICE_OVS_BRIDGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_OVS_BRIDGE))
+#define NM_IS_DEVICE_OVS_BRIDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_OVS_BRIDGE))
+#define NM_DEVICE_OVS_BRIDGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridgeClass))
+
+typedef struct _NMDeviceOvsBridge NMDeviceOvsBridge;
+typedef struct _NMDeviceOvsBridgeClass NMDeviceOvsBridgeClass;
+
+GType nm_device_ovs_bridge_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__ */
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
new file mode 100644
index 00000000..426521c5
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -0,0 +1,191 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-ovs-interface.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-interface.h"
+#include "nm-setting-ovs-port.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.OvsInterface.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceOvsInterface);
+
+/*****************************************************************************/
+
+struct _NMDeviceOvsInterface {
+	NMDevice parent;
+};
+
+struct _NMDeviceOvsInterfaceClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceOvsInterface, nm_device_ovs_interface, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "ovs-interface";
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	/* The actual backing resources will be created once an interface is
+	 * added to a port of ours, since there can be neither an empty port nor
+	 * an empty bridge. */
+
+	return TRUE;
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *device)
+{
+	return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection)
+{
+	NMSettingConnection *s_con;
+	NMSettingOvsInterface *s_ovs_iface;
+
+	if (!NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->check_connection_compatible (device, connection))
+		return FALSE;
+
+	s_ovs_iface = nm_connection_get_setting_ovs_interface (connection);
+	if (!s_ovs_iface)
+		return FALSE;
+	if (!NM_IN_STRSET (nm_setting_ovs_interface_get_interface_type (s_ovs_iface),
+	                   "internal", "patch")) {
+		return FALSE;
+	}
+
+	s_con = nm_connection_get_setting_connection (connection);
+	if (g_strcmp0 (nm_setting_connection_get_connection_type (s_con),
+	               NM_SETTING_OVS_INTERFACE_SETTING_NAME) != 0) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+link_changed (NMDevice *device,
+              const NMPlatformLink *pllink)
+{
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
+		nm_device_bring_up (device, TRUE, NULL);
+		nm_device_activate_schedule_stage3_ip_config_start (device);
+	}
+}
+
+static gboolean
+_is_internal_interface (NMDevice *device)
+{
+	NMConnection *connection = nm_device_get_applied_connection (device);
+	NMSettingOvsInterface *s_ovs_iface = nm_connection_get_setting_ovs_interface (connection);
+
+	g_return_val_if_fail (s_ovs_iface, FALSE);
+
+	return strcmp (nm_setting_ovs_interface_get_interface_type (s_ovs_iface), "internal") == 0;
+}
+
+static NMActStageReturn
+act_stage3_ip4_config_start (NMDevice *device,
+                             NMIP4Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	if (!_is_internal_interface (device))
+		return NM_ACT_STAGE_RETURN_IP_FAIL;
+
+	if (!nm_device_get_ip_ifindex (device))
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+
+	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
+}
+
+static NMActStageReturn
+act_stage3_ip6_config_start (NMDevice *device,
+                             NMIP6Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	if (!_is_internal_interface (device))
+		return NM_ACT_STAGE_RETURN_IP_FAIL;
+
+	if (!nm_device_get_ip_ifindex (device))
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+
+	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+}
+
+static gboolean
+can_unmanaged_external_down (NMDevice *self)
+{
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ovs_interface_init (NMDeviceOvsInterface *self)
+{
+}
+
+static void
+nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
+{
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_OPENVSWITCH);
+
+	device_class->connection_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
+	device_class->get_type_description = get_type_description;
+	device_class->create_and_realize = create_and_realize;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->is_available = is_available;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->link_changed = link_changed;
+	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_OVS_INTERFACE_SKELETON,
+	                                        NULL);
+}
diff --git a/src/devices/ovs/nm-device-ovs-interface.h b/src/devices/ovs/nm-device-ovs-interface.h
new file mode 100644
index 00000000..a748e206
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-interface.h
@@ -0,0 +1,35 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__
+#define __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__
+
+#define NM_TYPE_DEVICE_OVS_INTERFACE            (nm_device_ovs_interface_get_type ())
+#define NM_DEVICE_OVS_INTERFACE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterface))
+#define NM_DEVICE_OVS_INTERFACE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterfaceClass))
+#define NM_IS_DEVICE_OVS_INTERFACE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_OVS_INTERFACE))
+#define NM_IS_DEVICE_OVS_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_OVS_INTERFACE))
+#define NM_DEVICE_OVS_INTERFACE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterfaceClass))
+
+typedef struct _NMDeviceOvsInterface NMDeviceOvsInterface;
+typedef struct _NMDeviceOvsInterfaceClass NMDeviceOvsInterfaceClass;
+
+GType nm_device_ovs_interface_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__ */
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
new file mode 100644
index 00000000..83199f2d
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -0,0 +1,201 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-ovs-port.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-port.h"
+#include "nm-setting-ovs-port.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.OvsPort.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceOvsPort);
+
+/*****************************************************************************/
+
+struct _NMDeviceOvsPort {
+	NMDevice parent;
+};
+
+struct _NMDeviceOvsPortClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceOvsPort, nm_device_ovs_port, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "ovs-port";
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	/* The port will be added to ovsdb when an interface is enslaved,
+	 * because there's no such thing like an empty port. */
+
+	return TRUE;
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *device)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection)
+{
+	NMSettingConnection *s_con;
+	const char *connection_type;
+
+	if (!NM_DEVICE_CLASS (nm_device_ovs_port_parent_class)->check_connection_compatible (device, connection))
+		return FALSE;
+
+	s_con = nm_connection_get_setting_connection (connection);
+	connection_type = nm_setting_connection_get_connection_type (s_con);
+	if (!connection_type)
+		return FALSE;
+
+	if (strcmp (connection_type, NM_SETTING_OVS_PORT_SETTING_NAME) == 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+static NMActStageReturn
+act_stage3_ip4_config_start (NMDevice *device,
+                             NMIP4Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static NMActStageReturn
+act_stage3_ip6_config_start (NMDevice *device,
+                             NMIP6Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static void
+add_iface_cb (GError *error, gpointer user_data)
+{
+	NMDevice *slave = user_data;
+
+	if (error) {
+	        nm_log_warn (LOGD_DEVICE, "device %s could not be added to a ovs port: %s",
+		             nm_device_get_iface (slave), error->message);
+	        nm_device_state_changed (slave,
+	                                 NM_DEVICE_STATE_FAILED,
+	                                 NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+	}
+
+	g_object_unref (slave);
+}
+
+static gboolean
+enslave_slave (NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+{
+        NMActiveConnection *ac_port = NULL;
+        NMActiveConnection *ac_bridge = NULL;
+
+	if (!configure)
+		return TRUE;
+
+
+        ac_port = NM_ACTIVE_CONNECTION (nm_device_get_act_request (device));
+        ac_bridge = nm_active_connection_get_master (ac_port);
+        if (!ac_bridge)
+                ac_bridge = ac_port;
+
+        nm_ovsdb_add_interface (nm_ovsdb_get (),
+                                nm_active_connection_get_applied_connection (ac_bridge),
+                                nm_device_get_applied_connection (device),
+                                nm_device_get_applied_connection (slave),
+                                add_iface_cb, g_object_ref (slave));
+
+	return TRUE;
+}
+
+static void
+del_iface_cb (GError *error, gpointer user_data)
+{
+	NMDevice *slave = user_data;
+
+	if (error) {
+	        nm_log_warn (LOGD_DEVICE, "device %s could not be removed from a ovs port: %s",
+		             nm_device_get_iface (slave), error->message);
+	        nm_device_state_changed (slave,
+	                                 NM_DEVICE_STATE_FAILED,
+	                                 NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+	}
+
+	g_object_unref (slave);
+}
+
+static void
+release_slave (NMDevice *device, NMDevice *slave, gboolean configure)
+{
+	nm_ovsdb_del_interface (nm_ovsdb_get (), nm_device_get_iface (slave),
+				del_iface_cb, g_object_ref (slave));
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ovs_port_init (NMDeviceOvsPort *self)
+{
+}
+
+static void
+nm_device_ovs_port_class_init (NMDeviceOvsPortClass *klass)
+{
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	device_class->connection_type = NM_SETTING_OVS_PORT_SETTING_NAME;
+	device_class->is_master = TRUE;
+	device_class->get_type_description = get_type_description;
+	device_class->create_and_realize = create_and_realize;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->enslave_slave = enslave_slave;
+	device_class->release_slave = release_slave;
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_OVS_PORT_SKELETON,
+	                                        NULL);
+}
diff --git a/src/devices/ovs/nm-device-ovs-port.h b/src/devices/ovs/nm-device-ovs-port.h
new file mode 100644
index 00000000..5ccf1ec1
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-port.h
@@ -0,0 +1,35 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_PORT_H__
+#define __NETWORKMANAGER_DEVICE_OVS_PORT_H__
+
+#define NM_TYPE_DEVICE_OVS_PORT            (nm_device_ovs_port_get_type ())
+#define NM_DEVICE_OVS_PORT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPort))
+#define NM_DEVICE_OVS_PORT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPortClass))
+#define NM_IS_DEVICE_OVS_PORT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_OVS_PORT))
+#define NM_IS_DEVICE_OVS_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_OVS_PORT))
+#define NM_DEVICE_OVS_PORT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPortClass))
+
+typedef struct _NMDeviceOvsPort NMDeviceOvsPort;
+typedef struct _NMDeviceOvsPortClass NMDeviceOvsPortClass;
+
+GType nm_device_ovs_port_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_PORT_H__ */
diff --git a/src/devices/ovs/nm-ovs-factory.c b/src/devices/ovs/nm-ovs-factory.c
new file mode 100644
index 00000000..830f94fc
--- /dev/null
+++ b/src/devices/ovs/nm-ovs-factory.c
@@ -0,0 +1,195 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-manager.h"
+#include "nm-ovsdb.h"
+#include "nm-device-ovs-interface.h"
+#include "nm-device-ovs-port.h"
+#include "nm-device-ovs-bridge.h"
+#include "platform/nm-platform.h"
+#include "nm-core-internal.h"
+#include "devices/nm-device-factory.h"
+
+/*****************************************************************************/
+
+typedef struct {
+	NMDeviceFactory parent;
+} NMOvsFactory;
+
+typedef struct {
+	NMDeviceFactoryClass parent;
+} NMOvsFactoryClass;
+
+#define NM_TYPE_OVS_FACTORY            (nm_ovs_factory_get_type ())
+#define NM_OVS_FACTORY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_OVS_FACTORY, NMOvsFactory))
+#define NM_OVS_FACTORY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_OVS_FACTORY, NMOvsFactoryClass))
+#define NM_IS_OVS_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_OVS_FACTORY))
+#define NM_IS_OVS_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_OVS_FACTORY))
+#define NM_OVS_FACTORY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_OVS_FACTORY, NMOvsFactoryClass))
+
+static GType nm_ovs_factory_get_type (void);
+G_DEFINE_TYPE (NMOvsFactory, nm_ovs_factory, NM_TYPE_DEVICE_FACTORY)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_DEVICE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "ovs", __VA_ARGS__)
+
+/*****************************************************************************/
+
+NM_DEVICE_FACTORY_DECLARE_TYPES (
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_OPENVSWITCH)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_OVS_BRIDGE_SETTING_NAME,
+	                                         NM_SETTING_OVS_INTERFACE_SETTING_NAME,
+	                                         NM_SETTING_OVS_PORT_SETTING_NAME)
+)
+
+G_MODULE_EXPORT NMDeviceFactory *
+nm_device_factory_create (GError **error)
+{
+	return (NMDeviceFactory *) g_object_new (NM_TYPE_OVS_FACTORY, NULL);
+}
+
+static NMDevice *
+new_device_from_type (const char *name, NMDeviceType device_type)
+{
+	GType type;
+	const char *type_desc;
+	NMLinkType link_type = NM_LINK_TYPE_NONE;
+
+	if (nm_manager_get_device (nm_manager_get (), name, device_type))
+		return NULL;
+
+	if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE) {
+		type = NM_TYPE_DEVICE_OVS_INTERFACE;
+		type_desc = "OpenVSwitch Interface";
+		link_type = NM_LINK_TYPE_OPENVSWITCH;
+	} else if (device_type == NM_DEVICE_TYPE_OVS_PORT) {
+		type = NM_TYPE_DEVICE_OVS_PORT;
+		type_desc = "OpenVSwitch Port";
+	} else if (device_type == NM_DEVICE_TYPE_OVS_BRIDGE) {
+		type = NM_TYPE_DEVICE_OVS_BRIDGE;
+		type_desc = "OpenVSwitch Bridge";
+	} else {
+		return NULL;
+	}
+
+	return g_object_new (type,
+	                     NM_DEVICE_IFACE, name,
+	                     NM_DEVICE_DRIVER, "openvswitch",
+	                     NM_DEVICE_DEVICE_TYPE, device_type,
+	                     NM_DEVICE_TYPE_DESC, type_desc,
+	                     NM_DEVICE_LINK_TYPE, link_type,
+	                     NULL);
+}
+
+static void
+ovsdb_device_added (NMOvsdb *ovsdb, const char *name, NMDeviceType device_type,
+                    NMDeviceFactory *self)
+{
+	NMDevice *device = NULL;
+
+	device = new_device_from_type (name, device_type);
+	if (!device)
+		return;
+
+	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device);
+	g_object_unref (device);
+}
+
+static void
+ovsdb_device_removed (NMOvsdb *ovsdb, const char *name, NMDeviceType device_type,
+                      NMDeviceFactory *self)
+{
+	NMDevice *device;
+	NMDeviceState device_state;
+
+	device = nm_manager_get_device (nm_manager_get (), name, device_type);
+	if (!device)
+		return;
+
+	device_state = nm_device_get_state (device);
+	if (   device_type == NM_DEVICE_TYPE_OVS_INTERFACE
+	    && device_state > NM_DEVICE_STATE_DISCONNECTED
+	    && device_state < NM_DEVICE_STATE_DEACTIVATING) {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_DEACTIVATING,
+		                         NM_DEVICE_STATE_REASON_REMOVED);
+	} else if (device_state == NM_DEVICE_STATE_UNMANAGED) {
+		nm_device_unrealize (device, TRUE, NULL);
+	}
+}
+
+static void
+start (NMDeviceFactory *self)
+{
+	NMOvsdb *ovsdb;
+
+	ovsdb = nm_ovsdb_get ();
+
+	g_signal_connect_object (ovsdb, NM_OVSDB_DEVICE_ADDED, G_CALLBACK (ovsdb_device_added), self, (GConnectFlags) 0);
+	g_signal_connect_object (ovsdb, NM_OVSDB_DEVICE_REMOVED, G_CALLBACK (ovsdb_device_removed), self, (GConnectFlags) 0);
+}
+
+static NMDevice *
+create_device (NMDeviceFactory *self,
+               const char *iface,
+               const NMPlatformLink *plink,
+               NMConnection *connection,
+               gboolean *out_ignore)
+{
+	NMDeviceType device_type = NM_DEVICE_TYPE_UNKNOWN;
+	const char *connection_type = NULL;
+
+	if (g_strcmp0 (iface, "ovs-system") == 0) {
+		*out_ignore = TRUE;
+		return NULL;
+	}
+
+	if (connection)
+		connection_type = nm_connection_get_connection_type (connection);
+
+	if (plink)
+		device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
+	else if (g_strcmp0 (connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME) == 0)
+		device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
+	else if (g_strcmp0 (connection_type, NM_SETTING_OVS_PORT_SETTING_NAME) == 0)
+		device_type = NM_DEVICE_TYPE_OVS_PORT;
+	else if (g_strcmp0 (connection_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME) == 0)
+		device_type = NM_DEVICE_TYPE_OVS_BRIDGE;
+
+	return new_device_from_type (iface, device_type);
+}
+
+static void
+nm_ovs_factory_init (NMOvsFactory *self)
+{
+}
+
+static void
+nm_ovs_factory_class_init (NMOvsFactoryClass *klass)
+{
+	NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS (klass);
+
+	factory_class->get_supported_types = get_supported_types;
+	factory_class->start = start;
+	factory_class->create_device = create_device;
+}
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
new file mode 100644
index 00000000..b44668c0
--- /dev/null
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -0,0 +1,1607 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-ovsdb.h"
+
+#include <string.h>
+#include <jansson.h>
+#include <gmodule.h>
+#include <gio/gunixsocketaddress.h>
+
+#include "devices/nm-device.h"
+#include "platform/nm-platform.h"
+#include "nm-core-internal.h"
+
+/* Added in Jansson v2.4 (released Sep 23 2012), but travis.ci has v2.2. */
+#ifndef json_boolean
+#define json_boolean(val) ((val) ? json_true() : json_false())
+#endif
+
+/* Added in Jansson v2.5 (released Sep 19 2013), but travis.ci has v2.2. */
+#ifndef json_array_foreach
+#define json_array_foreach(array, index, value) \
+	for (index = 0; \
+	     index < json_array_size(array) && (value = json_array_get(array, index)); \
+	     index++)
+#endif
+
+/* Added in Jansson v2.3 (released Jan 27 2012) */
+#ifndef json_object_foreach
+#define json_object_foreach(object, key, value) \
+    for(key = json_object_iter_key(json_object_iter(object)); \
+        key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
+        key = json_object_iter_key(json_object_iter_next(object, json_object_key_to_iter(key))))
+#endif
+
+/*****************************************************************************/
+
+typedef struct {
+	char *name;
+	char *connection_uuid;
+	GPtrArray *interfaces;          /* interface uuids */
+} OpenvswitchPort;
+
+typedef struct {
+	char *name;
+	char *connection_uuid;
+	GPtrArray *ports;               /* port uuids */
+} OpenvswitchBridge;
+
+typedef struct {
+	char *name;
+	char *type;
+	char *connection_uuid;
+} OpenvswitchInterface;
+
+/*****************************************************************************/
+
+enum {
+	DEVICE_ADDED,
+	DEVICE_REMOVED,
+	DEVICE_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+	GSocketClient *client;
+	GSocketConnection *conn;
+	GCancellable *cancellable;
+	char buf[4096];                 /* Input buffer */
+	size_t bufp;                    /* Last decoded byte in the input buffer. */
+	GString *input;                 /* JSON stream waiting for decoding. */
+	GString *output;                /* JSON stream to be sent. */
+	gint64 seq;
+	GArray *calls;                  /* Method calls waiting for a response. */
+	GHashTable *interfaces;         /* interface uuid => OpenvswitchInterface */
+	GHashTable *ports;              /* port uuid => OpenvswitchPort */
+	GHashTable *bridges;            /* bridge uuid => OpenvswitchBridge */
+	const char *db_uuid;
+} NMOvsdbPrivate;
+
+struct _NMOvsdb {
+	GObject parent;
+	NMOvsdbPrivate _priv;
+};
+
+struct _NMOvsdbClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMOvsdb, nm_ovsdb, G_TYPE_OBJECT)
+
+#define NM_OVSDB_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMOvsdb, NM_IS_OVSDB)
+
+#define _NMLOG_DOMAIN      LOGD_DEVICE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "ovsdb", __VA_ARGS__)
+
+NM_DEFINE_SINGLETON_GETTER (NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB);
+
+/*****************************************************************************/
+
+static void ovsdb_try_connect (NMOvsdb *self);
+static void ovsdb_disconnect (NMOvsdb *self);
+static void ovsdb_read (NMOvsdb *self);
+static void ovsdb_write (NMOvsdb *self);
+static void ovsdb_next_command (NMOvsdb *self);
+
+/*****************************************************************************/
+
+/* ovsdb command abstraction. */
+
+typedef void (*OvsdbMethodCallback) (NMOvsdb *self, json_t *response,
+                                     GError *error, gpointer user_data);
+
+typedef enum {
+	OVSDB_MONITOR,
+	OVSDB_ADD_INTERFACE,
+	OVSDB_DEL_INTERFACE,
+} OvsdbCommand;
+
+typedef struct {
+	gint64 id;
+#define COMMAND_PENDING -1                      /* id not yet assigned */
+	OvsdbCommand command;
+	OvsdbMethodCallback callback;
+	gpointer user_data;
+	union {
+		const char *ifname;
+		struct {
+			NMConnection *bridge;
+			NMConnection *port;
+			NMConnection *interface;
+		};
+	};
+} OvsdbMethodCall;
+
+static void
+_call_trace (const char *comment, OvsdbMethodCall *call, json_t *msg)
+{
+#ifdef NM_MORE_LOGGING
+	char *str = NULL;
+
+	if (msg)
+		str = json_dumps (msg, 0);
+
+	switch (call->command) {
+	case OVSDB_MONITOR:
+		_LOGT ("%s: monitor%s%s",
+		       comment,
+		       msg ? ": " : "",
+		       msg ? str : "");
+		break;
+	case OVSDB_ADD_INTERFACE:
+		_LOGT ("%s: add-iface bridge=%s port=%s interface=%s%s%s",
+		       comment,
+		       nm_connection_get_interface_name (call->bridge),
+		       nm_connection_get_interface_name (call->port),
+		       nm_connection_get_interface_name (call->interface),
+		       msg ? ": " : "",
+		       msg ? str : "");
+		break;
+	case OVSDB_DEL_INTERFACE:
+		_LOGT ("%s: del-iface interface=%s%s%s",
+		       comment, call->ifname,
+		       msg ? ": " : "",
+		       msg ? str : "");
+		break;
+	}
+
+	if (msg)
+		g_free (str);
+#endif
+}
+
+/**
+ * ovsdb_call_method:
+ *
+ * Queues the ovsdb command. Eventually fires the command right away if
+ * there's no command pending completion.
+ */
+static void
+ovsdb_call_method (NMOvsdb *self, OvsdbCommand command,
+                   const char *ifname,
+                   NMConnection *bridge, NMConnection *port, NMConnection *interface,
+                   OvsdbMethodCallback callback, gpointer user_data)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	OvsdbMethodCall *call;
+
+	/* Ensure we're not unsynchronized before we queue the method call. */
+	ovsdb_try_connect (self);
+
+	g_array_set_size (priv->calls, priv->calls->len + 1);
+	call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
+	call->id = COMMAND_PENDING;
+	call->command = command;
+	call->callback = callback;
+	call->user_data = user_data;
+
+	switch (call->command) {
+	case OVSDB_MONITOR:
+		break;
+	case OVSDB_ADD_INTERFACE:
+		call->bridge = nm_simple_connection_new_clone (bridge);
+		call->port = nm_simple_connection_new_clone (port);
+		call->interface = nm_simple_connection_new_clone (interface);
+		break;
+	case OVSDB_DEL_INTERFACE:
+		call->ifname = g_strdup (ifname);
+		break;
+	}
+
+	_call_trace ("enqueue", call, NULL);
+
+	ovsdb_next_command (self);
+}
+
+/*****************************************************************************/
+
+/* Create and process the JSON-RPC messages from ovsdb. */
+
+/**
+ * _expect_ovs_bridges:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * bridges doesn't match @bridges. This is a way of detecting race conditions
+ * with other ovsdb clients that might be adding or removing bridges
+ * at the same time.
+ */
+static void
+_expect_ovs_bridges (json_t *params, const char *db_uuid, json_t *bridges)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, [s, s]]]}",
+		           "op", "wait", "table", "Open_vSwitch",
+		           "timeout", 0, "columns", "bridges",
+		           "until", "==", "rows", "bridges", "set", bridges,
+		           "where", "_uuid", "==", "uuid", db_uuid)
+	);
+}
+
+/**
+ * _set_ovs_bridges:
+ *
+ * Return a command that will update the list of bridges in @db_uuid
+ * database to @new_bridges.
+ */
+static void
+_set_ovs_bridges (json_t *params, const char *db_uuid, json_t *new_bridges)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, [s, s]]]}",
+		           "op", "update", "table", "Open_vSwitch",
+		           "row", "bridges", "set", new_bridges,
+		           "where", "_uuid", "==", "uuid", db_uuid)
+	);
+}
+
+/**
+ * _expect_bridge_ports:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * ports in bridge @ifname doesn't match @ports. This is a way of detecting
+ * race conditions with other ovsdb clients that might be adding or removing
+ * bridge ports at the same time.
+ */
+static void
+_expect_bridge_ports (json_t *params, const char *ifname, json_t *ports)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}",
+		           "op", "wait", "table", "Bridge",
+		           "timeout", 0, "columns", "ports",
+		           "until", "==", "rows", "ports", "set", ports,
+		           "where", "name", "==", ifname)
+	);
+}
+
+/**
+ * _set_bridge_ports:
+ *
+ * Return a command that will update the list of ports of bridge
+ * @ifname to @new_ports.
+ */
+static void
+_set_bridge_ports (json_t *params, const char *ifname, json_t *new_ports)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}",
+		           "op", "update", "table", "Bridge",
+		           "row", "ports", "set", new_ports,
+		           "where", "name", "==", ifname)
+	);
+}
+
+/**
+ * _expect_port_interfaces:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * interfaces in port @ifname doesn't match @interfaces. This is a way of
+ * detecting race conditions with other ovsdb clients that might be adding
+ * or removing port interfaces at the same time.
+ */
+static void
+_expect_port_interfaces (json_t *params, const char *ifname, json_t *interfaces)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}",
+		           "op", "wait", "table", "Port",
+		           "timeout", 0, "columns", "interfaces",
+		           "until", "==", "rows", "interfaces", "set", interfaces,
+		           "where", "name", "==", ifname)
+	);
+}
+
+/**
+ * _set_port_interfaces:
+ *
+ * Return a command that will update the list of interfaces of port @ifname
+ * to @new_interfaces.
+ */
+static void
+_set_port_interfaces (json_t *params, const char *ifname, json_t *new_interfaces)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}",
+		           "op", "update", "table", "Port",
+		           "row", "interfaces", "set", new_interfaces,
+		           "where", "name", "==", ifname)
+	);
+}
+
+/**
+ * _insert_interface:
+ *
+ * Returns an commands that adds new interface from a given connection.
+ */
+static void
+_insert_interface (json_t *params, NMConnection *interface)
+{
+	const char *type = NULL;
+	NMSettingOvsInterface *s_ovs_iface;
+	NMSettingOvsPatch *s_ovs_patch;
+	json_t *options = json_array ();
+
+	s_ovs_iface = nm_connection_get_setting_ovs_interface (interface);
+	if (s_ovs_iface)
+		type = nm_setting_ovs_interface_get_interface_type (s_ovs_iface);
+
+	json_array_append (options, json_string ("map"));
+	s_ovs_patch = nm_connection_get_setting_ovs_patch (interface);
+	if (s_ovs_patch) {
+		json_array_append (options, json_pack ("[[s, s]]",
+		                                       "peer",
+		                                        nm_setting_ovs_patch_get_peer (s_ovs_patch)));
+	} else {
+		json_array_append (options, json_array ());
+	}
+
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:s, s:s, s:o, s:[s, [[s, s]]]}, s:s}",
+		           "op", "insert", "table", "Interface", "row",
+		           "name", nm_connection_get_interface_name (interface),
+		           "type", type ? type : "",
+		           "options", options,
+		           "external_ids", "map", "NM.connection.uuid", nm_connection_get_uuid (interface),
+		           "uuid-name", "rowInterface"));
+}
+
+/**
+ * _insert_port:
+ *
+ * Returns an commands that adds new port from a given connection.
+ */
+static void
+_insert_port (json_t *params, NMConnection *port, json_t *new_interfaces)
+{
+	NMSettingOvsPort *s_ovs_port;
+	const char *vlan_mode = NULL;
+	guint tag = 0;
+	const char *lacp = NULL;
+	const char *bond_mode = NULL;
+	guint bond_updelay = 0;
+	guint bond_downdelay = 0;
+	json_t *row;
+
+	s_ovs_port = nm_connection_get_setting_ovs_port (port);
+
+	row = json_object ();
+
+	if (s_ovs_port) {
+		vlan_mode = nm_setting_ovs_port_get_vlan_mode (s_ovs_port);
+		tag = nm_setting_ovs_port_get_tag (s_ovs_port);
+		lacp = nm_setting_ovs_port_get_lacp (s_ovs_port);
+		bond_mode = nm_setting_ovs_port_get_bond_mode (s_ovs_port);
+		bond_updelay = nm_setting_ovs_port_get_bond_updelay (s_ovs_port);
+		bond_downdelay = nm_setting_ovs_port_get_bond_downdelay (s_ovs_port);
+	}
+
+	if (vlan_mode)
+		json_object_set_new (row, "vlan_mode", json_string (vlan_mode));
+	if (tag)
+		json_object_set_new (row, "tag", json_integer (tag));
+	if (lacp)
+		json_object_set_new (row, "lacp", json_string (lacp));
+	if (bond_mode)
+		json_object_set_new (row, "bond_mode", json_string (bond_mode));
+	if (bond_updelay)
+		json_object_set_new (row, "bond_updelay", json_integer (bond_updelay));
+	if (bond_downdelay)
+		json_object_set_new (row, "bond_downdelay", json_integer (bond_downdelay));
+
+	json_object_set_new (row, "name", json_string (nm_connection_get_interface_name (port)));
+	json_object_set_new (row, "interfaces", json_pack ("[s, O]", "set", new_interfaces));
+	json_object_set_new (row, "external_ids",
+		json_pack ("[s, [[s, s]]]", "map",
+		           "NM.connection.uuid", nm_connection_get_uuid (port)));
+
+	/* Create a new one. */
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:o, s:s}", "op", "insert", "table", "Port",
+		           "row", row, "uuid-name", "rowPort"));
+}
+
+/**
+ * _insert_bridge:
+ *
+ * Returns an commands that adds new bridge from a given connection.
+ */
+static void
+_insert_bridge (json_t *params, NMConnection *bridge, json_t *new_ports)
+{
+	NMSettingOvsBridge *s_ovs_bridge;
+	const char *fail_mode = NULL;
+	gboolean mcast_snooping_enable = FALSE;
+	gboolean rstp_enable = FALSE;
+	gboolean stp_enable = FALSE;
+	json_t *row;
+
+	s_ovs_bridge = nm_connection_get_setting_ovs_bridge (bridge);
+
+	row = json_object ();
+
+	if (s_ovs_bridge) {
+		fail_mode = nm_setting_ovs_bridge_get_fail_mode (s_ovs_bridge);
+		mcast_snooping_enable = nm_setting_ovs_bridge_get_mcast_snooping_enable (s_ovs_bridge);
+		rstp_enable = nm_setting_ovs_bridge_get_rstp_enable (s_ovs_bridge);
+		stp_enable = nm_setting_ovs_bridge_get_stp_enable (s_ovs_bridge);
+	}
+
+	if (fail_mode)
+		json_object_set_new (row, "fail_mode", json_string (fail_mode));
+	if (mcast_snooping_enable)
+		json_object_set_new (row, "mcast_snooping_enable", json_boolean (mcast_snooping_enable));
+	if (rstp_enable)
+		json_object_set_new (row, "rstp_enable", json_boolean (rstp_enable));
+	if (stp_enable)
+		json_object_set_new (row, "stp_enable", json_boolean (stp_enable));
+
+	json_object_set_new (row, "name", json_string (nm_connection_get_interface_name (bridge)));
+	json_object_set_new (row, "ports", json_pack ("[s, O]", "set", new_ports));
+	json_object_set_new (row, "external_ids",
+		json_pack ("[s, [[s, s]]]", "map",
+		           "NM.connection.uuid", nm_connection_get_uuid (bridge)));
+
+	/* Create a new one. */
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:o, s:s}", "op", "insert", "table", "Bridge",
+		           "row", row, "uuid-name", "rowBridge"));
+}
+
+/**
+ * _inc_next_cfg:
+ *
+ * Returns an mutate command that bumps next_cfg upon successful completion
+ * of the transaction it is in.
+ */
+static json_t *
+_inc_next_cfg (const char *db_uuid)
+{
+	return json_pack ("{s:s, s:s, s:[[s, s, i]], s:[[s, s, [s, s]]]}",
+                          "op", "mutate", "table", "Open_vSwitch",
+	                  "mutations", "next_cfg", "+=", 1,
+	                  "where", "_uuid", "==", "uuid", db_uuid);
+}
+
+/**
+ * _add_interface:
+ *
+ * Adds an interface as specified by @interface connection, optionally creating
+ * a parent @port and @bridge if needed.
+ */
+static void
+_add_interface (NMOvsdb *self, json_t *params,
+                NMConnection *bridge, NMConnection *port, NMConnection *interface)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GHashTableIter iter;
+	const char *bridge_uuid;
+	const char *port_uuid;
+	const char *interface_uuid;
+	OpenvswitchBridge *ovs_bridge = NULL;
+	OpenvswitchPort *ovs_port = NULL;
+	OpenvswitchInterface *ovs_interface = NULL;
+	int pi;
+	int ii;
+	json_t *bridges, *new_bridges;
+	json_t *ports, *new_ports;
+	json_t *interfaces, *new_interfaces;
+	gboolean has_interface = FALSE;
+
+	bridges = json_array ();
+	ports = json_array ();
+	interfaces = json_array ();
+	new_bridges = json_array ();
+	new_ports = json_array ();
+	new_interfaces = json_array ();
+
+	g_hash_table_iter_init (&iter, priv->bridges);
+	while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
+		json_array_append_new (bridges, json_pack ("[s, s]", "uuid", bridge_uuid));
+
+		if (   g_strcmp0 (ovs_bridge->name, nm_connection_get_interface_name (bridge)) != 0
+		    || g_strcmp0 (ovs_bridge->connection_uuid, nm_connection_get_uuid (bridge)) != 0)
+			continue;
+
+		for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
+			port_uuid = g_ptr_array_index (ovs_bridge->ports, pi);
+			ovs_port = g_hash_table_lookup (priv->ports, port_uuid);
+
+			json_array_append_new (ports, json_pack ("[s, s]", "uuid", port_uuid));
+
+			if (   g_strcmp0 (ovs_port->name, nm_connection_get_interface_name (port)) != 0
+			    || g_strcmp0 (ovs_port->connection_uuid, nm_connection_get_uuid (port)) != 0)
+				continue;
+
+			for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
+				interface_uuid = g_ptr_array_index (ovs_port->interfaces, ii);
+				ovs_interface = g_hash_table_lookup (priv->interfaces, interface_uuid);
+
+				json_array_append_new (interfaces, json_pack ("[s, s]", "uuid", interface_uuid));
+
+				if (   g_strcmp0 (ovs_interface->name, nm_connection_get_interface_name (interface)) == 0
+				    && g_strcmp0 (ovs_interface->connection_uuid, nm_connection_get_uuid (interface)) == 0)
+					has_interface = TRUE;
+			}
+
+			break;
+		}
+
+		break;
+	}
+
+	json_array_extend (new_bridges, bridges);
+	json_array_extend (new_ports, ports);
+	json_array_extend (new_interfaces, interfaces);
+
+	if (json_array_size (interfaces) == 0) {
+		/* Need to create a port. */
+		if (json_array_size (ports) == 0) {
+			/* Need to create a bridge. */
+			_expect_ovs_bridges (params, priv->db_uuid, bridges);
+			json_array_append_new (new_bridges, json_pack ("[s, s]", "named-uuid", "rowBridge"));
+			_set_ovs_bridges (params, priv->db_uuid, new_bridges);
+			_insert_bridge (params, bridge, new_ports);
+		} else {
+			/* Bridge already exists. */
+			g_return_if_fail (ovs_bridge);
+			_expect_bridge_ports (params, ovs_bridge->name, ports);
+			_set_bridge_ports (params, nm_connection_get_interface_name (bridge), new_ports);
+		}
+
+		json_array_append_new (new_ports, json_pack ("[s, s]", "named-uuid", "rowPort"));
+		_insert_port (params, port, new_interfaces);
+	} else {
+		/* Port already exists */
+		g_return_if_fail (ovs_port);
+		_expect_port_interfaces (params, ovs_port->name, interfaces);
+		_set_port_interfaces (params, nm_connection_get_interface_name (port), new_interfaces);
+	}
+
+	if (!has_interface) {
+		_insert_interface (params, interface);
+		json_array_append_new (new_interfaces, json_pack ("[s, s]", "named-uuid", "rowInterface"));
+	}
+
+	json_decref (interfaces);
+	json_decref (ports);
+	json_decref (bridges);
+
+	json_decref (new_interfaces);
+	json_decref (new_ports);
+	json_decref (new_bridges);
+}
+
+/**
+ * _delete_interface:
+ *
+ * Removes an interface of @ifname name, collecting empty ports and bridge
+ * if last item is removed from them.
+ */
+static void
+_delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GHashTableIter iter;
+	char *bridge_uuid;
+	char *port_uuid;
+	char *interface_uuid;
+	OpenvswitchBridge *ovs_bridge;
+	OpenvswitchPort *ovs_port;
+	OpenvswitchInterface *ovs_interface;
+	int pi;
+	int ii;
+	json_t *bridges, *new_bridges;
+	json_t *ports, *new_ports;
+	json_t *interfaces, *new_interfaces;
+	gboolean bridges_changed;
+	gboolean ports_changed;
+	gboolean interfaces_changed;
+
+	bridges = json_array ();
+	new_bridges = json_array ();
+	bridges_changed = FALSE;
+
+	g_hash_table_iter_init (&iter, priv->bridges);
+	while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
+		json_array_append_new (bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
+
+		ports = json_array ();
+		new_ports = json_array ();
+		ports_changed = FALSE;
+
+		for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
+			port_uuid = g_ptr_array_index (ovs_bridge->ports, pi);
+			ovs_port = g_hash_table_lookup (priv->ports, port_uuid);
+
+			json_array_append_new (ports, json_pack ("[s,s]", "uuid", port_uuid));
+
+			interfaces = json_array ();
+			new_interfaces = json_array ();
+			interfaces_changed = FALSE;
+
+			for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
+				interface_uuid = g_ptr_array_index (ovs_port->interfaces, ii);
+				ovs_interface = g_hash_table_lookup (priv->interfaces, interface_uuid);
+
+				json_array_append_new (interfaces, json_pack ("[s,s]", "uuid", interface_uuid));
+
+				if (strcmp (ovs_interface->name, ifname) == 0) {
+					/* skip the interface */
+					interfaces_changed = TRUE;
+					continue;
+				}
+
+				json_array_append_new (new_interfaces, json_pack ("[s,s]", "uuid", interface_uuid));
+			}
+
+			if (json_array_size (new_interfaces) == 0) {
+				ports_changed = TRUE;
+			} else {
+				if (interfaces_changed) {
+					_expect_port_interfaces (params, ovs_port->name, interfaces);
+					_set_port_interfaces (params, ovs_port->name, new_interfaces);
+				}
+				json_array_append_new (new_ports, json_pack ("[s,s]", "uuid", port_uuid));
+			}
+
+			json_decref (interfaces);
+			json_decref (new_interfaces);
+		}
+
+		if (json_array_size (new_ports) == 0) {
+			bridges_changed = TRUE;
+		} else {
+			if (ports_changed) {
+				_expect_bridge_ports (params, ovs_bridge->name, ports);
+				_set_bridge_ports (params, ovs_bridge->name, new_ports);
+			}
+			json_array_append_new (new_bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
+		}
+
+		json_decref (ports);
+		json_decref (new_ports);
+	}
+
+	if (bridges_changed) {
+		_expect_ovs_bridges (params, priv->db_uuid, bridges);
+		_set_ovs_bridges (params, priv->db_uuid, new_bridges);
+	}
+}
+
+/**
+ * ovsdb_next_command:
+ *
+ * Translates a higher level operation (add/remove bridge/port) to a RFC 7047
+ * command serialized into JSON ands sends it over to the database.
+
+ * Only called when no command is waiting for a response, since the serialized
+ * command might depend on result of a previous one (add and remove need to
+ * include an up to date bridge list in their transactions to rule out races).
+ */
+static void
+ovsdb_next_command (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	OvsdbMethodCall *call = NULL;
+	char *cmd;
+	json_t *msg = NULL;
+	json_t *params;
+
+	if (!priv->conn)
+		return;
+	if (!priv->calls->len)
+		return;
+	call = &g_array_index (priv->calls, OvsdbMethodCall, 0);
+	if (call->id != COMMAND_PENDING)
+		return;
+	call->id = priv->seq++;
+
+	switch (call->command) {
+	case OVSDB_MONITOR:
+		msg = json_pack ("{s:i, s:s, s:[s, n, {"
+		                 "  s:[{s:[s, s, s]}],"
+		                 "  s:[{s:[s, s, s]}],"
+		                 "  s:[{s:[s, s, s]}],"
+		                 "  s:[{s:[]}]"
+		                 "}]}",
+		                 "id", call->id,
+		                 "method", "monitor", "params", "Open_vSwitch",
+		                 "Bridge", "columns", "name", "ports", "external_ids",
+		                 "Port", "columns", "name", "interfaces", "external_ids",
+		                 "Interface", "columns", "name", "type", "external_ids",
+		                 "Open_vSwitch", "columns");
+		break;
+	case OVSDB_ADD_INTERFACE:
+		params = json_array ();
+		json_array_append_new (params, json_string ("Open_vSwitch"));
+		json_array_append_new (params, _inc_next_cfg (priv->db_uuid));
+
+		_add_interface (self, params, call->bridge, call->port, call->interface);
+
+		msg = json_pack ("{s:i, s:s, s:o}",
+		                 "id", call->id,
+		                 "method", "transact", "params", params);
+		break;
+	case OVSDB_DEL_INTERFACE:
+		params = json_array ();
+		json_array_append_new (params, json_string ("Open_vSwitch"));
+		json_array_append_new (params, _inc_next_cfg (priv->db_uuid));
+
+		_delete_interface (self, params, call->ifname);
+
+		msg = json_pack ("{s:i, s:s, s:o}",
+		                 "id", call->id,
+		                 "method", "transact", "params", params);
+		break;
+	}
+
+	g_return_if_fail (msg);
+	_call_trace ("send", call, msg);
+	cmd = json_dumps (msg, 0);
+
+	g_string_append (priv->output, cmd);
+	json_decref (msg);
+	free (cmd);
+
+	ovsdb_write (self);
+}
+
+/**
+ * _uuids_to_array:
+ *
+ * This tidies up the somewhat non-straightforward way ovsdb represents an array
+ * of UUID elements. The single element is a tuple (called <atom> in RFC7047),
+ *
+ *   [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ]
+ *
+ * while the list of multiple UUIDs are turned into a set of such tuples ("atoms"):
+ *
+ *   [ "set", [ [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ],
+ *              [ "uuid", "185c93f6-0b39-424e-8587-77d074aa7ce0" ], ... ] ]
+ */
+static void
+_uuids_to_array (GPtrArray *array, const json_t *items)
+{
+	const char *key;
+	json_t *value;
+	size_t index = 0;
+	json_t *set_value;
+	size_t set_index;
+
+	while (index < json_array_size (items)) {
+		key = json_string_value (json_array_get (items, index));
+		index++;
+		value = json_array_get (items, index);
+		index++;
+
+		if (!value)
+			return;
+
+		if (g_strcmp0 (key, "uuid") == 0 && json_is_string (value)) {
+			g_ptr_array_add (array, g_strdup (json_string_value (value)));
+		} else if (g_strcmp0 (key, "set") == 0 && json_is_array (value)) {
+			json_array_foreach (value, set_index, set_value) {
+				_uuids_to_array (array, set_value);
+			}
+		}
+	}
+}
+
+static char *
+_connection_uuid_from_external_ids (json_t *external_ids)
+{
+	json_t *value;
+	size_t index;
+
+	if (g_strcmp0 ("map", json_string_value (json_array_get (external_ids, 0))) != 0)
+		return NULL;
+
+	json_array_foreach (json_array_get (external_ids, 1), index, value) {
+		if (g_strcmp0 ("NM.connection.uuid", json_string_value (json_array_get (value, 0))) == 0)
+			return g_strdup (json_string_value (json_array_get (value, 1)));
+	}
+
+	return NULL;
+}
+
+/**
+ * ovsdb_got_update:
+ *
+ * Called when we've got an "update" method call (we asked for it with the monitor
+ * command). We use it to maintain a consistent view of bridge list regardless of
+ * whether the changes are done by us or externally.
+ */
+static void
+ovsdb_got_update (NMOvsdb *self, json_t *msg)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	json_t *ovs = NULL;
+	json_t *bridge = NULL;
+	json_t *port = NULL;
+	json_t *interface = NULL;
+	json_t *items;
+	json_t *external_ids;
+	json_error_t json_error = { 0, };
+	void *iter;
+	const char *name;
+	const char *key;
+	const char *type;
+	json_t *value;
+	OpenvswitchBridge *ovs_bridge;
+	OpenvswitchPort *ovs_port;
+	OpenvswitchInterface *ovs_interface;
+
+	if (json_unpack_ex (msg, &json_error, 0, "{s?:o, s?:o, s?:o, s?:o}",
+	                    "Open_vSwitch", &ovs,
+	                    "Bridge", &bridge,
+	                    "Port", &port,
+	                    "Interface", &interface) == -1) {
+		/* This doesn't really have to be an error; the key might
+		 * be missing if there really are no bridges present. */
+		_LOGD ("Bad update: %s", json_error.text);
+	}
+
+	if (ovs) {
+		iter = json_object_iter (ovs);
+		priv->db_uuid = g_strdup (iter ? json_object_iter_key (iter) : NULL);
+	}
+
+	/* Interfaces */
+	json_object_foreach (interface, key, value) {
+		gboolean old = FALSE;
+		gboolean new = FALSE;
+
+		if (json_unpack (value, "{s:{}}", "old") == 0)
+			old = TRUE;
+
+		if (json_unpack (value, "{s:{s:s, s:s, s:o}}", "new",
+		                 "name", &name,
+		                 "type", &type,
+		                 "external_ids", &external_ids) == 0)
+			new = TRUE;
+
+		if (old) {
+			ovs_interface = g_hash_table_lookup (priv->interfaces, key);
+			if (!new || g_strcmp0 (ovs_interface->name, name) != 0) {
+				old = FALSE;
+				_LOGT ("removed an '%s' interface: %s%s%s",
+				       ovs_interface->type, ovs_interface->name,
+				       ovs_interface->connection_uuid ? ", " : "",
+				       ovs_interface->connection_uuid ? ovs_interface->connection_uuid : "");
+				if (g_strcmp0 (ovs_interface->type, "internal") == 0) {
+					/* Currently the factory only creates NMDevices for
+					 * internal interfaces. Ignore the rest. */
+					g_signal_emit (self, signals[DEVICE_REMOVED], 0,
+					               ovs_interface->name, NM_DEVICE_TYPE_OVS_INTERFACE);
+				}
+			}
+			g_hash_table_remove (priv->interfaces, key);
+		}
+
+		if (new) {
+			ovs_interface = g_slice_new (OpenvswitchInterface);
+			ovs_interface->name = g_strdup (name);
+			ovs_interface->type = g_strdup (type);
+			ovs_interface->connection_uuid = _connection_uuid_from_external_ids (external_ids);
+			if (old) {
+				_LOGT ("changed an '%s' interface: %s%s%s", type, ovs_interface->name,
+				       ovs_interface->connection_uuid ? ", " : "",
+				       ovs_interface->connection_uuid ? ovs_interface->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_CHANGED], 0,
+				               "ovs-interface", ovs_interface->name);
+			} else {
+				_LOGT ("added an '%s' interface: %s%s%s",
+				       ovs_interface->type, ovs_interface->name,
+				       ovs_interface->connection_uuid ? ", " : "",
+				       ovs_interface->connection_uuid ? ovs_interface->connection_uuid : "");
+				if (g_strcmp0 (ovs_interface->type, "internal") == 0) {
+					/* Currently the factory only creates NMDevices for
+					 * internal interfaces. Ignore the rest. */
+					g_signal_emit (self, signals[DEVICE_ADDED], 0,
+					               ovs_interface->name, NM_DEVICE_TYPE_OVS_INTERFACE);
+				}
+			}
+			g_hash_table_insert (priv->interfaces, g_strdup (key), ovs_interface);
+		}
+	}
+
+	/* Ports */
+	json_object_foreach (port, key, value) {
+		gboolean old = FALSE;
+		gboolean new = FALSE;
+
+		if (json_unpack (value, "{s:{}}", "old") == 0)
+			old = TRUE;
+
+		if (json_unpack (value, "{s:{s:s, s:o, s:o}}", "new",
+		                 "name", &name,
+		                 "external_ids", &external_ids,
+		                 "interfaces", &items) == 0)
+			new = TRUE;
+
+		if (old) {
+			ovs_port = g_hash_table_lookup (priv->ports, key);
+			if (!new || g_strcmp0 (ovs_port->name, name) != 0) {
+				old = FALSE;
+				_LOGT ("removed a port: %s%s%s", ovs_port->name,
+				       ovs_port->connection_uuid ? ", " : "",
+				       ovs_port->connection_uuid ? ovs_port->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_REMOVED], 0,
+				               ovs_port->name, NM_DEVICE_TYPE_OVS_PORT);
+			}
+			g_hash_table_remove (priv->ports, key);
+		}
+
+		if (new) {
+			ovs_port = g_slice_new (OpenvswitchPort);
+			ovs_port->name = g_strdup (name);
+			ovs_port->connection_uuid = _connection_uuid_from_external_ids (external_ids);
+			ovs_port->interfaces = g_ptr_array_new_with_free_func (g_free);
+			_uuids_to_array (ovs_port->interfaces, items);
+			if (old) {
+				_LOGT ("changed a port: %s%s%s", ovs_port->name,
+				       ovs_port->connection_uuid ? ", " : "",
+				       ovs_port->connection_uuid ? ovs_port->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_CHANGED], 0,
+				               NM_SETTING_OVS_PORT_SETTING_NAME, ovs_port->name);
+			} else {
+				_LOGT ("added a port: %s%s%s", ovs_port->name,
+				       ovs_port->connection_uuid ? ", " : "",
+				       ovs_port->connection_uuid ? ovs_port->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_ADDED], 0,
+				               ovs_port->name, NM_DEVICE_TYPE_OVS_PORT);
+			}
+			g_hash_table_insert (priv->ports, g_strdup (key), ovs_port);
+		}
+	}
+
+	/* Bridges */
+	json_object_foreach (bridge, key, value) {
+		gboolean old = FALSE;
+		gboolean new = FALSE;
+
+		if (json_unpack (value, "{s:{}}", "old") == 0)
+			old = TRUE;
+
+		if (json_unpack (value, "{s:{s:s, s:o, s:o}}", "new",
+		                 "name", &name,
+		                 "external_ids", &external_ids,
+		                 "ports", &items) == 0)
+			new = TRUE;
+
+		if (old) {
+			ovs_bridge = g_hash_table_lookup (priv->bridges, key);
+			if (!new || g_strcmp0 (ovs_bridge->name, name) != 0) {
+				old = FALSE;
+				_LOGT ("removed a bridge: %s%s%s", ovs_bridge->name,
+				       ovs_bridge->connection_uuid ? ", " : "",
+				       ovs_bridge->connection_uuid ? ovs_bridge->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_REMOVED], 0,
+				               ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE);
+			}
+			g_hash_table_remove (priv->bridges, key);
+		}
+
+		if (new) {
+			ovs_bridge = g_slice_new (OpenvswitchBridge);
+			ovs_bridge->name = g_strdup (name);
+			ovs_bridge->connection_uuid = _connection_uuid_from_external_ids (external_ids);
+			ovs_bridge->ports = g_ptr_array_new_with_free_func (g_free);
+			_uuids_to_array (ovs_bridge->ports, items);
+			if (old) {
+				_LOGT ("changed a bridge: %s%s%s", ovs_bridge->name,
+				       ovs_bridge->connection_uuid ? ", " : "",
+				       ovs_bridge->connection_uuid ? ovs_bridge->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_CHANGED], 0,
+				               NM_SETTING_OVS_BRIDGE_SETTING_NAME, ovs_bridge->name);
+			} else {
+				_LOGT ("added a bridge: %s%s%s", ovs_bridge->name,
+				       ovs_bridge->connection_uuid ? ", " : "",
+				       ovs_bridge->connection_uuid ? ovs_bridge->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_ADDED], 0,
+				               ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE);
+			}
+			g_hash_table_insert (priv->bridges, g_strdup (key), ovs_bridge);
+		}
+	}
+
+}
+
+/**
+ * ovsdb_got_echo:
+ *
+ * Only implemented because the specification mandates it. Actual ovsdb hasn't been
+ * seen doing this.
+ */
+static void
+ovsdb_got_echo (NMOvsdb *self, json_int_t id, json_t *data)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	json_t *msg;
+	char *reply;
+	gboolean output_was_empty;
+
+	output_was_empty = priv->output->len == 0;
+
+	msg = json_pack ("{s:I, s:O}", "id", id, "result", data);
+	reply = json_dumps (msg, 0);
+	g_string_append (priv->output, reply);
+	json_decref (msg);
+	free (reply);
+
+	if (output_was_empty)
+		ovsdb_write (self);
+}
+
+/**
+ * ovsdb_got_msg::
+ *
+ * Called when when a complete JSON object was seen and unmarshalled.
+ * Either finishes a method call or processes a method call.
+ */
+static void
+ovsdb_got_msg (NMOvsdb *self, json_t *msg)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	json_error_t json_error = { 0, };
+	json_t *json_id = NULL;
+	gint64 id = -1;
+	const char *method = NULL;
+	json_t *params = NULL;
+	json_t *result = NULL;
+	json_t *error = NULL;
+	OvsdbMethodCall *call = NULL;
+	OvsdbMethodCallback callback;
+	gpointer user_data;
+	GError *local = NULL;
+
+	if (json_unpack_ex (msg, &json_error, 0, "{s?:o, s?:s, s?:o, s?:o, s?:o}",
+	                    "id", &json_id,
+	                    "method", &method,
+	                    "params", &params,
+	                    "result", &result,
+	                    "error", &error) == -1) {
+		_LOGW ("couldn't grok the message: %s", json_error.text);
+		ovsdb_disconnect (self);
+		return;
+	}
+
+	if (json_is_number (json_id))
+		id = json_integer_value (json_id);
+
+	if (method) {
+		/* It's a method call! */
+		if (!params) {
+			_LOGW ("a method call with no params: '%s'", method);
+			ovsdb_disconnect (self);
+			return;
+		}
+
+		if (g_strcmp0 (method, "update") == 0) {
+			/* This is a update method call. */
+			ovsdb_got_update (self, json_array_get (params, 1));
+		} else if (g_strcmp0 (method, "echo") == 0) {
+			/* This is an echo request. */
+			ovsdb_got_echo (self, id, params);
+		} else {
+			_LOGW ("got an unknown method call: '%s'", method);
+		}
+		return;
+	}
+
+	if (id > -1) {
+		/* This is a response to a method call. */
+		if (!priv->calls->len) {
+			_LOGE ("there are no queued calls expecting response %" G_GUINT64_FORMAT, id);
+			ovsdb_disconnect (self);
+			return;
+		}
+		call = &g_array_index (priv->calls, OvsdbMethodCall, 0);
+		if (call->id != id) {
+			_LOGE ("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT, call->id, id);
+			ovsdb_disconnect (self);
+			return;
+		}
+		/* Cool, we found a corresponsing call. Finish it. */
+
+		_call_trace ("response", call, msg);
+
+		if (!json_is_null (error)) {
+			/* The response contains an error. */
+			g_set_error (&local, G_IO_ERROR, G_IO_ERROR_FAILED,
+			             "Error call to OVSDB returned an error: %s",
+			              json_string_value (error));
+		}
+
+		callback = call->callback;
+		user_data = call->user_data;
+		g_array_remove_index (priv->calls, 0);
+		callback (self, result, local, user_data);
+
+		/* Don't progress further commands in case the callback hit an error
+		 * and disconnected us. */
+		if (!priv->conn)
+			return;
+
+		/* Now we're free to serialize and send the next command, if any. */
+		ovsdb_next_command (self);
+
+		return;
+	}
+
+
+	/* This is a message we are not interested in. */
+	_LOGW ("got an unknown message, ignoring");
+}
+
+/*****************************************************************************/
+
+/* Lower level marshalling and demarshalling of the JSON-RPC traffic on the
+ * ovsdb socket. */
+
+static size_t
+_json_callback (void *buffer, size_t buflen, void *user_data)
+{
+	NMOvsdb *self = NM_OVSDB (user_data);
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+
+	if (priv->bufp == priv->input->len) {
+		/* No more bytes buffered for decoding. */
+		return 0;
+	}
+
+	/* Pass one more byte to the JSON decoder. */
+	*(char *)buffer = priv->input->str[priv->bufp];
+	priv->bufp++;
+
+	return (size_t)1;
+}
+
+/**
+ * ovsdb_read_cb:
+ *
+ * Read out the data available from the ovsdb socket and try to deserialize
+ * the JSON. If we see a complete object, pass it upwards to ovsdb_got_msg().
+ */
+static void
+ovsdb_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	NMOvsdb *self = NM_OVSDB (user_data);
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GInputStream *stream = G_INPUT_STREAM (source_object);
+	GError *error = NULL;
+	gssize size;
+	json_t *msg;
+	json_error_t json_error = { 0, };
+
+	size = g_input_stream_read_finish (stream, res, &error);
+	if (size == -1) {
+		_LOGW ("short read from ovsdb: %s", error->message);
+		g_clear_error (&error);
+		ovsdb_disconnect (self);
+		return;
+	}
+
+	g_string_append_len (priv->input, priv->buf, size);
+	do {
+		priv->bufp = 0;
+		/* The callback always eats up only up to a single byte. This makes
+		 * it possible for us to identify complete JSON objects in spite of
+		 * us not knowing the length in advance. */
+		msg = json_load_callback (_json_callback, self, JSON_DISABLE_EOF_CHECK, &json_error);
+		if (msg) {
+			ovsdb_got_msg (self, msg);
+			g_string_erase (priv->input, 0, priv->bufp);
+		}
+		json_decref (msg);
+	} while (msg);
+
+	if (!priv->conn)
+		return;
+
+	if (size)
+		ovsdb_read (self);
+}
+
+static void
+ovsdb_read (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+
+	g_input_stream_read_async (g_io_stream_get_input_stream (G_IO_STREAM (priv->conn)),
+	                           priv->buf, sizeof(priv->buf),
+	                           G_PRIORITY_DEFAULT, NULL, ovsdb_read_cb, self);
+}
+
+static void
+ovsdb_write_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	GOutputStream *stream = G_OUTPUT_STREAM (source_object);
+	NMOvsdb *self = NM_OVSDB (user_data);
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GError *error = NULL;
+	gssize size;
+
+	size = g_output_stream_write_finish (stream, res, &error);
+	if (size == -1) {
+		_LOGW ("short write to ovsdb: %s", error->message);
+		g_clear_error (&error);
+		ovsdb_disconnect (self);
+		return;
+	}
+
+	if (!priv->conn)
+		return;
+
+	g_string_erase (priv->output, 0, size);
+
+	ovsdb_write (self);
+}
+
+static void
+ovsdb_write (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GOutputStream *stream;
+
+	if (!priv->output->len)
+		return;
+
+	stream = g_io_stream_get_output_stream (G_IO_STREAM (priv->conn));
+	if (g_output_stream_has_pending (stream))
+		return;
+
+	g_output_stream_write_async (stream,
+	                             priv->output->str, priv->output->len,
+	                             G_PRIORITY_DEFAULT, NULL, ovsdb_write_cb, self);
+}
+
+/*****************************************************************************/
+
+/* Routines to maintain the ovsdb connection. */
+
+/**
+ * ovsdb_disconnect:
+ *
+ * Clean up the internal state to the point equivalent to before connecting.
+ * Apart from clean shutdown this is a good response to unexpected trouble,
+ * since the next method call attempt a will trigger reconnect which hopefully
+ * puts us back in sync.
+ */
+static void
+ovsdb_disconnect (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	OvsdbMethodCall *call;
+	OvsdbMethodCallback callback;
+	gpointer user_data;
+	GError *error;
+
+	_LOGD ("disconnecting from ovsdb");
+
+	while (priv->calls->len) {
+		error = NULL;
+		call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
+		g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
+
+		callback = call->callback;
+		user_data = call->user_data;
+		g_array_remove_index (priv->calls, priv->calls->len - 1);
+		callback (self, NULL, error, user_data);
+	}
+
+	priv->bufp = 0;
+	g_string_truncate (priv->input, 0);
+	g_string_truncate (priv->output, 0);
+	g_clear_object (&priv->client);
+	g_clear_object (&priv->conn);
+	g_clear_pointer (&priv->db_uuid, g_free);
+}
+
+static void
+_monitor_bridges_cb (NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
+{
+	if (error) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			_LOGI ("%s", error->message);
+			ovsdb_disconnect (self);
+		}
+
+		g_clear_error (&error);
+		return;
+	}
+
+	/* Treat the first response the same as the subsequent "update"
+	 * messages we eventually get. */
+	ovsdb_got_update (self, result);
+}
+
+static void
+_client_connect_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	GSocketClient *client = G_SOCKET_CLIENT (source_object);
+	NMOvsdb *self = NM_OVSDB (user_data);
+	NMOvsdbPrivate *priv;
+	GError *error = NULL;
+	GSocketConnection *conn;
+
+	conn = g_socket_client_connect_finish (client, res, &error);
+	if (conn == NULL) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			_LOGI ("%s", error->message);
+
+		ovsdb_disconnect (self);
+		g_clear_error (&error);
+		return;
+	}
+
+	priv = NM_OVSDB_GET_PRIVATE (self);
+	priv->conn = conn;
+	g_clear_object (&priv->cancellable);
+
+	ovsdb_read (self);
+	ovsdb_next_command (self);
+}
+
+/**
+ * ovsdb_try_connect:
+ *
+ * Establish a connection to ovsdb unless it's already established or being
+ * established. Queues a monitor command as a very first one so that we're in
+ * sync when other commands are issued.
+ */
+static void
+ovsdb_try_connect (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GSocketAddress *addr;
+
+	if (priv->client)
+		return;
+
+	/* XXX: This should probably be made configurable via NetworkManager.conf */
+	addr = g_unix_socket_address_new (RUNSTATEDIR "/openvswitch/db.sock");
+
+	priv->client = g_socket_client_new ();
+	priv->cancellable = g_cancellable_new ();
+	g_socket_client_connect_async (priv->client, G_SOCKET_CONNECTABLE (addr),
+	                               priv->cancellable, _client_connect_cb, self);
+	g_object_unref (addr);
+
+	/* Queue a monitor call before any other command, ensuring that we have an up
+	 * to date view of existing bridged that we need for add and remove ops. */
+	ovsdb_call_method (self, OVSDB_MONITOR, NULL,
+	                   NULL, NULL, NULL, _monitor_bridges_cb, NULL);
+}
+
+/*****************************************************************************/
+
+/* Public functions useful for NMDeviceOpenvswitch to maintain the life cycle of
+ * their ovsdb entries without having to deal with ovsdb complexities themselves. */
+
+typedef struct {
+	NMOvsdbCallback callback;
+	gpointer user_data;
+} OvsdbCall;
+
+static void
+_transact_cb (NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
+{
+	OvsdbCall *call = user_data;
+	const char *err;
+	const char *err_details;
+	size_t index;
+	json_t *value;
+
+	if (error)
+		goto out;
+
+	json_array_foreach (result, index, value) {
+		if (json_unpack (value, "{s:s, s:s}", "error", &err, "details", &err_details) == 0) {
+			g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
+			             "Error running the transaction: %s: %s", err, err_details);
+			goto out;
+		}
+	}
+
+out:
+	call->callback (error, call->user_data);
+	g_slice_free (OvsdbCall, call);
+}
+
+void
+nm_ovsdb_add_interface (NMOvsdb *self,
+                        NMConnection *bridge, NMConnection *port, NMConnection *interface,
+                        NMOvsdbCallback callback, gpointer user_data)
+{
+	OvsdbCall *call;
+
+	call = g_slice_new (OvsdbCall);
+	call->callback = callback;
+	call->user_data = user_data;
+
+	ovsdb_call_method (self, OVSDB_ADD_INTERFACE, NULL,
+	                   bridge, port, interface, _transact_cb, call);
+}
+
+void
+nm_ovsdb_del_interface (NMOvsdb *self, const char *ifname,
+                        NMOvsdbCallback callback, gpointer user_data)
+{
+	OvsdbCall *call;
+
+	call = g_slice_new (OvsdbCall);
+	call->callback = callback;
+	call->user_data = user_data;
+
+	ovsdb_call_method (self, OVSDB_DEL_INTERFACE, ifname,
+	                   NULL, NULL, NULL, _transact_cb, call);
+}
+
+/*****************************************************************************/
+
+static void
+_clear_call (gpointer data)
+{
+	OvsdbMethodCall *call = data;
+
+	switch (call->command) {
+	case OVSDB_MONITOR:
+		break;
+	case OVSDB_ADD_INTERFACE:
+		g_clear_object (&call->bridge);
+		g_clear_object (&call->port);
+		g_clear_object (&call->interface);
+		break;
+	case OVSDB_DEL_INTERFACE:
+		g_clear_pointer (&call->ifname, g_free);
+		break;
+	}
+}
+
+static void
+_free_bridge (gpointer data)
+{
+	OpenvswitchBridge *ovs_bridge = data;
+
+	g_free (ovs_bridge->name);
+	g_free (ovs_bridge->connection_uuid);
+	g_ptr_array_free (ovs_bridge->ports, TRUE);
+	g_slice_free (OpenvswitchBridge, ovs_bridge);
+}
+
+static void
+_free_port (gpointer data)
+{
+	OpenvswitchPort *ovs_port = data;
+
+	g_free (ovs_port->name);
+	g_free (ovs_port->connection_uuid);
+	g_ptr_array_free (ovs_port->interfaces, TRUE);
+	g_slice_free (OpenvswitchPort, ovs_port);
+}
+
+static void
+_free_interface (gpointer data)
+{
+	OpenvswitchInterface *ovs_interface = data;
+
+	g_free (ovs_interface->name);
+	g_free (ovs_interface->connection_uuid);
+	g_free (ovs_interface->type);
+	g_slice_free (OpenvswitchInterface, ovs_interface);
+}
+
+static void
+nm_ovsdb_init (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+
+	priv->calls = g_array_new (FALSE, TRUE, sizeof (OvsdbMethodCall));
+	g_array_set_clear_func (priv->calls, _clear_call);
+	priv->input = g_string_new (NULL);
+	priv->output = g_string_new (NULL);
+	priv->bridges = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, _free_bridge);
+	priv->ports = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, _free_port);
+	priv->interfaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, _free_interface);
+
+	ovsdb_try_connect (self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMOvsdb *self = NM_OVSDB (object);
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+
+	ovsdb_disconnect (self);
+
+	g_string_free (priv->input, TRUE);
+	priv->input = NULL;
+	g_string_free (priv->output, TRUE);
+	priv->output = NULL;
+
+	if (priv->calls) {
+		g_array_free (priv->calls, TRUE);
+		priv->calls = NULL;
+	}
+
+	g_clear_pointer (&priv->bridges, g_hash_table_destroy);
+	g_clear_pointer (&priv->ports, g_hash_table_destroy);
+	g_clear_pointer (&priv->interfaces, g_hash_table_destroy);
+
+	g_cancellable_cancel (priv->cancellable);
+	g_clear_object (&priv->cancellable);
+
+	G_OBJECT_CLASS (nm_ovsdb_parent_class)->dispose (object);
+}
+
+static void
+nm_ovsdb_class_init (NMOvsdbClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	signals[DEVICE_ADDED] =
+		g_signal_new (NM_OVSDB_DEVICE_ADDED,
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0, NULL, NULL, NULL,
+		              G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+	signals[DEVICE_REMOVED] =
+		g_signal_new (NM_OVSDB_DEVICE_REMOVED,
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0, NULL, NULL, NULL,
+		              G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+	signals[DEVICE_CHANGED] =
+		g_signal_new (NM_OVSDB_DEVICE_CHANGED,
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0, NULL, NULL, NULL,
+		              G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+}
diff --git a/src/devices/ovs/nm-ovsdb.h b/src/devices/ovs/nm-ovsdb.h
new file mode 100644
index 00000000..cf9fe2a2
--- /dev/null
+++ b/src/devices/ovs/nm-ovsdb.h
@@ -0,0 +1,50 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_OVSDB_H__
+#define __NETWORKMANAGER_OVSDB_H__
+
+#define NM_TYPE_OVSDB            (nm_ovsdb_get_type ())
+#define NM_OVSDB(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_OVSDB, NMOvsdb))
+#define NM_OVSDB_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_OVSDB, NMOvsdbClass))
+#define NM_IS_OVSDB(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_OVSDB))
+#define NM_IS_OVSDB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_OVSDB))
+#define NM_OVSDB_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_OVSDB, NMOvsdbClass))
+
+#define NM_OVSDB_DEVICE_ADDED   "device-added"
+#define NM_OVSDB_DEVICE_REMOVED "device-removed"
+#define NM_OVSDB_DEVICE_CHANGED "device-changed"
+
+typedef struct _NMOvsdb NMOvsdb;
+typedef struct _NMOvsdbClass NMOvsdbClass;
+
+typedef void (*NMOvsdbCallback) (GError *error, gpointer user_data);
+
+NMOvsdb *nm_ovsdb_get (void);
+
+GType nm_ovsdb_get_type (void);
+
+void nm_ovsdb_add_interface (NMOvsdb *self,
+                             NMConnection *bridge, NMConnection *port, NMConnection *interface,
+                             NMOvsdbCallback callback, gpointer user_data);
+
+void nm_ovsdb_del_interface (NMOvsdb *self, const char *ifname,
+                             NMOvsdbCallback callback, gpointer user_data);
+
+#endif /* __NETWORKMANAGER_OVSDB_H__ */
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 1c4d2ef6..098cd437 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -56,6 +56,8 @@ typedef struct {
 	guint teamd_read_timeout;
 	guint teamd_dbus_watch;
 	char *config;
+	gboolean kill_in_progress;
+	NMConnection *connection;
 } NMDeviceTeamPrivate;
 
 struct _NMDeviceTeam {
@@ -84,24 +86,6 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static gboolean
-is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
-{
-	return TRUE;
-}
-
-static gboolean
-check_connection_available (NMDevice *device,
-                            NMConnection *connection,
-                            NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
-{
-	/* Connections are always available because the carrier state is determined
-	 * by the team port carrier states, not the team's state.
-	 */
-	return TRUE;
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMSettingTeam *s_team;
@@ -306,6 +290,26 @@ master_update_slave_connection (NMDevice *self,
 }
 
 /*****************************************************************************/
+static void
+teamd_kill_cb (pid_t pid, gboolean success, int child_status, void *user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceTeam *self = (NMDeviceTeam *) device;
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
+
+	priv->kill_in_progress = FALSE;
+
+	if (priv->connection) {
+		_LOGT (LOGD_TEAM, "kill terminated, starting teamd...");
+		if (!teamd_start (device, priv->connection)) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+		}
+		g_clear_object (&priv->connection);
+	}
+	g_object_unref (device);
+}
 
 static void
 teamd_cleanup (NMDevice *device, gboolean free_tdc)
@@ -317,7 +321,12 @@ teamd_cleanup (NMDevice *device, gboolean free_tdc)
 	nm_clear_g_source (&priv->teamd_read_timeout);
 
 	if (priv->teamd_pid > 0) {
-		nm_utils_kill_child_async (priv->teamd_pid, SIGTERM, LOGD_TEAM, "teamd", 2000, NULL, NULL);
+		priv->kill_in_progress = TRUE;
+		nm_utils_kill_child_async (priv->teamd_pid, SIGTERM,
+		                           LOGD_TEAM, "teamd",
+		                           2000,
+		                           teamd_kill_cb,
+		                           g_object_ref (device));
 		priv->teamd_pid = 0;
 	}
 
@@ -340,7 +349,7 @@ teamd_timeout_cb (gpointer user_data)
 
 	if (priv->teamd_pid && !priv->tdc) {
 		/* Timed out launching our own teamd process */
-		_LOGW (LOGD_TEAM, "teamd timed out.");
+		_LOGW (LOGD_TEAM, "teamd timed out");
 		teamd_cleanup (device, TRUE);
 
 		g_warn_if_fail (nm_device_is_activating (device));
@@ -568,7 +577,7 @@ teamd_start (NMDevice *device, NMConnection *connection)
 		/* Inject the hwaddr property into the JSON configuration.
 		 * While doing so, detect potential conflicts */
 
-		json = json_loads (config ?: "{}", 0, &jerror);
+		json = json_loads (config ?: "{}", JSON_REJECT_DUPLICATES, &jerror);
 		g_return_val_if_fail (json, FALSE);
 
 		hwaddr = json_object_get (json, "hwaddr");
@@ -663,6 +672,12 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		teamd_cleanup (device, TRUE);
 	}
 
+	if (priv->kill_in_progress) {
+		_LOGT (LOGD_TEAM, "kill in progress, wait before starting teamd");
+		priv->connection = g_object_ref (connection);
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
 	return teamd_start (device, connection) ?
 		NM_ACT_STAGE_RETURN_POSTPONE : NM_ACT_STAGE_RETURN_FAILURE;
 }
@@ -679,6 +694,7 @@ deactivate (NMDevice *device)
 	if (!priv->teamd_pid)
 		teamd_kill (self, NULL, NULL);
 	teamd_cleanup (device, TRUE);
+	g_clear_object (&priv->connection);
 }
 
 static gboolean
@@ -792,7 +808,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create team master interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -822,6 +838,7 @@ get_property (GObject *object, guint prop_id,
 static void
 nm_device_team_init (NMDeviceTeam * self)
 {
+	nm_assert (nm_device_is_master (NM_DEVICE (self)));
 }
 
 static void
@@ -854,7 +871,6 @@ nm_device_team_new (const char *iface)
 	                                  NM_DEVICE_TYPE_DESC, "Team",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_TEAM,
 	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_TEAM,
-	                                  NM_DEVICE_IS_MASTER, TRUE,
 	                                  NULL);
 }
 
@@ -887,11 +903,10 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 	object_class->dispose = dispose;
 	object_class->get_property = get_property;
 
+	parent_class->is_master = TRUE;
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->is_available = is_available;
 	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->check_connection_available = check_connection_available;
 	parent_class->complete_connection = complete_connection;
 	parent_class->update_connection = update_connection;
 	parent_class->master_update_slave_connection = master_update_slave_connection;
diff --git a/src/devices/tests/test-arping.c b/src/devices/tests/test-arping.c
index 59223f11..4b4642f3 100644
--- a/src/devices/tests/test-arping.c
+++ b/src/devices/tests/test-arping.c
@@ -40,9 +40,8 @@ static void
 fixture_setup (test_fixture *fixture, gconstpointer user_data)
 {
 	/* create veth pair. */
-	nmtstp_run_command_check ("ip link add dev %s type veth peer name %s", IFACE_VETH0, IFACE_VETH1);
-	fixture->ifindex0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
-	fixture->ifindex1 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
+	fixture->ifindex0 = nmtstp_link_veth_add (NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex;
+	fixture->ifindex1 = nmtstp_link_get_typed (NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex;
 
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex0, NULL));
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex1, NULL));
@@ -87,7 +86,7 @@ test_arping_common (test_fixture *fixture, TestInfo *info)
 	g_signal_connect (manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
 	                  G_CALLBACK (arping_manager_probe_terminated), loop);
 	g_assert (nm_arping_manager_start_probe (manager, 100, NULL));
-	g_assert (nmtst_main_loop_run (loop, 1000));
+	g_assert (nmtst_main_loop_run (loop, 2000));
 
 	for (i = 0; info->addresses[i]; i++) {
 		g_assert_cmpint (nm_arping_manager_check_address (manager, info->addresses[i]),
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 24811931..ac78757d 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -312,13 +312,13 @@ companion_state_changed_cb (NMDeviceWifi *companion,
 }
 
 static gboolean
-companion_scan_allowed_cb (NMDeviceWifi *companion, gpointer user_data)
+companion_scan_prohibited_cb (NMDeviceWifi *companion, gpointer user_data)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data);
 	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
 
 	/* Don't allow the companion to scan while configuring the mesh interface */
-	return (state < NM_DEVICE_STATE_PREPARE) || (state > NM_DEVICE_STATE_IP_CONFIG);
+	return (state >= NM_DEVICE_STATE_PREPARE) && (state <= NM_DEVICE_STATE_IP_CONFIG);
 }
 
 static gboolean
@@ -358,8 +358,8 @@ check_companion (NMDeviceOlpcMesh *self, NMDevice *other)
 	g_signal_connect (G_OBJECT (other), "notify::" NM_DEVICE_WIFI_SCANNING,
 	                  G_CALLBACK (companion_notify_cb), self);
 
-	g_signal_connect (G_OBJECT (other), NM_DEVICE_WIFI_SCANNING_ALLOWED,
-	                  G_CALLBACK (companion_scan_allowed_cb), self);
+	g_signal_connect (G_OBJECT (other), NM_DEVICE_WIFI_SCANNING_PROHIBITED,
+	                  G_CALLBACK (companion_scan_prohibited_cb), self);
 
 	g_signal_connect (G_OBJECT (other), NM_DEVICE_AUTOCONNECT_ALLOWED,
 	                  G_CALLBACK (companion_autoconnect_allowed_cb), self);
@@ -425,6 +425,13 @@ state_changed (NMDevice *device,
 		find_companion (NM_DEVICE_OLPC_MESH (device));
 }
 
+static guint32
+get_dhcp_timeout (NMDevice *device, int addr_family)
+{
+	/* shorter timeout for mesh connectivity */
+	return 20;
+}
+
 /*****************************************************************************/
 
 static void
@@ -465,11 +472,8 @@ constructed (GObject *object)
 
 	priv->manager = g_object_ref (nm_manager_get ());
 
-	g_signal_connect (priv->manager, "device-added", G_CALLBACK (device_added_cb), self);
-	g_signal_connect (priv->manager, "device-removed", G_CALLBACK (device_removed_cb), self);
-
-	/* shorter timeout for mesh connectivity */
-	nm_device_set_dhcp_timeout (NM_DEVICE (self), 20);
+	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK (device_added_cb), self);
+	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK (device_removed_cb), self);
 }
 
 NMDevice *
@@ -519,6 +523,7 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 	parent_class->act_stage1_prepare = act_stage1_prepare;
 	parent_class->act_stage2_config = act_stage2_config;
 	parent_class->state_changed = state_changed;
+	parent_class->get_dhcp_timeout = get_dhcp_timeout;
 
 	obj_properties[PROP_COMPANION] =
 	     g_param_spec_string (NM_DEVICE_OLPC_MESH_COMPANION, "", "",
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 20692ed9..8bfddbd9 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2012 Red Hat, Inc.
+ * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -63,8 +63,6 @@ _LOG_DECLARE_SELF(NMDeviceWifi);
 
 #define SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN 5
 
-static NM_CACHED_QUARK_FCN ("wireless-secrets-tries", wireless_secrets_tries_quark)
-
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
@@ -79,7 +77,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 enum {
 	ACCESS_POINT_ADDED,
 	ACCESS_POINT_REMOVED,
-	SCANNING_ALLOWED,
+	SCANNING_PROHIBITED,
 
 	LAST_SIGNAL
 };
@@ -119,6 +117,8 @@ typedef struct {
 	NMDeviceWifiCapabilities capabilities;
 
 	gint32 hw_addr_scan_expire;
+
+	guint             wps_timeout_id;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -132,7 +132,7 @@ struct _NMDeviceWifiClass
 	NMDeviceClass parent;
 
 	/* Signals */
-	gboolean (*scanning_allowed) (NMDeviceWifi *device);
+	gboolean (*scanning_prohibited) (NMDeviceWifi *device, gboolean periodic);
 };
 
 /*****************************************************************************/
@@ -143,7 +143,7 @@ G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
 
 /*****************************************************************************/
 
-static gboolean check_scanning_allowed (NMDeviceWifi *self);
+static gboolean check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic);
 
 static void schedule_scan (NMDeviceWifi *self, gboolean backoff);
 
@@ -169,6 +169,10 @@ static void supplicant_iface_scan_done_cb (NMSupplicantInterface * iface,
                                            gboolean success,
                                            NMDeviceWifi * self);
 
+static void supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
+                                                 GVariant *credentials,
+                                                 NMDeviceWifi *self);
+
 static void supplicant_iface_notify_scanning_cb (NMSupplicantInterface * iface,
                                                  GParamSpec * pspec,
                                                  NMDeviceWifi * self);
@@ -177,7 +181,10 @@ static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
 
-static void request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant *scan_options);
+static void request_wireless_scan (NMDeviceWifi *self,
+                                   gboolean periodic,
+                                   gboolean force_if_scanning,
+                                   const GPtrArray *ssids);
 
 static void ap_add_remove (NMDeviceWifi *self,
                            guint signum,
@@ -268,6 +275,10 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 	                  G_CALLBACK (supplicant_iface_scan_done_cb),
 	                  self);
 	g_signal_connect (priv->sup_iface,
+	                  NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
+	                  G_CALLBACK (supplicant_iface_wps_credentials_cb),
+	                  self);
+	g_signal_connect (priv->sup_iface,
 	                  "notify::"NM_SUPPLICANT_INTERFACE_SCANNING,
 	                  G_CALLBACK (supplicant_iface_notify_scanning_cb),
 	                  self);
@@ -559,7 +570,7 @@ deactivate (NMDevice *device)
 
 	/* Ensure we trigger a scan after deactivating a Hotspot */
 	if (old_mode == NM_802_11_MODE_AP)
-		request_wireless_scan (self, FALSE, NULL);
+		request_wireless_scan (self, FALSE, FALSE, NULL);
 }
 
 static void
@@ -781,15 +792,18 @@ complete_connection (NMDevice *device,
 	NMSettingWireless *s_wifi;
 	const char *setting_mac;
 	char *str_ssid = NULL;
-	NMWifiAP *ap = NULL;
+	NMWifiAP *ap;
 	const GByteArray *ssid = NULL;
 	GByteArray *tmp_ssid = NULL;
 	GBytes *setting_ssid = NULL;
 	gboolean hidden = FALSE;
 	const char *perm_hw_addr;
+	const char *mode;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 
+	mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
+
 	if (!specific_object) {
 		/* If not given a specific object, we need at minimum an SSID */
 		if (!s_wifi) {
@@ -809,19 +823,29 @@ complete_connection (NMDevice *device,
 			return FALSE;
 		}
 
-		/* Find a compatible AP in the scan list */
-		ap = find_first_compatible_ap (self, connection, FALSE);
+		if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+			/* Find a compatible AP in the scan list */
+			ap = find_first_compatible_ap (self, connection, FALSE);
 
-		/* If we still don't have an AP, then the WiFI settings needs to be
-		 * fully specified by the client.  Might not be able to find an AP
-		 * if the network isn't broadcasting the SSID for example.
-		 */
-		if (!ap) {
+			/* If we still don't have an AP, then the WiFI settings needs to be
+			 * fully specified by the client.  Might not be able to find an AP
+			 * if the network isn't broadcasting the SSID for example.
+			 */
+			if (!ap) {
+				if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+					return FALSE;
+
+				hidden = TRUE;
+			}
+		} else {
 			if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
 				return FALSE;
-
-			hidden = TRUE;
+			ap = NULL;
 		}
+	} else if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+			return FALSE;
+		ap = NULL;
 	} else {
 		ap = get_ap_by_path (self, specific_object);
 		if (!ap) {
@@ -1169,23 +1193,65 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 	}
 }
 
+static GPtrArray *
+ssids_options_to_ptrarray (GVariant *value, GError **error)
+{
+	GPtrArray *ssids = NULL;
+	GByteArray *ssid_array;
+	GVariant *v;
+	const guint8 *bytes;
+	gsize len;
+	int num_ssids, i;
+
+	num_ssids = g_variant_n_children (value);
+	if (num_ssids > 32) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_NOT_ALLOWED,
+		                     "too many SSIDs requested to scan");
+		return NULL;
+	}
+
+	if (num_ssids) {
+		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_byte_array_unref);
+		for (i = 0; i < num_ssids; i++) {
+			v = g_variant_get_child_value (value, i);
+			bytes = g_variant_get_fixed_array (v, &len, sizeof (guint8));
+			if (len > 32) {
+				g_set_error (error,
+				             NM_DEVICE_ERROR,
+				             NM_DEVICE_ERROR_NOT_ALLOWED,
+				             "SSID at index %d more than 32 bytes", i);
+				g_ptr_array_unref (ssids);
+				return NULL;
+			}
+
+			ssid_array = g_byte_array_new ();
+			g_byte_array_append (ssid_array, bytes, len);
+			g_ptr_array_add (ssids, ssid_array);
+		}
+	}
+	return ssids;
+}
+
 static void
-request_scan_cb (NMDevice *device,
-                 GDBusMethodInvocation *context,
-                 NMAuthSubject *subject,
-                 GError *error,
-                 gpointer user_data)
+dbus_request_scan_cb (NMDevice *device,
+                      GDBusMethodInvocation *context,
+                      NMAuthSubject *subject,
+                      GError *error,
+                      gpointer user_data)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv;
-	gs_unref_variant GVariant *new_scan_options = user_data;
+	gs_unref_variant GVariant *scan_options = user_data;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
 
 	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
 		return;
 	}
 
-	if (!check_scanning_allowed (self)) {
+	if (check_scanning_prohibited (self, FALSE)) {
 		g_dbus_method_invocation_return_error_literal (context,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1195,7 +1261,29 @@ request_scan_cb (NMDevice *device,
 
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	request_wireless_scan (self, FALSE, new_scan_options);
+	if (scan_options) {
+		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
+
+		if (val) {
+			gs_free_error GError *ssid_error = NULL;
+
+			if (!g_variant_is_of_type (val, G_VARIANT_TYPE ("aay"))) {
+				g_dbus_method_invocation_return_error_literal (context,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               "Invalid 'ssid' scan option");
+				return;
+			}
+
+			ssids = ssids_options_to_ptrarray (val, &ssid_error);
+			if (ssid_error) {
+				g_dbus_method_invocation_return_gerror (context, ssid_error);
+				return;
+			}
+		}
+	}
+
+	request_wireless_scan (self, FALSE, FALSE, ssids);
 	g_dbus_method_invocation_return_value (context, NULL);
 }
 
@@ -1243,22 +1331,23 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NULL,
 	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	                       TRUE,
-	                       request_scan_cb,
+	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
 }
 
 static gboolean
-scanning_allowed (NMDeviceWifi *self)
+scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSupplicantInterfaceState supplicant_state;
-	NMConnection *connection;
 
-	g_return_val_if_fail (priv->sup_iface != NULL, FALSE);
+	g_return_val_if_fail (priv->sup_iface != NULL, TRUE);
 
-	/* Scanning not done in AP mode */
-	if (priv->mode == NM_802_11_MODE_AP)
-		return FALSE;
+	/* Don't scan when a an AP or Ad-Hoc connection is active as it will
+	 * disrupt connected clients or peers.
+	 */
+	if (priv->mode == NM_802_11_MODE_ADHOC || priv->mode == NM_802_11_MODE_AP)
+		return TRUE;
 
 	switch (nm_device_get_state (NM_DEVICE (self))) {
 	case NM_DEVICE_STATE_UNKNOWN:
@@ -1271,78 +1360,42 @@ scanning_allowed (NMDeviceWifi *self)
 	case NM_DEVICE_STATE_IP_CHECK:
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_DEACTIVATING:
-		/* Don't scan when unusable or activating */
-		return FALSE;
+		/* Prohibit scans when unusable or activating */
+		return TRUE;
 	case NM_DEVICE_STATE_DISCONNECTED:
 	case NM_DEVICE_STATE_FAILED:
 		/* Can always scan when disconnected */
-		return TRUE;
+		return FALSE;
 	case NM_DEVICE_STATE_ACTIVATED:
-		/* Need to do further checks when activated */
+		/* Prohibit periodic scans when connected; we ask the supplicant to
+		 * background scan for us, unless the connection is locked to a specifc
+		 * BSSID.
+		 */
+		if (periodic)
+			return TRUE;
 		break;
 	}
 
-	/* Don't scan if the supplicant is busy */
+	/* Prohibit scans if the supplicant is busy */
 	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
 	if (   supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE
 	    || nm_supplicant_interface_get_scanning (priv->sup_iface))
-		return FALSE;
-
-	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	if (connection) {
-		NMSettingWireless *s_wifi;
-		const char *ip4_method = NULL;
-
-		/* Don't scan when a shared connection is active; it makes drivers mad */
-		ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-
-		if (!strcmp (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
-			return FALSE;
-
-		/* Don't scan when the connection is locked to a specifc AP, since
-		 * intra-ESS roaming (which requires periodic scanning) isn't being
-		 * used due to the specific AP lock. (bgo #513820)
-		 */
-		s_wifi = nm_connection_get_setting_wireless (connection);
-		g_assert (s_wifi);
-		if (nm_setting_wireless_get_bssid (s_wifi))
-			return FALSE;
-	}
-
-	return TRUE;
-}
+		return TRUE;
 
-static gboolean
-scanning_allowed_accumulator (GSignalInvocationHint *ihint,
-                              GValue *return_accu,
-                              const GValue *handler_return,
-                              gpointer data)
-{
-	if (!g_value_get_boolean (handler_return))
-		g_value_set_boolean (return_accu, FALSE);
-	return TRUE;
+	/* Allow the scan */
+	return FALSE;
 }
 
 static gboolean
-check_scanning_allowed (NMDeviceWifi *self)
+check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 {
-	GValue instance = G_VALUE_INIT;
-	GValue retval = G_VALUE_INIT;
-
-	g_value_init (&instance, G_TYPE_OBJECT);
-	g_value_take_object (&instance, self);
-
-	g_value_init (&retval, G_TYPE_BOOLEAN);
-	g_value_set_boolean (&retval, TRUE);
+	gboolean prohibited = FALSE;
 
-	/* Use g_signal_emitv() rather than g_signal_emit() to avoid the return
-	 * value being changed if no handlers are connected */
-	g_signal_emitv (&instance, signals[SCANNING_ALLOWED], 0, &retval);
-
-	return g_value_get_boolean (&retval);
+	g_signal_emit (self, signals[SCANNING_PROHIBITED], 0, periodic, &prohibited);
+	return prohibited;
 }
 
 static gboolean
@@ -1354,8 +1407,12 @@ hidden_filter_func (NMSettings *settings,
 
 	if (!nm_connection_is_type (NM_CONNECTION (connection), NM_SETTING_WIRELESS_SETTING_NAME))
 		return FALSE;
-	s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (NM_CONNECTION (connection));
-	return s_wifi ? nm_setting_wireless_get_hidden (s_wifi) : FALSE;
+	s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (connection));
+	if (!s_wifi)
+		return FALSE;
+	if (nm_streq0 (nm_setting_wireless_get_mode (s_wifi), NM_SETTING_WIRELESS_MODE_AP))
+		return FALSE;
+	return nm_setting_wireless_get_hidden (s_wifi);
 }
 
 static GPtrArray *
@@ -1410,32 +1467,11 @@ build_hidden_probe_list (NMDeviceWifi *self)
 	return ssids;
 }
 
-static GPtrArray *
-ssids_options_to_ptrarray (GVariant *value)
-{
-	GPtrArray *ssids = NULL;
-	GByteArray *ssid_array;
-	GVariant *v;
-	const guint8 *bytes;
-	gsize len;
-	int num_ssids, i;
-
-	num_ssids = g_variant_n_children (value);
-	if (num_ssids) {
-		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_byte_array_unref);
-		for (i = 0; i < num_ssids; i++) {
-			v = g_variant_get_child_value (value, i);
-			bytes = g_variant_get_fixed_array (v, &len, sizeof (guint8));
-			ssid_array = g_byte_array_new ();
-			g_byte_array_append (ssid_array, bytes, len);
-			g_ptr_array_add (ssids, ssid_array);
-		}
-	}
-	return ssids;
-}
-
 static void
-request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant *scan_options)
+request_wireless_scan (NMDeviceWifi *self,
+                       gboolean periodic,
+                       gboolean force_if_scanning,
+                       const GPtrArray *ssids)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	gboolean request_started = FALSE;
@@ -1447,24 +1483,14 @@ request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant
 		return;
 	}
 
-	if (check_scanning_allowed (self)) {
-		gs_unref_ptrarray GPtrArray *ssids = NULL;
+	if (!check_scanning_prohibited (self, periodic)) {
+		gs_unref_ptrarray GPtrArray *hidden_ssids = NULL;
 
 		_LOGD (LOGD_WIFI, "wifi-scan: scanning requested");
 
-		if (scan_options) {
-			GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
-
-			if (val) {
-				if (g_variant_is_of_type (val, G_VARIANT_TYPE ("aay")))
-					ssids = ssids_options_to_ptrarray (val);
-				else
-					_LOGD (LOGD_WIFI, "wifi-scan: ignoring invalid 'ssids' scan option");
-				g_variant_unref (val);
-			}
+		if (!ssids) {
+			ssids = hidden_ssids = build_hidden_probe_list (self);
 		}
-		if (!ssids)
-			ssids = build_hidden_probe_list (self);
 
 		if (_LOGD_ENABLED (LOGD_WIFI)) {
 			if (ssids) {
@@ -1478,7 +1504,7 @@ request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant
 					      ? nm_utils_ssid_to_utf8 (ssid->data, ssid->len)
 					      : NULL;
 					_LOGD (LOGD_WIFI, "wifi-scan: (%u) probe scanning SSID %s%s%s",
-					       i, NM_PRINT_FMT_QUOTED (foo, "\"", foo, "\"", "<hidden>"));
+					       i, NM_PRINT_FMT_QUOTED (foo, "\"", foo, "\"", "*any*"));
 					g_free (foo);
 				}
 			} else
@@ -1504,7 +1530,7 @@ request_wireless_scan_periodic (gpointer user_data)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	priv->pending_scan_id = 0;
-	request_wireless_scan (self, FALSE, NULL);
+	request_wireless_scan (self, TRUE, FALSE, NULL);
 	return G_SOURCE_REMOVE;
 }
 
@@ -1747,6 +1773,7 @@ cleanup_association_attempt (NMDeviceWifi *self, gboolean disconnect)
 
 	nm_clear_g_source (&priv->sup_timeout_id);
 	nm_clear_g_source (&priv->link_timeout_id);
+	nm_clear_g_source (&priv->wps_timeout_id);
 	if (disconnect && priv->sup_iface)
 		nm_supplicant_interface_disconnect (priv->sup_iface);
 }
@@ -1789,9 +1816,19 @@ wifi_secrets_cb (NMActRequest *req,
 
 	if (error) {
 		_LOGW (LOGD_WIFI, "%s", error->message);
-		nm_device_state_changed (device,
-		                         NM_DEVICE_STATE_FAILED,
-		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+
+		if (g_error_matches (error, NM_AGENT_MANAGER_ERROR,
+		                     NM_AGENT_MANAGER_ERROR_USER_CANCELED)) {
+			/* Don't wait for WPS timeout on an explicit cancel. */
+			nm_clear_g_source (&priv->wps_timeout_id);
+		}
+
+		if (!priv->wps_timeout_id) {
+			/* Fail the device only if the WPS period is over too. */
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+		}
 	} else
 		nm_device_activate_schedule_stage1_device_prepare (device);
 }
@@ -1807,6 +1844,78 @@ wifi_secrets_cancel (NMDeviceWifi *self)
 }
 
 static void
+supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
+                                     GVariant *credentials,
+                                     NMDeviceWifi *self)
+{
+	NMActRequest *req;
+	GVariant *val, *secrets = NULL;
+	const char *array;
+	gsize psk_len = 0;
+	GError *error = NULL;
+
+	if (nm_device_get_state (NM_DEVICE (self)) != NM_DEVICE_STATE_NEED_AUTH) {
+		_LOGI (LOGD_DEVICE | LOGD_WIFI, "WPS: The connection can't be updated with credentials");
+		return;
+	}
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI, "WPS: Updating the connection with credentials");
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	val = g_variant_lookup_value (credentials, "Key", G_VARIANT_TYPE_BYTESTRING);
+	if (val) {
+		char psk[64];
+
+		array = g_variant_get_fixed_array (val, &psk_len, 1);
+		if (psk_len >= 8 && psk_len <= 63) {
+			memcpy (psk, array, psk_len);
+			psk[psk_len] = '\0';
+			if (g_utf8_validate (psk, psk_len, NULL)) {
+				secrets = g_variant_new_parsed ("[{%s, [{%s, <%s>}]}]",
+				                                NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+				                                NM_SETTING_WIRELESS_SECURITY_PSK, psk);
+				g_variant_ref_sink (secrets);
+			}
+		}
+		if (!secrets)
+			_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK");
+		g_variant_unref (val);
+	}
+	if (secrets) {
+		if (nm_settings_connection_new_secrets (nm_act_request_get_settings_connection (req),
+		                                         nm_act_request_get_applied_connection (req),
+		                                         NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+		                                         secrets, &error)) {
+			wifi_secrets_cancel (self);
+			nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+		} else {
+			_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: Could not update the connection with credentials: %s", error->message);
+			g_error_free (error);
+		}
+		g_variant_unref (secrets);
+	}
+}
+
+static gboolean
+wps_timeout_cb (gpointer user_data)
+{
+	NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	priv->wps_timeout_id = 0;
+	if (!priv->wifi_secrets_id) {
+		/* Fail only if the secrets are not being requested. */
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
 wifi_secrets_get_secrets (NMDeviceWifi *self,
                           const char *setting_name,
                           NMSecretAgentGetSecretsFlags flags)
@@ -2055,6 +2164,7 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
 		nm_clear_g_source (&priv->sup_timeout_id);
 		nm_clear_g_source (&priv->link_timeout_id);
+		nm_clear_g_source (&priv->wps_timeout_id);
 
 		/* If this is the initial association during device activation,
 		 * schedule the next activation stage.
@@ -2131,7 +2241,7 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		/* we would clear _requested_scan_set() and trigger a new scan.
 		 * However, we don't want to cancel the current pending action, so force
 		 * a new scan request. */
-		request_wireless_scan (self, TRUE, NULL);
+		request_wireless_scan (self, FALSE, TRUE, NULL);
 		break;
 	default:
 		break;
@@ -2222,9 +2332,15 @@ handle_auth_or_fail (NMDeviceWifi *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	const char *setting_name;
-	guint32 tries;
 	NMConnection *applied_connection;
+	NMSettingWirelessSecurity *s_wsec;
+	const char *bssid = NULL;
+	NM80211ApFlags ap_flags;
+	NMSettingWirelessSecurityWpsMethod wps_method;
+	const char *type;
+	NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
 	g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), FALSE);
 
@@ -2233,14 +2349,50 @@ handle_auth_or_fail (NMDeviceWifi *self,
 		g_return_val_if_fail (req, FALSE);
 	}
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-
-	tries = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark ()));
-	if (tries > 3)
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return FALSE;
 
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
+	applied_connection = nm_act_request_get_applied_connection (req);
+	s_wsec = nm_connection_get_setting_wireless_security (applied_connection);
+	wps_method = nm_setting_wireless_security_get_wps_method (s_wsec);
+
+	/* Negotiate the WPS method */
+	if (wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT)
+		wps_method = NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO;
+
+	if (   wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO
+	    && priv->current_ap) {
+		/* Determine the method to use from AP capabilities. */
+		ap_flags = nm_wifi_ap_get_flags (priv->current_ap);
+		if (ap_flags & NM_802_11_AP_FLAGS_WPS_PBC)
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC;
+		if (ap_flags & NM_802_11_AP_FLAGS_WPS_PIN)
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN;
+		if (   ap_flags & NM_802_11_AP_FLAGS_WPS
+		    && wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO) {
+			/* The AP doesn't specify which methods are supported. Allow all. */
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC;
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN;
+		}
+	}
+
+	if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC) {
+		get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE;
+		type = "pbc";
+	} else if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) {
+		type = "pin";
+	} else
+		type = NULL;
+
+	if (type) {
+		priv->wps_timeout_id = g_timeout_add_seconds (30, wps_timeout_cb, self);
+		if (priv->current_ap)
+			bssid = nm_wifi_ap_get_address (priv->current_ap);
+		nm_supplicant_interface_enroll_wps (priv->sup_iface, type, bssid, NULL);
+	}
+
 	nm_act_request_clear_secrets (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (!setting_name) {
@@ -2248,10 +2400,9 @@ handle_auth_or_fail (NMDeviceWifi *self,
 		return FALSE;
 	}
 
-	wifi_secrets_get_secrets (self, setting_name,
-	                          NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
-	                          | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), GUINT_TO_POINTER (++tries));
+	if (new_secrets)
+		get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+	wifi_secrets_get_secrets (self, setting_name, get_secret_flags);
 	return TRUE;
 }
 
@@ -2348,6 +2499,8 @@ build_supplicant_config (NMDeviceWifi *self,
 	NMSupplicantConfig *config = NULL;
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessSecurity *s_wireless_sec;
+	NMSettingWirelessSecurityPmf pmf;
+	gs_free char *value = NULL;
 
 	g_return_val_if_fail (priv->sup_iface, NULL);
 
@@ -2370,6 +2523,11 @@ build_supplicant_config (NMDeviceWifi *self,
 		goto error;
 	}
 
+	if (!nm_supplicant_config_add_bgscan (config, connection, error)) {
+		g_prefix_error (error, "bgscan: ");
+		goto error;
+	}
+
 	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
 	if (s_wireless_sec) {
 		NMSetting8021x *s_8021x;
@@ -2378,12 +2536,46 @@ build_supplicant_config (NMDeviceWifi *self,
 		                                        nm_device_get_ifindex (NM_DEVICE (self)));
 
 		g_assert (con_uuid);
+
+		/* Configure PMF (802.11w) */
+		pmf = nm_setting_wireless_security_get_pmf (s_wireless_sec);
+		if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT) {
+			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+			                                               "wifi-sec.pmf",
+			                                               NM_DEVICE (self));
+			pmf = _nm_utils_ascii_str_to_int64 (value, 10,
+			                                    NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
+			                                    NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED,
+			                                    NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
+		}
+
+		/* Don't try to enable PMF on non-WPA networks */
+		if (!NM_IN_STRSET (nm_setting_wireless_security_get_key_mgmt (s_wireless_sec),
+		                   "wpa-eap",
+		                   "wpa-psk"))
+			pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+
+		/* Check if we actually support PMF */
+		if (nm_supplicant_interface_get_pmf_support (priv->sup_iface) != NM_SUPPLICANT_FEATURE_YES) {
+			if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
+				g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
+				                     "Supplicant does not support PMF");
+				goto error;
+			} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL) {
+				/* To be on the safe side, assume no support if we can't determine
+				 * capabilities.
+				 */
+				pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+			}
+		}
+
 		s_8021x = nm_connection_get_setting_802_1x (connection);
 		if (!nm_supplicant_config_add_setting_wireless_security (config,
 		                                                         s_wireless_sec,
 		                                                         s_8021x,
 		                                                         con_uuid,
 		                                                         mtu,
+		                                                         pmf,
 		                                                         error)) {
 			g_prefix_error (error, "802-11-wireless-security: ");
 			goto error;
@@ -2430,6 +2622,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
+	nm_supplicant_interface_cancel_wps (priv->sup_iface);
+
 	mode = nm_setting_wireless_get_mode (s_wireless);
 	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0)
 		priv->mode = NM_802_11_MODE_INFRA;
@@ -2578,6 +2772,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	nm_clear_g_source (&priv->sup_timeout_id);
 	nm_clear_g_source (&priv->link_timeout_id);
+	nm_clear_g_source (&priv->wps_timeout_id);
 
 	req = nm_device_get_act_request (device);
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
@@ -2873,9 +3068,6 @@ activation_success_handler (NMDevice *device)
 	/* Clear any critical protocol notification in the wifi stack */
 	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
 
-	/* Clear wireless secrets tries on success */
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), NULL);
-
 	/* There should always be a current AP, either a fake one because we haven't
 	 * seen a scan result for the activated AP yet, or a real one from the
 	 * supplicant's scan list.
@@ -2922,21 +3114,6 @@ activation_success_handler (NMDevice *device)
 }
 
 static void
-activation_failure_handler (NMDevice *device)
-{
-	NMConnection *applied_connection;
-
-	applied_connection = nm_device_get_applied_connection (device);
-	g_assert (applied_connection);
-
-	/* Clear wireless secrets tries on failure */
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), NULL);
-
-	/* Clear any critical protocol notification in the wifi stack */
-	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE);
-}
-
-static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -2990,12 +3167,13 @@ device_state_changed (NMDevice *device,
 		activation_success_handler (device);
 		break;
 	case NM_DEVICE_STATE_FAILED:
-		activation_failure_handler (device);
+		/* Clear any critical protocol notification in the wifi stack */
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE);
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
 		/* Kick off a scan to get latest results */
 		priv->scan_interval = SCAN_INTERVAL_MIN;
-		request_wireless_scan (self, FALSE, NULL);
+		request_wireless_scan (self, FALSE, FALSE, NULL);
 		break;
 	default:
 		break;
@@ -3159,7 +3337,7 @@ nm_device_wifi_init (NMDeviceWifi *self)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	priv->mode = NM_802_11_MODE_INFRA;
-	priv->aps = g_hash_table_new (g_str_hash, g_str_equal);
+	priv->aps = g_hash_table_new (nm_str_hash, g_str_equal);
 }
 
 static void
@@ -3261,7 +3439,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	parent_class->state_changed = device_state_changed;
 
-	klass->scanning_allowed = scanning_allowed;
+	klass->scanning_prohibited = scanning_prohibited;
 
 	obj_properties[PROP_MODE] =
 	    g_param_spec_uint (NM_DEVICE_WIFI_MODE, "", "",
@@ -3322,13 +3500,13 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_TYPE_NONE, 1,
 	                  NM_TYPE_WIFI_AP);
 
-	signals[SCANNING_ALLOWED] =
-	    g_signal_new (NM_DEVICE_WIFI_SCANNING_ALLOWED,
+	signals[SCANNING_PROHIBITED] =
+	    g_signal_new (NM_DEVICE_WIFI_SCANNING_PROHIBITED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_LAST,
-	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_allowed),
-	                  scanning_allowed_accumulator, NULL, NULL,
-	                  G_TYPE_BOOLEAN, 0);
+	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
+	                  NULL, NULL, NULL,
+	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
 
 	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
 	                                        NMDBUS_TYPE_DEVICE_WIFI_SKELETON,
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 024fe0ef..09707d4f 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -44,7 +44,7 @@
 #define NM_DEVICE_WIFI_ACCESS_POINT_REMOVED "access-point-removed"
 
 /* internal signals */
-#define NM_DEVICE_WIFI_SCANNING_ALLOWED    "scanning-allowed"
+#define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
 typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index 7de0838f..bc823af0 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2004 - 2011 Red Hat, Inc.
+ * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -71,7 +71,7 @@ typedef struct {
 	/* Non-scanned attributes */
 	bool                fake:1;       /* Whether or not the AP is from a scan */
 	bool                hotspot:1;    /* Whether the AP is a local device's hotspot network */
-	gint32              last_seen;  /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+	gint32              last_seen;    /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
 } NMWifiAPPrivate;
 
 struct _NMWifiAP {
@@ -79,7 +79,7 @@ struct _NMWifiAP {
 	NMWifiAPPrivate _priv;
 };
 
-struct _NMWifiAPClass{
+struct _NMWifiAPClass {
 	NMExportedObjectClass parent;
 };
 
@@ -376,6 +376,14 @@ nm_wifi_ap_set_fake (NMWifiAP *ap, gboolean fake)
 	return FALSE;
 }
 
+NM80211ApFlags
+nm_wifi_ap_get_flags (const NMWifiAP *ap)
+{
+	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NM_802_11_AP_FLAGS_NONE);
+
+	return NM_WIFI_AP_GET_PRIVATE (ap)->flags;
+}
+
 static gboolean
 nm_wifi_ap_set_last_seen (NMWifiAP *ap, gint32 last_seen)
 {
@@ -435,6 +443,326 @@ security_from_vardict (GVariant *security)
 	return flags;
 }
 
+/*****************************************************************************/
+
+static guint32
+get_max_rate_ht_20 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 6500000;
+	case 1:
+	case 8:  return 13000000;
+	case 2:
+	case 16: return 19500000;
+	case 3:
+	case 9:
+	case 24: return 26000000;
+	case 4:
+	case 10:
+	case 17: return 39000000;
+	case 5:
+	case 11:
+	case 25: return 52000000;
+	case 6:
+	case 18: return 58500000;
+	case 7:  return 65000000;
+	case 12:
+	case 19:
+	case 26: return 78000000;
+	case 13:
+	case 27: return 104000000;
+	case 14:
+	case 20: return 117000000;
+	case 15: return 130000000;
+	case 21:
+	case 28: return 156000000;
+	case 22: return 175500000;
+	case 23: return 195000000;
+	case 29: return 208000000;
+	case 30: return 234000000;
+	case 31: return 260000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_ht_40 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 13500000;
+	case 1:
+	case 8:  return 27000000;
+	case 2:  return 40500000;
+	case 3:
+	case 9:
+	case 24: return 54000000;
+	case 4:
+	case 10:
+	case 17: return 81000000;
+	case 5:
+	case 11:
+	case 25: return 108000000;
+	case 6:
+	case 18: return 121500000;
+	case 7:  return 135000000;
+	case 12:
+	case 19:
+	case 26: return 162000000;
+	case 13:
+	case 27: return 216000000;
+	case 14:
+	case 20: return 243000000;
+	case 15: return 270000000;
+	case 16: return 40500000;
+	case 21:
+	case 28: return 324000000;
+	case 22: return 364500000;
+	case 23: return 405000000;
+	case 29: return 432000000;
+	case 30: return 486000000;
+	case 31: return 540000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss1 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 29300000;
+	case 1:  return 58500000;
+	case 2:  return 87800000;
+	case 3:  return 117000000;
+	case 4:  return 175500000;
+	case 5:  return 234000000;
+	case 6:  return 263300000;
+	case 7:  return 292500000;
+	case 8:  return 351000000;
+	case 9:  return 390000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss2 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 58500000;
+	case 1:  return 117000000;
+	case 2:  return 175500000;
+	case 3:  return 234000000;
+	case 4:  return 351000000;
+	case 5:  return 468000000;
+	case 6:  return 526500000;
+	case 7:  return 585000000;
+	case 8:  return 702000000;
+	case 9:  return 780000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss3 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 87800000;
+	case 1:  return 175500000;
+	case 2:  return 263300000;
+	case 3:  return 351000000;
+	case 4:  return 526500000;
+	case 5:  return 702000000;
+	case 6:  return 0;
+	case 7:  return 877500000;
+	case 8:  return 105300000;
+	case 9:  return 117000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss1 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 58500000;
+	case 1:  return 117000000;
+	case 2:  return 175500000;
+	case 3:  return 234000000;
+	case 4:  return 351000000;
+	case 5:  return 468000000;
+	case 6:  return 526500000;
+	case 7:  return 585000000;
+	case 8:  return 702000000;
+	case 9:  return 780000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss2 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 117000000;
+	case 1:  return 234000000;
+	case 2:  return 351000000;
+	case 3:  return 468000000;
+	case 4:  return 702000000;
+	case 5:  return 936000000;
+	case 6:  return 1053000000;
+	case 7:  return 1170000000;
+	case 8:  return 1404000000;
+	case 9:  return 1560000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss3 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 175500000;
+	case 1:  return 351000000;
+	case 2:  return 526500000;
+	case 3:  return 702000000;
+	case 4:  return 1053000000;
+	case 5:  return 1404000000;
+	case 6:  return 1579500000;
+	case 7:  return 1755000000;
+	case 8:  return 2106000000;
+	case 9:  return 0;
+	}
+	return 0;
+}
+
+static gboolean
+get_max_rate_ht (const guint8 *bytes, guint len, guint32 *out_maxrate)
+{
+	guint32 mcs, i;
+	guint8 ht_cap_info;
+	const guint8 *supported_mcs_set;
+	guint32 rate;
+
+	/* http://standards.ieee.org/getieee802/download/802.11-2012.pdf
+	 * https://mrncciew.com/2014/10/19/cwap-ht-capabilities-ie/
+	 */
+
+	if (len != 26)
+		return FALSE;
+
+	ht_cap_info = bytes[0];
+	supported_mcs_set = &bytes[3];
+	*out_maxrate = 0;
+
+	/* Find the maximum supported mcs rate */
+	mcs = -1;
+	for (i = 0; i <= 76; i++) {
+		unsigned int mcs_octet = i / 8;
+		unsigned int MCS_RATE_BIT = 1 << i % 8;
+
+		if (supported_mcs_set[mcs_octet] & MCS_RATE_BIT) {
+			/* Check for 40Mhz wide channel support */
+			if (ht_cap_info & (1 << 1))
+				rate = get_max_rate_ht_40 (i);
+			else
+				rate = get_max_rate_ht_20 (i);
+
+			if (rate > *out_maxrate)
+				*out_maxrate = rate;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+get_max_rate_vht (const guint8 *bytes, guint len, guint32 *out_maxrate)
+{
+	guint32 mcs, m;
+	guint8 vht_cap, tx_map;
+
+	/* https://tda802dot11.blogspot.it/2014/10/vht-capabilities-element-vht.html
+	 * http://chimera.labs.oreilly.com/books/1234000001739/ch03.html#management_frames */
+
+	if (len != 12)
+		return FALSE;
+
+	vht_cap = bytes[0];
+	tx_map = bytes[8];
+
+	/* Check for mcs rates 8 and 9 support */
+	if (tx_map & 0x2a)
+		mcs = 9;
+	else if (tx_map & 0x15)
+		mcs = 8;
+	else
+		mcs = 7;
+
+	/* Check for 160Mhz wide channel support and
+	 * spatial stream support */
+	if (vht_cap & (1 << 2)) {
+		if (tx_map & 0x30)
+			m = get_max_rate_vht_160_ss3 (mcs);
+		else if (tx_map & 0x0C)
+			m = get_max_rate_vht_160_ss2 (mcs);
+		else
+			m = get_max_rate_vht_160_ss1 (mcs);
+	} else {
+		if (tx_map & 0x30)
+			m = get_max_rate_vht_80_ss3 (mcs);
+		else if (tx_map & 0x0C)
+			m = get_max_rate_vht_80_ss2 (mcs);
+		else
+			m = get_max_rate_vht_80_ss1 (mcs);
+	}
+
+	*out_maxrate = m;
+	return TRUE;
+}
+
+/* Management Frame Information Element IDs, ieee80211_eid */
+#define WLAN_EID_HT_CAPABILITY       45
+#define WLAN_EID_VHT_CAPABILITY     191
+
+static guint32
+get_max_rate (const guint8 *bytes, gsize len)
+{
+	guint8 id, elem_len;
+	guint32 max_rate = 0;
+
+	while (len) {
+		guint32 m;
+
+		if (len < 2)
+			return 0;
+
+		id = *bytes++;
+		elem_len = *bytes++;
+		len -= 2;
+
+		if (elem_len > len)
+			return 0;
+
+		switch (id) {
+		case WLAN_EID_HT_CAPABILITY:
+			if (!get_max_rate_ht (bytes, elem_len, &m))
+				return 0;
+			max_rate = NM_MAX (max_rate, m);
+			break;
+		case WLAN_EID_VHT_CAPABILITY:
+			if (!get_max_rate_vht (bytes, elem_len, &m))
+				return 0;
+			max_rate = NM_MAX (max_rate, m);
+			break;
+		}
+
+		len -= elem_len;
+		bytes += elem_len;
+	}
+
+	return max_rate;
+}
+
+/*****************************************************************************/
+
 gboolean
 nm_wifi_ap_update_from_properties (NMWifiAP *ap,
                                    const char *supplicant_path,
@@ -444,11 +772,13 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 	const guint8 *bytes;
 	GVariant *v;
 	gsize len;
+	gsize i;
 	gboolean b = FALSE;
 	const char *s;
 	gint16 i16;
 	guint16 u16;
 	gboolean changed = FALSE;
+	guint32 max_rate;
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
 	g_return_val_if_fail (properties, FALSE);
@@ -460,6 +790,18 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 	if (g_variant_lookup (properties, "Privacy", "b", &b) && b)
 		changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_PRIVACY);
 
+	v = g_variant_lookup_value (properties, "WPS", G_VARIANT_TYPE_VARDICT);
+	if (v) {
+		if (g_variant_lookup (v, "Type", "&s", &s)) {
+			changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS);
+			if (strcmp (s, "pbc") == 0)
+				changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS_PBC);
+			else if (strcmp (s, "pin") == 0)
+				changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS_PIN);
+		}
+		g_variant_unref (v);
+	}
+
 	if (g_variant_lookup (properties, "Mode", "&s", &s)) {
 		if (!g_strcmp0 (s, "infrastructure"))
 			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
@@ -497,21 +839,23 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 		g_variant_unref (v);
 	}
 
+	max_rate = 0;
 	v = g_variant_lookup_value (properties, "Rates", G_VARIANT_TYPE ("au"));
 	if (v) {
 		const guint32 *rates = g_variant_get_fixed_array (v, &len, sizeof (guint32));
-		guint32 maxrate = 0;
-		int i;
 
-		/* Find the max AP rate */
-		for (i = 0; i < len; i++) {
-			if (rates[i] > maxrate)
-				maxrate = rates[i];
-		}
-		if (maxrate)
-			changed |= nm_wifi_ap_set_max_bitrate (ap, maxrate / 1000);
+		for (i = 0; i < len; i++)
+			max_rate = NM_MAX (max_rate, rates[i]);
+		g_variant_unref (v);
+	}
+	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		max_rate = NM_MAX (max_rate, get_max_rate (bytes, len));
 		g_variant_unref (v);
 	}
+	if (max_rate)
+		changed |= nm_wifi_ap_set_max_bitrate (ap, max_rate / 1000);
 
 	v = g_variant_lookup_value (properties, "WPA", G_VARIANT_TYPE_VARDICT);
 	if (v) {
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index 5e64087c..dd5a4ad1 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2004 - 2011 Red Hat, Inc.
+ * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -88,6 +88,7 @@ gboolean          nm_wifi_ap_set_max_bitrate          (NMWifiAP *ap,
 gboolean          nm_wifi_ap_get_fake                 (const NMWifiAP *ap);
 gboolean          nm_wifi_ap_set_fake                 (NMWifiAP *ap,
                                                        gboolean fake);
+NM80211ApFlags    nm_wifi_ap_get_flags                (const NMWifiAP *self);
 
 const char       *nm_wifi_ap_to_string                (const NMWifiAP *self,
                                                        char *str_buf,
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 06da92ce..3ff82004 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -777,10 +777,8 @@ nm_wifi_utils_level_to_quality (gint val)
 		val = 100 - (int) ((100.0 * (double) val) / 60.0);
 	} else {
 		/* Assume signal is a "quality" percentage */
-		val = CLAMP (val, 0, 100);
 	}
-	g_assert (val >= 0);
 
-	return (guint32) val;
+	return CLAMP (val, 0, 100);
 }
 
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-general.c
index 3e61c5f0..89eebb22 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-general.c
@@ -44,16 +44,16 @@
 \
 		success = nm_connection_compare (src, expected, NM_SETTING_COMPARE_FLAG_EXACT); \
 		if (success == FALSE && DEBUG) { \
-			g_message ("\n- COMPLETED ---------------------------------\n"); \
+			g_print ("\n- COMPLETED ---------------------------------\n"); \
 			nm_connection_dump (src); \
-			g_message ("+ EXPECTED ++++++++++++++++++++++++++++++++++++\n"); \
+			g_print ("+ EXPECTED ++++++++++++++++++++++++++++++++++++\n"); \
 			nm_connection_dump (expected); \
-			g_message ("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); \
+			g_print ("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); \
 		} \
 		g_assert (success == TRUE); \
 	} else { \
 		if (success) { \
-			g_message ("\n- COMPLETED ---------------------------------\n"); \
+			g_print ("\n- COMPLETED ---------------------------------\n"); \
 			nm_connection_dump (src); \
 		} \
 		g_assert (success == FALSE); \
@@ -1334,6 +1334,28 @@ test_strength_wext (void)
 	g_assert_cmpint (nm_wifi_utils_level_to_quality (215), ==, 99);
 }
 
+#define _assert_strength_in_range(x) \
+	({ \
+		guint32 _x = (x); \
+		g_assert_cmpint (_x, >=, 0); \
+		g_assert_cmpint (_x, <=, 100); \
+	})
+
+static void
+test_strength_all (void)
+{
+	int val;
+
+	for (val = -200; val < 300; val++)
+		_assert_strength_in_range (nm_wifi_utils_level_to_quality (val));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT32));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT32));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT16));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT16));
+}
+
 /*****************************************************************************/
 
 NMTST_DEFINE ();
@@ -1497,6 +1519,8 @@ main (int argc, char **argv)
 	                 test_strength_percent);
 	g_test_add_func ("/wifi/strength/wext",
 	                 test_strength_wext);
+	g_test_add_func ("/wifi/strength/all",
+	                 test_strength_all);
 
 	return g_test_run ();
 }
diff --git a/src/devices/wwan/libnm-wwan.ver b/src/devices/wwan/libnm-wwan.ver
index eb577aaf..6efcb03f 100644
--- a/src/devices/wwan/libnm-wwan.ver
+++ b/src/devices/wwan/libnm-wwan.ver
@@ -20,7 +20,11 @@ global:
 	nm_modem_get_type;
 	nm_modem_get_uid;
 	nm_modem_ip4_pre_commit;
+	nm_modem_manager_get;
 	nm_modem_manager_get_type;
+	nm_modem_manager_name_owner_get;
+	nm_modem_manager_name_owner_ref;
+	nm_modem_manager_name_owner_unref;
 	nm_modem_owns_port;
 	nm_modem_set_mm_enabled;
 	nm_modem_stage3_ip4_config_start;
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 4a4d2f2c..22fb8c67 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -364,9 +364,8 @@ device_state_changed (NMDevice *device,
 {
 	NMDeviceModem *self = NM_DEVICE_MODEM (device);
 	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self);
-	NMSettingsConnection *connection = nm_device_get_settings_connection (device);
 
-	g_assert (priv->modem);
+	g_return_if_fail (priv->modem);
 
 	if (new_state == NM_DEVICE_STATE_UNAVAILABLE &&
 	    old_state < NM_DEVICE_STATE_UNAVAILABLE) {
@@ -374,30 +373,7 @@ device_state_changed (NMDevice *device,
 		_LOGI (LOGD_MB, "modem state '%s'",
 		       nm_modem_state_to_string (nm_modem_get_state (priv->modem)));
 	}
-
 	nm_modem_device_state_changed (priv->modem, new_state, old_state);
-
-	switch (nm_device_state_reason_check (reason)) {
-	case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED:
-	case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG:
-	case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT:
-	case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
-	case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
-		/* Block autoconnect of the just-failed connection for situations
-		 * where a retry attempt would just fail again.
-		 */
-		if (connection) {
-			nm_settings_connection_set_autoconnect_blocked_reason (connection,
-			                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_BLOCKED);
-		}
-		break;
-	default:
-		break;
-	}
 }
 
 static NMDeviceCapabilities
@@ -664,6 +640,16 @@ set_modem (NMDeviceModem *self, NMModem *modem)
 	g_signal_connect (modem, "notify::" NM_MODEM_SIM_OPERATOR_ID, G_CALLBACK (ids_changed_cb), self);
 }
 
+static guint32
+get_dhcp_timeout (NMDevice *device, int addr_family)
+{
+	/* DHCP is always done by the modem firmware, not by the network, and
+	 * by the time we get around to DHCP the firmware should already know
+	 * the IP addressing details.  So the DHCP timeout can be much shorter.
+	 */
+	return 15;
+}
+
 /*****************************************************************************/
 
 static void
@@ -718,18 +704,6 @@ nm_device_modem_init (NMDeviceModem *self)
 {
 }
 
-static void
-constructed (GObject *object)
-{
-	G_OBJECT_CLASS (nm_device_modem_parent_class)->constructed (object);
-
-	/* DHCP is always done by the modem firmware, not by the network, and
-	 * by the time we get around to DHCP the firmware should already know
-	 * the IP addressing details.  So the DHCP timeout can be much shorter.
-	 */
-	nm_device_set_dhcp_timeout (NM_DEVICE (object), 15);
-}
-
 NMDevice *
 nm_device_modem_new (NMModem *modem)
 {
@@ -786,7 +760,6 @@ nm_device_modem_class_init (NMDeviceModemClass *mclass)
 	object_class->dispose = dispose;
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
-	object_class->constructed = constructed;
 
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->get_type_description = get_type_description;
@@ -807,6 +780,7 @@ nm_device_modem_class_init (NMDeviceModemClass *mclass)
 	device_class->is_available = is_available;
 	device_class->get_ip_iface_identifier = get_ip_iface_identifier;
 	device_class->get_configured_mtu = nm_modem_get_configured_mtu;
+	device_class->get_dhcp_timeout = get_dhcp_timeout;
 
 	device_class->state_changed = device_state_changed;
 
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 4b16fb14..6e5f10a0 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -90,6 +90,9 @@ typedef struct {
 	MMBearerIpConfig *ipv4_config;
 	MMBearerIpConfig *ipv6_config;
 
+	guint idle_id_ip4;
+	guint idle_id_ip6;
+
 	guint32 pin_tries;
 } NMModemBroadbandPrivate;
 
@@ -860,21 +863,6 @@ set_mm_enabled (NMModem *_self,
 /* IPv4 method static */
 
 static gboolean
-ip4_string_to_num (const gchar *str, guint32 *out)
-{
-	guint32 addr = 0;
-	gboolean success = FALSE;
-
-	if (!str || inet_pton (AF_INET, str, &addr) != 1)
-		addr = 0;
-	else
-		success = TRUE;
-
-	*out = (guint32)addr;
-	return success;
-}
-
-static gboolean
 static_stage3_ip4_done (NMModemBroadband *self)
 {
 	GError *error = NULL;
@@ -883,7 +871,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	const gchar *address_string;
 	const gchar *gw_string;
 	guint32 address_network;
-	guint32 gw;
+	guint32 gw = 0;
 	NMPlatformIP4Address address;
 	const gchar **dns;
 	guint i;
@@ -895,7 +883,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 
 	/* Fully fail if invalid IP address retrieved */
 	address_string = mm_bearer_ip_config_get_address (self->_priv.ipv4_config);
-	if (!ip4_string_to_num (address_string, &address_network)) {
+	if (!nm_utils_parse_inaddr_bin (AF_INET, address_string, &address_network)) {
 		error = g_error_new (NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "(%s) retrieving IP4 configuration failed: invalid address given '%s'",
@@ -906,11 +894,20 @@ static_stage3_ip4_done (NMModemBroadband *self)
 
 	/* Missing gateway not a hard failure */
 	gw_string = mm_bearer_ip_config_get_gateway (self->_priv.ipv4_config);
-	ip4_string_to_num (gw_string, &gw);
+	if (   !gw_string
+	    || !nm_utils_parse_inaddr_bin (AF_INET, gw_string, &gw)) {
+		error = g_error_new (NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "(%s) retrieving IP4 configuration failed: invalid gateway address %s%s%s",
+		                     nm_modem_get_uid (NM_MODEM (self)),
+		                     NM_PRINT_FMT_QUOTE_STRING (gw_string));
+		goto out;
+	}
 
 	data_port = mm_bearer_get_interface (self->_priv.bearer);
 	g_assert (data_port);
-	config = nm_ip4_config_new (nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
+	config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+	                            nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
 
 	memset (&address, 0, sizeof (address));
 	address.address = address_network;
@@ -923,14 +920,30 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	_LOGI ("  address %s/%d", address_string, address.plen);
 
 	if (gw) {
-		nm_ip4_config_set_gateway (config, gw);
-		_LOGI ("  gateway %s", gw_string);
+		guint32 ip4_route_table, ip4_route_metric;
+
+		nm_modem_get_route_parameters (NM_MODEM (self),
+		                               &ip4_route_table,
+		                               &ip4_route_metric,
+		                               NULL,
+		                               NULL);
+		{
+			const NMPlatformIP4Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
+				.gateway = gw,
+				.table_coerced = nm_platform_route_table_coerce (ip4_route_table),
+				.metric = ip4_route_metric,
+			};
+
+			_LOGI ("  gateway %s", gw_string);
+			nm_ip4_config_add_route (config, &r, NULL);
+		}
 	}
 
 	/* DNS servers */
 	dns = mm_bearer_ip_config_get_dns (self->_priv.ipv4_config);
 	for (i = 0; dns && dns[i]; i++) {
-		if (   ip4_string_to_num (dns[i], &address_network)
+		if (   nm_utils_parse_inaddr_bin (AF_INET, dns[i], &address_network)
 		    && address_network > 0) {
 			nm_ip4_config_add_nameserver (config, address_network);
 			_LOGI ("  DNS %s", dns[i]);
@@ -944,15 +957,17 @@ out:
 }
 
 static NMActStageReturn
-static_stage3_ip4_config_start (NMModem *_self,
+static_stage3_ip4_config_start (NMModem *modem,
                                 NMActRequest *req,
                                 NMDeviceStateReason *out_failure_reason)
 {
-	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
+	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
+	NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE (self);
 
 	/* We schedule it in an idle just to follow the same logic as in the
 	 * generic modem implementation. */
-	g_idle_add ((GSourceFunc) static_stage3_ip4_done, self);
+	nm_clear_g_source (&priv->idle_id_ip4);
+	priv->idle_id_ip4 = g_idle_add ((GSourceFunc) static_stage3_ip4_done, self);
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
@@ -1004,7 +1019,8 @@ stage3_ip6_done (NMModemBroadband *self)
 
 	data_port = mm_bearer_get_interface (self->_priv.bearer);
 	g_assert (data_port);
-	config = nm_ip6_config_new (nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
+	config = nm_ip6_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+	                            nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
 
 	address.plen = mm_bearer_ip_config_get_prefix (self->_priv.ipv6_config);
 	if (address.plen <= 128)
@@ -1014,7 +1030,9 @@ stage3_ip6_done (NMModemBroadband *self)
 
 	address_string = mm_bearer_ip_config_get_gateway (self->_priv.ipv6_config);
 	if (address_string) {
-		if (!inet_pton (AF_INET6, address_string, (void *) &(address.address))) {
+		guint32 ip6_route_table, ip6_route_metric;
+
+		if (inet_pton (AF_INET6, address_string, &address.address) != 1) {
 			error = g_error_new (NM_DEVICE_ERROR,
 			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 			                     "(%s) retrieving IPv6 configuration failed: invalid gateway given '%s'",
@@ -1022,8 +1040,23 @@ stage3_ip6_done (NMModemBroadband *self)
 			                     address_string);
 			goto out;
 		}
-		_LOGI ("  gateway %s", address_string);
-		nm_ip6_config_set_gateway (config, &address.address);
+
+		nm_modem_get_route_parameters (NM_MODEM (self),
+		                               NULL,
+		                               NULL,
+		                               &ip6_route_table,
+		                               &ip6_route_metric);
+		{
+			const NMPlatformIP6Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
+				.gateway = address.address,
+				.table_coerced = nm_platform_route_table_coerce (ip6_route_table),
+				.metric = ip6_route_metric,
+			};
+
+			_LOGI ("  gateway %s", address_string);
+			nm_ip6_config_add_route (config, &r, NULL);
+		}
 	} else if (ip_method == NM_MODEM_IP_METHOD_STATIC) {
 		/* Gateway required for the 'static' method */
 		error = g_error_new (NM_DEVICE_ERROR,
@@ -1052,13 +1085,15 @@ out:
 }
 
 static NMActStageReturn
-stage3_ip6_config_request (NMModem *_self, NMDeviceStateReason *out_failure_reason)
+stage3_ip6_config_request (NMModem *modem, NMDeviceStateReason *out_failure_reason)
 {
-	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
+	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
+	NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE (self);
 
 	/* We schedule it in an idle just to follow the same logic as in the
 	 * generic modem implementation. */
-	g_idle_add ((GSourceFunc) stage3_ip6_done, self);
+	nm_clear_g_source (&priv->idle_id_ip6);
+	priv->idle_id_ip6 = g_idle_add ((GSourceFunc) stage3_ip6_done, self);
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
@@ -1409,6 +1444,10 @@ static void
 dispose (GObject *object)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (object);
+	NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->idle_id_ip4);
+	nm_clear_g_source (&priv->idle_id_ip6);
 
 	connect_context_clear (self);
 	g_clear_object (&self->_priv.ipv4_config);
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index b1f6d92e..59cd2bca 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -45,6 +45,10 @@
 
 /*****************************************************************************/
 
+NM_GOBJECT_PROPERTIES_DEFINE (NMModemManager,
+	PROP_NAME_OWNER,
+);
+
 enum {
 	MODEM_ADDED,
 	LAST_SIGNAL,
@@ -54,14 +58,38 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
 	GDBusConnection *dbus_connection;
-	MMManager *modem_manager;
-	guint mm_launch_id;
-	gulong mm_name_owner_changed_id;
-	gulong mm_object_added_id;
-	gulong mm_object_removed_id;
+
+	/* used during g_bus_get() and later during mm_manager_new(). */
+	GCancellable *main_cancellable;
+
+	struct {
+		MMManager *manager;
+		GCancellable *poke_cancellable;
+		gulong handle_name_owner_changed_id;
+		gulong handle_object_added_id;
+		gulong handle_object_removed_id;
+		guint relaunch_id;
+
+		/* this only has one use: that the <info> logging line about
+		 * ModemManager available distinguishes between first-time
+		 * and later name-owner-changed. */
+		enum {
+			LOG_AVAILABLE_NOT_INITIALIZED = 0,
+			LOG_AVAILABLE_YES,
+			LOG_AVAILABLE_NO,
+		} log_available:3;
+
+		GDBusProxy *proxy;
+		GCancellable *proxy_cancellable;
+		guint proxy_ref_count;
+		char *proxy_name_owner;
+	} modm;
 
 #if WITH_OFONO
-	GDBusProxy *ofono_proxy;
+	struct {
+		GDBusProxy *proxy;
+		GCancellable *cancellable;
+	} ofono;
 #endif
 
 	GHashTable *modems;
@@ -82,19 +110,35 @@ G_DEFINE_TYPE (NMModemManager, nm_modem_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_MB
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "modem-manager", __VA_ARGS__)
+
+/*****************************************************************************/
+
+NM_DEFINE_SINGLETON_GETTER (NMModemManager, nm_modem_manager_get, NM_TYPE_MODEM_MANAGER);
+
+/*****************************************************************************/
+
+static void modm_schedule_manager_relaunch (NMModemManager *self,
+                                            guint n_seconds);
+static void modm_ensure_manager (NMModemManager *self);
+
+/*****************************************************************************/
+
 static void
 handle_new_modem (NMModemManager *self, NMModem *modem)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	const char *path;
 
 	path = nm_modem_get_path (modem);
-	if (g_hash_table_lookup (self->_priv.modems, path)) {
+	if (g_hash_table_lookup (priv->modems, path)) {
 		g_warn_if_reached ();
 		return;
 	}
 
 	/* Track the new modem */
-	g_hash_table_insert (self->_priv.modems, g_strdup (path), modem);
+	g_hash_table_insert (priv->modems, g_strdup (path), modem);
 	g_signal_emit (self, signals[MODEM_ADDED], 0, modem);
 }
 
@@ -105,22 +149,27 @@ remove_one_modem (gpointer key, gpointer value, gpointer user_data)
 	return TRUE;
 }
 
+/*****************************************************************************/
+
 static void
-clear_modem_manager (NMModemManager *self)
+modm_clear_manager (NMModemManager *self)
 {
-	if (!self->_priv.modem_manager)
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	if (!priv->modm.manager)
 		return;
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_name_owner_changed_id);
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_object_added_id);
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_object_removed_id);
-	g_clear_object (&self->_priv.modem_manager);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_name_owner_changed_id);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_object_added_id);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_object_removed_id);
+	g_clear_object (&priv->modm.manager);
 }
 
 static void
-modem_object_added (MMManager *modem_manager,
-                    MMObject  *modem_object,
-                    NMModemManager *self)
+modm_handle_object_added (MMManager *modem_manager,
+                          MMObject  *modem_object,
+                          NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	const gchar *path;
 	MMModem *modem_iface;
 	NMModem *modem;
@@ -128,21 +177,21 @@ modem_object_added (MMManager *modem_manager,
 
 	/* Ensure we don't have the same modem already */
 	path = mm_object_get_path (modem_object);
-	if (g_hash_table_lookup (self->_priv.modems, path)) {
-		nm_log_warn (LOGD_MB, "modem with path %s already exists, ignoring", path);
+	if (g_hash_table_lookup (priv->modems, path)) {
+		_LOGW ("modem with path %s already exists, ignoring", path);
 		return;
 	}
 
 	/* Ensure we have the 'Modem' interface at least */
 	modem_iface = mm_object_peek_modem (modem_object);
 	if (!modem_iface) {
-		nm_log_warn (LOGD_MB, "modem with path %s doesn't have the Modem interface, ignoring", path);
+		_LOGW ("modem with path %s doesn't have the Modem interface, ignoring", path);
 		return;
 	}
 
 	/* Ensure we have a primary port reported */
 	if (!mm_modem_get_primary_port (modem_iface)) {
-		nm_log_warn (LOGD_MB, "modem with path %s has unknown primary port, ignoring", path);
+		_LOGW ("modem with path %s has unknown primary port, ignoring", path);
 		return;
 	}
 
@@ -150,65 +199,68 @@ modem_object_added (MMManager *modem_manager,
 	modem = nm_modem_broadband_new (G_OBJECT (modem_object), &error);
 	if (modem)
 		handle_new_modem (self, modem);
-	else {
-		nm_log_warn (LOGD_MB, "failed to create modem: %s",
-		             error->message);
-	}
+	else
+		_LOGW ("failed to create modem: %s", error->message);
 	g_clear_error (&error);
 }
 
 static void
-modem_object_removed (MMManager *manager,
-                      MMObject  *modem_object,
-                      NMModemManager *self)
+modm_handle_object_removed (MMManager *manager,
+                            MMObject  *modem_object,
+                            NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	NMModem *modem;
 	const gchar *path;
 
 	path = mm_object_get_path (modem_object);
-	modem = (NMModem *) g_hash_table_lookup (self->_priv.modems, path);
+	modem = (NMModem *) g_hash_table_lookup (priv->modems, path);
 	if (!modem)
 		return;
 
 	nm_modem_emit_removed (modem);
-	g_hash_table_remove (self->_priv.modems, path);
+	g_hash_table_remove (priv->modems, path);
 }
 
 static void
-modem_manager_available (NMModemManager *self)
+modm_manager_available (NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	GList *modems, *l;
 
-	nm_log_info (LOGD_MB, "ModemManager available in the bus");
+	if (priv->modm.log_available != LOG_AVAILABLE_YES) {
+		_LOGI ("ModemManager %savailable", priv->modm.log_available ? "now " : "");
+		priv->modm.log_available = LOG_AVAILABLE_YES;
+	}
 
 	/* Update initial modems list */
-	modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->_priv.modem_manager));
+	modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (priv->modm.manager));
 	for (l = modems; l; l = g_list_next (l))
-		modem_object_added (self->_priv.modem_manager, MM_OBJECT (l->data), self);
+		modm_handle_object_added (priv->modm.manager, MM_OBJECT (l->data), self);
 	g_list_free_full (modems, (GDestroyNotify) g_object_unref);
 }
 
-static void schedule_modem_manager_relaunch (NMModemManager *self,
-                                             guint n_seconds);
-static void ensure_modem_manager (NMModemManager *self);
-
 static void
-modem_manager_name_owner_changed (MMManager *modem_manager,
-                                  GParamSpec *pspec,
-                                  NMModemManager *self)
+modm_handle_name_owner_changed (MMManager *modem_manager,
+                                GParamSpec *pspec,
+                                NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gchar *name_owner;
 
 	/* Quit poking, if any */
-	nm_clear_g_source (&self->_priv.mm_launch_id);
+	nm_clear_g_source (&priv->modm.relaunch_id);
 
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (modem_manager));
 	if (!name_owner) {
-		nm_log_info (LOGD_MB, "ModemManager disappeared from bus");
+		if (priv->modm.log_available != LOG_AVAILABLE_NO) {
+			_LOGI ("ModemManager %savailable", priv->modm.log_available ? "no longer " : "not ");
+			priv->modm.log_available = LOG_AVAILABLE_NO;
+		}
 
 		/* If not managed by systemd, schedule relaunch */
 		if (!sd_booted ())
-			schedule_modem_manager_relaunch (self, 0);
+			modm_schedule_manager_relaunch (self, 0);
 
 		return;
 	}
@@ -220,18 +272,322 @@ modem_manager_name_owner_changed (MMManager *modem_manager,
 	 * nor 'object-removed' if it was created while there was no ModemManager in
 	 * the bus. This hack avoids this issue until we get a GIO with the fix
 	 * included... */
-	clear_modem_manager (self);
-	ensure_modem_manager (self);
+	modm_clear_manager (self);
+	modm_ensure_manager (self);
 
 	/* Whenever GDBusObjectManagerClient is fixed, we can just do the following:
-	 * modem_manager_available (self);
+	 * modm_manager_available (self);
 	 */
 }
 
+static void
+modm_manager_poke_cb (GObject *connection,
+                      GAsyncResult *res,
+                      gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *result = NULL;
+
+	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (connection), res, &error);
+
+	if (   !result
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->modm.poke_cancellable);
+
+	if (error) {
+		_LOGW ("error poking ModemManager: %s", error->message);
+
+		/* Don't reschedule poke is MM service doesn't exist. */
+		if (   !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
+			&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
+
+			/* Setup timeout to relaunch */
+			modm_schedule_manager_relaunch (self, MODEM_POKE_INTERVAL);
+		}
+	}
+}
+
+static void
+modm_manager_poke (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_clear_g_cancellable (&priv->modm.poke_cancellable);
+	priv->modm.poke_cancellable = g_cancellable_new ();
+
+	/* If there is no current owner right away, ensure we poke to get one */
+	g_dbus_connection_call (priv->dbus_connection,
+	                        NM_MODEM_MANAGER_MM_DBUS_SERVICE,
+	                        NM_MODEM_MANAGER_MM_DBUS_PATH,
+	                        DBUS_INTERFACE_PEER,
+	                        "Ping",
+	                        NULL,
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1,
+	                        priv->modm.poke_cancellable,
+	                        modm_manager_poke_cb,
+	                        self);
+}
+
+static void
+modm_manager_check_name_owner (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+	gs_free gchar *name_owner = NULL;
+
+	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->modm.manager));
+	if (name_owner) {
+		modm_manager_available (self);
+		return;
+	}
+
+	/* If the lifecycle is not managed by systemd, poke */
+	if (!sd_booted ())
+		modm_manager_poke (self);
+}
+
+static void
+modm_manager_new_cb (GObject *source,
+                     GAsyncResult *res,
+                     gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	MMManager *modem_manager;
+
+	modem_manager = mm_manager_new_finish (res, &error);
+	if (   !modem_manager
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (!priv->modm.manager);
+
+	g_clear_object (&priv->main_cancellable);
+
+	if (!modem_manager) {
+		/* We're not really supposed to get any error here. If we do get one,
+		 * though, just re-schedule the MMManager creation after some time.
+		 * During this period, name-owner changes won't be followed. */
+		_LOGW ("error creating ModemManager client: %s", error->message);
+		/* Setup timeout to relaunch */
+		modm_schedule_manager_relaunch (self, MODEM_POKE_INTERVAL);
+		return;
+	}
+
+	priv->modm.manager = modem_manager;
+
+	/* Setup signals in the GDBusObjectManagerClient */
+	priv->modm.handle_name_owner_changed_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "notify::name-owner",
+	                      G_CALLBACK (modm_handle_name_owner_changed),
+	                      self);
+	priv->modm.handle_object_added_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "object-added",
+	                      G_CALLBACK (modm_handle_object_added),
+	                      self);
+	priv->modm.handle_object_removed_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "object-removed",
+	                      G_CALLBACK (modm_handle_object_removed),
+	                      self);
+
+	modm_manager_check_name_owner (self);
+}
+
+static void
+modm_ensure_manager (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_assert (priv->dbus_connection);
+
+	/* Create the GDBusObjectManagerClient. We do not request to autostart, as
+	 * we don't really want the MMManager creation to fail. We can always poke
+	 * later on if we want to request the autostart */
+	if (!priv->modm.manager) {
+		if (!priv->main_cancellable)
+			priv->main_cancellable = g_cancellable_new ();
+		mm_manager_new (priv->dbus_connection,
+		                G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+		                priv->main_cancellable,
+		                modm_manager_new_cb,
+		                self);
+		return;
+	}
+
+	/* If already available, recheck name owner! */
+	modm_manager_check_name_owner (self);
+}
+
+static gboolean
+modm_schedule_manager_relaunch_cb (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	priv->modm.relaunch_id = 0;
+	modm_ensure_manager (self);
+	return G_SOURCE_REMOVE;
+}
+
+static void
+modm_schedule_manager_relaunch (NMModemManager *self,
+                                guint n_seconds)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	/* No need to pass an extra reference to self; timeout/idle will be
+	 * cancelled if the object gets disposed. */
+	if (n_seconds)
+		priv->modm.relaunch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)modm_schedule_manager_relaunch_cb, self);
+	else
+		priv->modm.relaunch_id = g_idle_add ((GSourceFunc)modm_schedule_manager_relaunch_cb, self);
+}
+
+/*****************************************************************************/
+
+static void
+modm_proxy_name_owner_reset (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+	char *name = NULL;
+
+	if (priv->modm.proxy)
+		name = g_dbus_proxy_get_name_owner (priv->modm.proxy);
+
+	if (nm_streq0 (priv->modm.proxy_name_owner, name)) {
+		g_free (name);
+		return;
+	}
+	g_free (priv->modm.proxy_name_owner);
+	priv->modm.proxy_name_owner = name;
+
+	_notify (self, PROP_NAME_OWNER);
+}
+
+static void
+modm_proxy_name_owner_changed_cb (GObject    *object,
+                                  GParamSpec *pspec,
+                                  gpointer    user_data)
+{
+	modm_proxy_name_owner_reset (user_data);
+}
+
+static void
+modm_proxy_new_cb (GObject *source_object,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	GDBusProxy *proxy;
+	gs_free_error GError *error = NULL;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->modm.proxy_cancellable);
+
+	if (!proxy) {
+		_LOGW ("could not obtain D-Bus proxy for ModemManager: %s", error->message);
+		return;
+	}
+
+	priv->modm.proxy = proxy;
+	g_signal_connect (priv->modm.proxy, "notify::g-name-owner",
+	                  G_CALLBACK (modm_proxy_name_owner_changed_cb), self);
+
+	modm_proxy_name_owner_reset (self);
+}
+
+void
+nm_modem_manager_name_owner_ref (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM_MANAGER (self));
+
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	if (priv->modm.proxy_ref_count++ > 0) {
+		/* only try once to create the proxy. If proxy creation
+		 * for the first "ref" failed, it's unclear what to do.
+		 * The proxy is hosed. */
+		return;
+	}
+
+	nm_assert (!priv->modm.proxy && !priv->modm.proxy_cancellable);
+
+	priv->modm.proxy_cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                            G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+	                          | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
+	                          | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                          NULL,
+	                          NM_MODEM_MANAGER_MM_DBUS_SERVICE,
+	                          NM_MODEM_MANAGER_MM_DBUS_PATH,
+	                          NM_MODEM_MANAGER_MM_DBUS_INTERFACE,
+	                          priv->modm.proxy_cancellable,
+	                          modm_proxy_new_cb,
+	                          self);
+}
+
+void
+nm_modem_manager_name_owner_unref (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM_MANAGER (self));
+
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->modm.proxy_ref_count > 0);
+
+	if (--priv->modm.proxy_ref_count > 0)
+		return;
+
+	nm_clear_g_cancellable (&priv->modm.proxy_cancellable);
+	g_clear_object (&priv->modm.proxy);
+
+	modm_proxy_name_owner_reset (self);
+}
+
+const char *
+nm_modem_manager_name_owner_get (NMModemManager *self)
+{
+	g_return_val_if_fail (NM_IS_MODEM_MANAGER (self), NULL);
+	nm_assert (NM_MODEM_MANAGER_GET_PRIVATE (self)->modm.proxy_ref_count > 0);
+
+	return NM_MODEM_MANAGER_GET_PRIVATE (self)->modm.proxy_name_owner;
+}
+
+/*****************************************************************************/
+
 #if WITH_OFONO
+
 static void
 ofono_create_modem (NMModemManager *self, const char *path)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	NMModem *modem = NULL;
 
 	/* Ensure duplicate modems aren't created.  Because we're not using the
@@ -239,12 +595,12 @@ ofono_create_modem (NMModemManager *self, const char *path)
 	 * receive ModemAdded signals before GetModems() returns, so some of the
 	 * modems returned from GetModems() may already have been created.
 	 */
-	if (!g_hash_table_lookup (self->_priv.modems, path)) {
+	if (!g_hash_table_lookup (priv->modems, path)) {
 		modem = nm_modem_ofono_new (path);
 		if (modem)
 			handle_new_modem (self, modem);
 		else
-			nm_log_warn (LOGD_MB, "Failed to create oFono modem for %s", path);
+			_LOGW ("Failed to create oFono modem for %s", path);
 	}
 }
 
@@ -256,80 +612,96 @@ ofono_signal_cb (GDBusProxy *proxy,
                  gpointer user_data)
 {
 	NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gchar *object_path;
 	NMModem *modem;
 
 	if (g_strcmp0 (signal_name, "ModemAdded") == 0) {
 		g_variant_get (parameters, "(oa{sv})", &object_path, NULL);
-		nm_log_info (LOGD_MB, "oFono modem appeared: %s", object_path);
+		_LOGI ("oFono modem appeared: %s", object_path);
 
 		ofono_create_modem (NM_MODEM_MANAGER (user_data), object_path);
 		g_free (object_path);
 	} else if (g_strcmp0 (signal_name, "ModemRemoved") == 0) {
 		g_variant_get (parameters, "(o)", &object_path);
-		nm_log_info (LOGD_MB, "oFono modem removed: %s", object_path);
+		_LOGI ("oFono modem removed: %s", object_path);
 
-		modem = (NMModem *) g_hash_table_lookup (self->_priv.modems, object_path);
+		modem = (NMModem *) g_hash_table_lookup (priv->modems, object_path);
 		if (modem) {
 			nm_modem_emit_removed (modem);
-			g_hash_table_remove (self->_priv.modems, object_path);
+			g_hash_table_remove (priv->modems, object_path);
 		} else {
-			nm_log_warn (LOGD_MB, "could not remove modem %s, not found in table",
-			             object_path);
+			_LOGW ("could not remove modem %s, not found in table",
+			       object_path);
 		}
 		g_free (object_path);
 	}
 }
 
 static void
-ofono_enumerate_devices_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
+ofono_enumerate_devices_done (GObject *proxy,
+                              GAsyncResult *res,
+                              gpointer user_data)
 {
-	NMModemManager *manager = NM_MODEM_MANAGER (user_data);
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
 	gs_free_error GError *error = NULL;
 	GVariant *results;
 	GVariantIter *iter;
 	const char *path;
 
-	results = g_dbus_proxy_call_finish (proxy, res, &error);
-	if (results) {
-		g_variant_get (results, "(a(oa{sv}))", &iter);
-		while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
-			ofono_create_modem (manager, path);
-		g_variant_iter_free (iter);
-		g_variant_unref (results);
-	}
+	results = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);
+	if (   !results
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
 
-	if (error) {
-		nm_log_warn (LOGD_MB, "failed to enumerate oFono devices: %s",
-		             error->message);
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->ofono.cancellable);
+
+	if (!results) {
+		_LOGW ("failed to enumerate oFono devices: %s",
+		       error->message);
+		return;
 	}
+
+	g_variant_get (results, "(a(oa{sv}))", &iter);
+	while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
+		ofono_create_modem (self, path);
+	g_variant_iter_free (iter);
+	g_variant_unref (results);
 }
 
 static void
-ofono_check_name_owner (NMModemManager *self)
+ofono_check_name_owner (NMModemManager *self, gboolean first_invocation)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gs_free char *name_owner = NULL;
 
-	name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->_priv.ofono_proxy));
+	name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (priv->ofono.proxy));
 	if (name_owner) {
-		nm_log_info (LOGD_MB, "oFono is now available");
+		_LOGI ("oFono is %savailable", first_invocation ? "" : "now ");
+
+		nm_clear_g_cancellable (&priv->ofono.cancellable);
+		priv->ofono.cancellable = g_cancellable_new ();
 
-		g_dbus_proxy_call (self->_priv.ofono_proxy,
+		g_dbus_proxy_call (priv->ofono.proxy,
 		                   "GetModems",
 		                   NULL,
 		                   G_DBUS_CALL_FLAGS_NONE,
 		                   -1,
-		                   NULL,
-		                   (GAsyncReadyCallback) ofono_enumerate_devices_done,
-		                   g_object_ref (self));
+		                   priv->ofono.cancellable,
+		                   ofono_enumerate_devices_done,
+		                   self);
 	} else {
 		GHashTableIter iter;
 		NMModem *modem;
 
-		nm_log_info (LOGD_MB, "oFono disappeared from bus");
+		_LOGI ("oFono is %savailable", first_invocation ? "not " : "no longer ");
 
 		/* Remove any oFono modems that might be left around */
-		g_hash_table_iter_init (&iter, self->_priv.modems);
+		g_hash_table_iter_init (&iter, priv->modems);
 		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &modem)) {
 			if (NM_IS_MODEM_OFONO (modem)) {
 				nm_modem_emit_removed (modem);
@@ -344,219 +716,121 @@ ofono_name_owner_changed (GDBusProxy *ofono_proxy,
                           GParamSpec *pspec,
                           NMModemManager *self)
 {
-	ofono_check_name_owner (self);
+	ofono_check_name_owner (self, FALSE);
 }
 
 static void
-ofono_proxy_new_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+ofono_proxy_new_cb (GObject *source_object,
+                    GAsyncResult *res,
+                    gpointer user_data)
 {
-	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
 	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	self->_priv.ofono_proxy = g_dbus_proxy_new_finish (res, &error);
-	if (error) {
-		nm_log_warn (LOGD_MB, "error getting oFono bus proxy: %s", error->message);
+	proxy = g_dbus_proxy_new_finish (res, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->ofono.cancellable);
+
+	if (!proxy) {
+		_LOGW ("error getting oFono bus proxy: %s", error->message);
 		return;
 	}
 
-	g_signal_connect (self->_priv.ofono_proxy,
+	priv->ofono.proxy = proxy;
+
+	g_signal_connect (priv->ofono.proxy,
 	                  "notify::g-name-owner",
 	                  G_CALLBACK (ofono_name_owner_changed),
 	                  self);
 
-	g_signal_connect (self->_priv.ofono_proxy,
+	g_signal_connect (priv->ofono.proxy,
 	                  "g-signal",
 	                  G_CALLBACK (ofono_signal_cb),
 	                  self);
 
-	ofono_check_name_owner (self);
+	ofono_check_name_owner (self, TRUE);
 }
 
 static void
-ensure_ofono_client (NMModemManager *self)
+ofono_init_proxy (NMModemManager *self)
 {
-	g_assert (self->_priv.dbus_connection);
-	g_dbus_proxy_new (self->_priv.dbus_connection,
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (priv->dbus_connection);
+	nm_assert (!priv->ofono.cancellable);
+
+	priv->ofono.cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new (priv->dbus_connection,
 	                  G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 	                  NULL,
 	                  OFONO_DBUS_SERVICE,
 	                  OFONO_DBUS_PATH,
 	                  OFONO_DBUS_INTERFACE,
-	                  NULL,
-	                  (GAsyncReadyCallback) ofono_proxy_new_cb,
-	                  g_object_ref (self));
+	                  priv->ofono.cancellable,
+	                  ofono_proxy_new_cb,
+	                  self);
 }
 #endif
 
-static void
-modem_manager_poke_cb (GDBusConnection *connection,
-                       GAsyncResult *res,
-                       NMModemManager *self)
-{
-	GError *error = NULL;
-	GVariant *result;
-
-	result = g_dbus_connection_call_finish (connection, res, &error);
-	if (error) {
-		nm_log_warn (LOGD_MB, "error poking ModemManager: %s",
-					error ? error->message : "");
-
-		/* Don't reschedule poke is MM service doesn't exist. */
-		if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
-			&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
-
-			/* Setup timeout to relaunch */
-			schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
-		}
-
-		g_error_free (error);
-	} else
-		g_variant_unref (result);
-
-	/* Balance refcount */
-	g_object_unref (self);
-}
-
-static void
-modem_manager_poke (NMModemManager *self)
-{
-	/* If there is no current owner right away, ensure we poke to get one */
-	g_dbus_connection_call (self->_priv.dbus_connection,
-	                        "org.freedesktop.ModemManager1",
-	                        "/org/freedesktop/ModemManager1",
-	                        DBUS_INTERFACE_PEER,
-	                        "Ping",
-	                        NULL, /* inputs */
-	                        NULL, /* outputs */
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        -1,
-	                        NULL, /* cancellable */
-	                        (GAsyncReadyCallback)modem_manager_poke_cb, /* callback */
-	                        g_object_ref (self)); /* user_data */
-}
+/*****************************************************************************/
 
 static void
-modem_manager_check_name_owner (NMModemManager *self)
+bus_get_ready (GObject *source,
+               GAsyncResult *res,
+               gpointer user_data)
 {
-	gs_free gchar *name_owner = NULL;
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusConnection *connection;
 
-	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (self->_priv.modem_manager));
-	if (name_owner) {
-		/* Available! */
-		modem_manager_available (self);
+	connection = g_bus_get_finish (res, &error);
+	if (   !connection
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
-	}
-
-	/* If the lifecycle is not managed by systemd, poke */
-	if (!sd_booted ())
-		modem_manager_poke (self);
-}
-
-static void
-manager_new_ready (GObject *source,
-                   GAsyncResult *res,
-                   NMModemManager *self)
-{
-	/* Note we always get an extra reference to self here */
 
-	GError *error = NULL;
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (!self->_priv.modem_manager);
-
-	self->_priv.modem_manager = mm_manager_new_finish (res, &error);
-	if (!self->_priv.modem_manager) {
-		/* We're not really supposed to get any error here. If we do get one,
-		 * though, just re-schedule the MMManager creation after some time.
-		 * During this period, name-owner changes won't be followed. */
-		nm_log_warn (LOGD_MB, "error creating ModemManager client: %s", error->message);
-		g_error_free (error);
-		/* Setup timeout to relaunch */
-		schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
-	} else {
-		/* Setup signals in the GDBusObjectManagerClient */
-		self->_priv.mm_name_owner_changed_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "notify::name-owner",
-		                      G_CALLBACK (modem_manager_name_owner_changed),
-		                      self);
-		self->_priv.mm_object_added_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "object-added",
-		                      G_CALLBACK (modem_object_added),
-		                      self);
-		self->_priv.mm_object_removed_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "object-removed",
-		                      G_CALLBACK (modem_object_removed),
-		                      self);
-
-		modem_manager_check_name_owner (self);
-	}
-
-	/* Balance refcount */
-	g_object_unref (self);
-}
-
-static void
-ensure_modem_manager (NMModemManager *self)
-{
-	g_assert (self->_priv.dbus_connection);
-
-	/* Create the GDBusObjectManagerClient. We do not request to autostart, as
-	 * we don't really want the MMManager creation to fail. We can always poke
-	 * later on if we want to request the autostart */
-	if (!self->_priv.modem_manager) {
-		mm_manager_new (self->_priv.dbus_connection,
-		                G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
-		                NULL,
-		                (GAsyncReadyCallback)manager_new_ready,
-		                g_object_ref (self));
+	if (!connection) {
+		_LOGW ("error getting bus connection: %s", error->message);
 		return;
 	}
 
-	/* If already available, recheck name owner! */
-	modem_manager_check_name_owner (self);
-}
+	priv->dbus_connection = connection;
 
-static gboolean
-mm_launch_cb (NMModemManager *self)
-{
-	self->_priv.mm_launch_id = 0;
-	ensure_modem_manager (self);
-	return G_SOURCE_REMOVE;
+	modm_ensure_manager (self);
+#if WITH_OFONO
+	ofono_init_proxy (self);
+#endif
 }
 
-static void
-schedule_modem_manager_relaunch (NMModemManager *self,
-                                 guint n_seconds)
-{
-	/* No need to pass an extra reference to self; timeout/idle will be
-	 * cancelled if the object gets disposed. */
-	if (n_seconds)
-		self->_priv.mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)mm_launch_cb, self);
-	else
-		self->_priv.mm_launch_id = g_idle_add ((GSourceFunc)mm_launch_cb, self);
-}
+/*****************************************************************************/
 
 static void
-bus_get_ready (GObject *source,
-               GAsyncResult *res,
-               gpointer user_data)
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
 {
-	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
-	gs_free_error GError *error = NULL;
-
-	self->_priv.dbus_connection = g_bus_get_finish (res, &error);
-	if (!self->_priv.dbus_connection) {
-		nm_log_warn (LOGD_MB, "error getting bus connection: %s", error->message);
-		return;
+	NMModemManager *self = NM_MODEM_MANAGER (object);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_NAME_OWNER:
+		g_value_set_string (value, priv->modm.proxy_name_owner);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
 	}
-
-	/* Got the bus, ensure clients */
-	ensure_modem_manager (self);
-#if WITH_OFONO
-	ensure_ofono_client (self);
-#endif
 }
 
 /*****************************************************************************/
@@ -564,36 +838,50 @@ bus_get_ready (GObject *source,
 static void
 nm_modem_manager_init (NMModemManager *self)
 {
-	self->_priv.modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	priv->modems = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+
+	priv->main_cancellable = g_cancellable_new ();
 
 	g_bus_get (G_BUS_TYPE_SYSTEM,
-	           NULL,
-	           (GAsyncReadyCallback)bus_get_ready,
-	           g_object_ref (self));
+	           priv->main_cancellable,
+	           bus_get_ready,
+	           self);
 }
 
 static void
 dispose (GObject *object)
 {
 	NMModemManager *self = NM_MODEM_MANAGER (object);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 
-	nm_clear_g_source (&self->_priv.mm_launch_id);
+	nm_clear_g_cancellable (&priv->main_cancellable);
+	nm_clear_g_cancellable (&priv->modm.poke_cancellable);
 
-	clear_modem_manager (self);
+	nm_clear_g_source (&priv->modm.relaunch_id);
+
+	nm_clear_g_cancellable (&priv->modm.proxy_cancellable);
+	g_clear_object (&priv->modm.proxy);
+	nm_clear_g_free (&priv->modm.proxy_name_owner);
+
+	modm_clear_manager (self);
 
 #if WITH_OFONO
-	if (self->_priv.ofono_proxy) {
-		g_signal_handlers_disconnect_by_func (self->_priv.ofono_proxy, ofono_name_owner_changed, self);
-		g_signal_handlers_disconnect_by_func (self->_priv.ofono_proxy, ofono_signal_cb, self);
-		g_clear_object (&self->_priv.ofono_proxy);
+	if (priv->ofono.proxy) {
+		g_signal_handlers_disconnect_by_func (priv->ofono.proxy, ofono_name_owner_changed, self);
+		g_signal_handlers_disconnect_by_func (priv->ofono.proxy, ofono_signal_cb, self);
+		g_clear_object (&priv->ofono.proxy);
 	}
+	nm_clear_g_cancellable (&priv->ofono.cancellable);
 #endif
 
-	g_clear_object (&self->_priv.dbus_connection);
+	g_clear_object (&priv->dbus_connection);
 
-	if (self->_priv.modems) {
-		g_hash_table_foreach_remove (self->_priv.modems, remove_one_modem, object);
-		g_hash_table_destroy (self->_priv.modems);
+	if (priv->modems) {
+		g_hash_table_foreach_remove (priv->modems, remove_one_modem, object);
+		g_hash_table_destroy (priv->modems);
+		priv->modems = NULL;
 	}
 
 	G_OBJECT_CLASS (nm_modem_manager_parent_class)->dispose (object);
@@ -605,6 +893,15 @@ nm_modem_manager_class_init (NMModemManagerClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	object_class->dispose = dispose;
+	object_class->get_property = get_property;
+
+	obj_properties[PROP_NAME_OWNER] =
+	     g_param_spec_string (NM_MODEM_MANAGER_NAME_OWNER, "", "",
+	                          NULL,
+	                          G_PARAM_READABLE
+	                          | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[MODEM_ADDED] =
 	    g_signal_new (NM_MODEM_MANAGER_MODEM_ADDED,
diff --git a/src/devices/wwan/nm-modem-manager.h b/src/devices/wwan/nm-modem-manager.h
index 65594dfa..5f913083 100644
--- a/src/devices/wwan/nm-modem-manager.h
+++ b/src/devices/wwan/nm-modem-manager.h
@@ -34,9 +34,22 @@
 
 #define NM_MODEM_MANAGER_MODEM_ADDED "modem-added"
 
+#define NM_MODEM_MANAGER_NAME_OWNER "name-owner"
+
+#define NM_MODEM_MANAGER_MM_DBUS_SERVICE   "org.freedesktop.ModemManager1"
+#define NM_MODEM_MANAGER_MM_DBUS_PATH      "/org/freedesktop/ModemManager1"
+#define NM_MODEM_MANAGER_MM_DBUS_INTERFACE "org.freedesktop.ModemManager1"
+
 typedef struct _NMModemManager NMModemManager;
 typedef struct _NMModemManagerClass NMModemManagerClass;
 
 GType nm_modem_manager_get_type (void);
 
+NMModemManager *nm_modem_manager_get (void);
+
+void nm_modem_manager_name_owner_ref (NMModemManager *self);
+void nm_modem_manager_name_owner_unref (NMModemManager *self);
+
+const char *nm_modem_manager_name_owner_get (NMModemManager *self);
+
 #endif /* __NETWORKMANAGER_MODEM_MANAGER_H__ */
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index 52b335c7..8b3fc2e8 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -46,6 +46,11 @@ typedef struct {
 	GDBusProxy *context_proxy;
 	GDBusProxy *sim_proxy;
 
+	GCancellable *modem_proxy_cancellable;
+	GCancellable *connman_proxy_cancellable;
+	GCancellable *context_proxy_cancellable;
+	GCancellable *sim_proxy_cancellable;
+
 	GError *property_error;
 
 	char *context_path;
@@ -99,22 +104,6 @@ G_DEFINE_TYPE (NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
 
 /*****************************************************************************/
 
-static gboolean
-ip_string_to_network_address (const gchar *str,
-                              guint32 *out)
-{
-	guint32 addr = 0;
-	gboolean success = FALSE;
-
-	if (!str || inet_pton (AF_INET, str, &addr) != 1)
-		addr = 0;
-	else
-		success = TRUE;
-
-	*out = (guint32)addr;
-	return success;
-}
-
 static void
 get_capabilities (NMModem *_self,
                   NMDeviceModemCapabilities *modem_caps,
@@ -165,29 +154,17 @@ typedef struct {
 static void
 disconnect_context_complete (DisconnectContext *ctx)
 {
-	g_simple_async_result_complete_in_idle (ctx->result);
 	if (ctx->cancellable)
 		g_object_unref (ctx->cancellable);
-	g_object_unref (ctx->result);
+	if (ctx->result) {
+		g_simple_async_result_complete_in_idle (ctx->result);
+		g_object_unref (ctx->result);
+	}
 	g_object_unref (ctx->self);
 	g_slice_free (DisconnectContext, ctx);
 }
 
 static gboolean
-disconnect_context_complete_if_cancelled (DisconnectContext *ctx)
-{
-	GError *error = NULL;
-
-	if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		disconnect_context_complete (ctx);
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
-static gboolean
 disconnect_finish (NMModem *self,
                    GAsyncResult *result,
                    GError **error)
@@ -196,25 +173,25 @@ disconnect_finish (NMModem *self,
 }
 
 static void
-disconnect_done (GDBusProxy *proxy,
-				 GAsyncResult *result,
-				 gpointer user_data)
+disconnect_done (GObject *source,
+                 GAsyncResult *result,
+                 gpointer user_data)
 {
 	DisconnectContext *ctx = (DisconnectContext*) user_data;
 	NMModemOfono *self = ctx->self;
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v = NULL;
 
-	g_dbus_proxy_call_finish (proxy, result, &error);
+	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-		_LOGD ("disconnect cancelled");
+		if (ctx->result)
+			g_simple_async_result_take_error (ctx->result, g_steal_pointer (&error));
+		disconnect_context_complete (ctx);
 		return;
 	}
 
-	if (error) {
-		if (ctx->warn)
-			_LOGW ("failed to disconnect modem: %s", error->message);
-		g_clear_error (&error);
-	}
+	if (error && ctx->warn)
+		_LOGW ("failed to disconnect modem: %s", error->message);
 
 	_LOGD ("modem disconnected");
 
@@ -233,18 +210,15 @@ disconnect (NMModem *modem,
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
 	DisconnectContext *ctx;
 	NMModemState state = nm_modem_get_state (NM_MODEM (self));
+	GError *error = NULL;
 
 	_LOGD ("warn: %s modem_state: %s",
 	       warn ? "TRUE" : "FALSE",
 	       nm_modem_state_to_string (state));
 
-	if (state != NM_MODEM_STATE_CONNECTED)
-		return;
-
-	ctx = g_slice_new (DisconnectContext);
+	ctx = g_slice_new0 (DisconnectContext);
 	ctx->self = g_object_ref (self);
 	ctx->warn = warn;
-
 	if (callback) {
 		ctx->result = g_simple_async_result_new (G_OBJECT (self),
 		                                         callback,
@@ -252,9 +226,28 @@ disconnect (NMModem *modem,
 		                                         disconnect);
 	}
 
-	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-	if (disconnect_context_complete_if_cancelled (ctx))
+	if (state != NM_MODEM_STATE_CONNECTED) {
+		if (ctx->result) {
+			g_set_error_literal (&error,
+			                     NM_UTILS_ERROR,
+			                     NM_UTILS_ERROR_UNKNOWN,
+			                     ("modem is currently not connected"));
+			g_simple_async_result_take_error (ctx->result, error);
+		}
+		disconnect_context_complete (ctx);
 		return;
+	}
+
+	if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		if (ctx->result)
+			g_simple_async_result_take_error (ctx->result, error);
+		else
+			g_clear_error (&error);
+		disconnect_context_complete (ctx);
+		return;
+	}
+
+	ctx->cancellable = nm_g_object_ref (cancellable);
 
 	nm_modem_set_state (NM_MODEM (self),
 	                    NM_MODEM_STATE_DISCONNECTING,
@@ -267,8 +260,8 @@ disconnect (NMModem *modem,
 	                                  g_variant_new ("b", warn)),
 	                   G_DBUS_CALL_FLAGS_NONE,
 	                   20000,
-	                   NULL,
-	                   (GAsyncReadyCallback) disconnect_done,
+	                   ctx->cancellable,
+	                   disconnect_done,
 	                   ctx);
 }
 
@@ -375,22 +368,35 @@ sim_property_changed (GDBusProxy *proxy,
 }
 
 static void
-sim_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+sim_get_properties_done (GObject *source,
+                         GAsyncResult *result,
+                         gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	GError *error = NULL;
-	GVariant *v_properties, *v_dict, *v;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v_properties = NULL;
+	gs_unref_variant GVariant *v_dict = NULL;
+	GVariant *v;
 	GVariantIter i;
 	const char *property;
 
-	v_properties = _nm_dbus_proxy_call_finish (proxy,
+	v_properties = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source),
 	                                           result,
 	                                           G_VARIANT_TYPE ("(a{sv})"),
 	                                           &error);
+	if (   !v_properties
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_clear_object (&priv->sim_proxy_cancellable);
+
 	if (!v_properties) {
 		g_dbus_error_strip_remote_error (error);
 		_LOGW ("error getting sim properties: %s", error->message);
-		g_error_free (error);
 		return;
 	}
 
@@ -418,9 +424,49 @@ sim_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 		handle_sim_property (NULL, property, v, self);
 		g_variant_unref (v);
 	}
+}
 
-	g_variant_unref (v_dict);
-	g_variant_unref (v_properties);
+static void
+_sim_proxy_new_cb (GObject *source,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("failed to create SimManager proxy: %s", error->message);
+		g_clear_object (&priv->sim_proxy_cancellable);
+		return;
+	}
+
+	priv->sim_proxy = proxy;
+
+	/* Watch for custom ofono PropertyChanged signals */
+	_nm_dbus_signal_connect (priv->sim_proxy,
+	                         "PropertyChanged",
+	                         G_VARIANT_TYPE ("(sv)"),
+	                         G_CALLBACK (sim_property_changed),
+	                         self);
+
+	g_dbus_proxy_call (priv->sim_proxy,
+	                   "GetProperties",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   priv->sim_proxy_cancellable,
+	                   sim_get_properties_done,
+	                   self);
 }
 
 static void
@@ -430,47 +476,30 @@ handle_sim_iface (NMModemOfono *self, gboolean found)
 
 	_LOGD ("SimManager interface %sfound", found ? "" : "not ");
 
-	if (!found && priv->sim_proxy) {
+	if (!found && (priv->sim_proxy || priv->sim_proxy_cancellable)) {
 		_LOGI ("SimManager interface disappeared");
-		g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
-		g_clear_object (&priv->sim_proxy);
+		nm_clear_g_cancellable (&priv->sim_proxy_cancellable);
+		if (priv->sim_proxy) {
+			g_signal_handlers_disconnect_by_data (priv->sim_proxy, self);
+			g_clear_object (&priv->sim_proxy);
+		}
 		g_clear_pointer (&priv->imsi, g_free);
 		update_modem_state (self);
-	} else if (found && !priv->sim_proxy) {
-		GError *error = NULL;
-
+	} else if (found && (!priv->sim_proxy && !priv->sim_proxy_cancellable)) {
 		_LOGI ("found new SimManager interface");
 
-		priv->sim_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-		                                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
-		                                                 | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
-		                                                 NULL, /* GDBusInterfaceInfo */
-		                                                 OFONO_DBUS_SERVICE,
-		                                                 nm_modem_get_path (NM_MODEM (self)),
-		                                                 OFONO_DBUS_INTERFACE_SIM_MANAGER,
-		                                                 NULL, /* GCancellable */
-		                                                 &error);
-		if (priv->sim_proxy == NULL) {
-			_LOGW ("failed to create SimManager proxy: %s", error->message);
-			g_error_free (error);
-			return;
-		}
-
-		/* Watch for custom ofono PropertyChanged signals */
-		_nm_dbus_signal_connect (priv->sim_proxy,
-		                         "PropertyChanged",
-		                         G_VARIANT_TYPE ("(sv)"),
-		                         G_CALLBACK (sim_property_changed),
-		                         self);
-
-		g_dbus_proxy_call (priv->sim_proxy,
-		                   "GetProperties",
-		                   NULL,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   20000,
-		                   NULL,
-		                   (GAsyncReadyCallback) sim_get_properties_done,
-		                   g_object_ref (self));
+		priv->sim_proxy_cancellable = g_cancellable_new ();
+
+		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+		                            G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+		                          | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+		                          NULL, /* GDBusInterfaceInfo */
+		                          OFONO_DBUS_SERVICE,
+		                          nm_modem_get_path (NM_MODEM (self)),
+		                          OFONO_DBUS_INTERFACE_SIM_MANAGER,
+		                          priv->sim_proxy_cancellable, /* GCancellable */
+		                          _sim_proxy_new_cb,
+		                          self);
 	}
 }
 
@@ -514,22 +543,35 @@ connman_property_changed (GDBusProxy *proxy,
 }
 
 static void
-connman_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+connman_get_properties_done (GObject *source,
+                             GAsyncResult *result,
+                             gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	GError *error = NULL;
-	GVariant *v_properties, *v_dict, *v;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v_properties = NULL;
+	gs_unref_variant GVariant *v_dict = NULL;
+	GVariant *v;
 	GVariantIter i;
 	const char *property;
 
-	v_properties = _nm_dbus_proxy_call_finish (proxy,
-		                                       result,
-		                                       G_VARIANT_TYPE ("(a{sv})"),
-		                                       &error);
+	v_properties = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source),
+	                                           result,
+	                                           G_VARIANT_TYPE ("(a{sv})"),
+	                                           &error);
+	if (   !v_properties
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_clear_object (&priv->connman_proxy_cancellable);
+
 	if (!v_properties) {
 		g_dbus_error_strip_remote_error (error);
 		_LOGW ("error getting connman properties: %s", error->message);
-		g_error_free (error);
 		return;
 	}
 
@@ -549,9 +591,48 @@ connman_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer u
 		handle_connman_property (NULL, property, v, self);
 		g_variant_unref (v);
 	}
+}
+
+static void
+_connman_proxy_new_cb (GObject *source,
+                       GAsyncResult *result,
+                       gpointer user_data)
+{
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	g_variant_unref (v_dict);
-	g_variant_unref (v_properties);
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("failed to create ConnectionManager proxy: %s", error->message);
+		g_clear_object (&priv->connman_proxy_cancellable);
+		return;
+	}
+
+	priv->connman_proxy = proxy;
+
+	_nm_dbus_signal_connect (priv->connman_proxy,
+	                         "PropertyChanged",
+	                         G_VARIANT_TYPE ("(sv)"),
+	                         G_CALLBACK (connman_property_changed),
+	                         self);
+
+	g_dbus_proxy_call (priv->connman_proxy,
+	                   "GetProperties",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   priv->connman_proxy_cancellable,
+	                   connman_get_properties_done,
+	                   self);
 }
 
 static void
@@ -561,11 +642,13 @@ handle_connman_iface (NMModemOfono *self, gboolean found)
 
 	_LOGD ("ConnectionManager interface %sfound", found ? "" : "not ");
 
-	if (!found && priv->connman_proxy) {
+	if (!found && (priv->connman_proxy || priv->connman_proxy_cancellable)) {
 		_LOGI ("ConnectionManager interface disappeared");
-
-		g_signal_handlers_disconnect_by_data (priv->connman_proxy, NM_MODEM_OFONO (self));
-		g_clear_object (&priv->connman_proxy);
+		nm_clear_g_cancellable (&priv->connman_proxy_cancellable);
+		if (priv->connman_proxy) {
+			g_signal_handlers_disconnect_by_data (priv->connman_proxy, self);
+			g_clear_object (&priv->connman_proxy);
+		}
 
 		/* The connection manager proxy disappeared, we should
 		 * consider the modem disabled.
@@ -573,41 +656,21 @@ handle_connman_iface (NMModemOfono *self, gboolean found)
 		priv->gprs_attached = FALSE;
 
 		update_modem_state (self);
-	} else if (found && !priv->connman_proxy) {
-		GError *error = NULL;
-
+	} else if (found && (!priv->connman_proxy && !priv->connman_proxy_cancellable)) {
 		_LOGI ("found new ConnectionManager interface");
 
-		priv->connman_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-		                                                     G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
-		                                                     | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
-		                                                     NULL, /* GDBusInterfaceInfo */
-		                                                     OFONO_DBUS_SERVICE,
-		                                                     nm_modem_get_path (NM_MODEM (self)),
-		                                                     OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
-		                                                     NULL, /* GCancellable */
-		                                                     &error);
-		if (priv->connman_proxy == NULL) {
-			_LOGW ("failed to create ConnectionManager proxy: %s", error->message);
-			g_error_free (error);
-			return;
-		}
-
-		/* Watch for custom ofono PropertyChanged signals */
-		_nm_dbus_signal_connect (priv->connman_proxy,
-		                         "PropertyChanged",
-		                         G_VARIANT_TYPE ("(sv)"),
-		                         G_CALLBACK (connman_property_changed),
-		                         self);
-
-		g_dbus_proxy_call (priv->connman_proxy,
-		                   "GetProperties",
-		                   NULL,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   20000,
-		                   NULL,
-		                   (GAsyncReadyCallback) connman_get_properties_done,
-		                   g_object_ref (self));
+		priv->connman_proxy_cancellable = g_cancellable_new ();
+
+		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+		                            G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+		                          | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+		                          NULL, /* GDBusInterfaceInfo */
+		                          OFONO_DBUS_SERVICE,
+		                          nm_modem_get_path (NM_MODEM (self)),
+		                          OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
+		                          priv->connman_proxy_cancellable,
+		                          _connman_proxy_new_cb,
+		                          NULL);
 	}
 }
 
@@ -667,22 +730,35 @@ modem_property_changed (GDBusProxy *proxy,
 }
 
 static void
-modem_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+modem_get_properties_done (GObject *source,
+                           GAsyncResult *result,
+                           gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	GError *error = NULL;
-	GVariant *v_properties, *v_dict, *v;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v_properties = NULL;
+	gs_unref_variant GVariant *v_dict = NULL;
+	GVariant *v;
 	GVariantIter i;
 	const char *property;
 
-	v_properties = _nm_dbus_proxy_call_finish (proxy,
+	v_properties = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source),
 	                                           result,
 	                                           G_VARIANT_TYPE ("(a{sv})"),
 	                                           &error);
+	if (   !v_properties
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_clear_object (&priv->modem_proxy_cancellable);
+
 	if (!v_properties) {
 		g_dbus_error_strip_remote_error (error);
 		_LOGW ("error getting modem properties: %s", error->message);
-		g_error_free (error);
 		return;
 	}
 
@@ -706,21 +782,29 @@ modem_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer use
 		handle_modem_property (NULL, property, v, self);
 		g_variant_unref (v);
 	}
-
-	g_variant_unref (v_dict);
-	g_variant_unref (v_properties);
 }
 
 static void
-stage1_prepare_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+stage1_prepare_done (GObject *source,
+                     GAsyncResult *result,
+                     gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
-	GError *error = NULL;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v = NULL;
+
+	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_clear_object (&priv->context_proxy_cancellable);
 
 	g_clear_pointer (&priv->connect_properties, g_hash_table_destroy);
 
-	g_dbus_proxy_call_finish (proxy, result, &error);
 	if (error) {
 		_LOGW ("connection failed: %s", error->message);
 
@@ -732,8 +816,6 @@ stage1_prepare_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data
 		 * leading to the connection being disabled, and a 5m
 		 * timeout...
 		 */
-
-		g_clear_error (&error);
 	}
 }
 
@@ -751,6 +833,7 @@ context_property_changed (GDBusProxy *proxy,
 	const gchar *s, *addr_s;
 	const gchar **array, **iter;
 	guint32 address_network, gateway_network;
+	guint32 ip4_route_table, ip4_route_metric;
 	guint prefix = 0;
 
 	_LOGD ("PropertyChanged: %s", property);
@@ -809,15 +892,20 @@ context_property_changed (GDBusProxy *proxy,
 	 * 'Interface'.
 	 *
 	 * This needs discussion with upstream.
+	 *
+	 * FIXME: it is no longer allowed to omit the ifindex for NMIP4Config instances.
+	 * This is broken.
 	 */
-	priv->ip4_config = nm_ip4_config_new (0);
+	priv->ip4_config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+	                                      0);
 
 	/* TODO: simply if/else error logic! */
 
 	if (g_variant_lookup (v_dict, "Address", "&s", &addr_s)) {
 		_LOGD ("Address: %s", addr_s);
 
-		if (ip_string_to_network_address (addr_s, &address_network)) {
+		if (   addr_s
+		    && nm_utils_parse_inaddr_bin (AF_INET, addr_s, &address_network)) {
 			addr.address = address_network;
 			addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
 		} else {
@@ -833,7 +921,8 @@ context_property_changed (GDBusProxy *proxy,
 	if (g_variant_lookup (v_dict, "Netmask", "&s", &s)) {
 		_LOGD ("Netmask: %s", s);
 
-		if (s && ip_string_to_network_address (s, &address_network)) {
+		if (   s
+		    && nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
 			prefix = nm_utils_ip4_netmask_to_prefix (address_network);
 			if (prefix > 0)
 				addr.plen = prefix;
@@ -850,15 +939,30 @@ context_property_changed (GDBusProxy *proxy,
 
 	nm_ip4_config_add_address (priv->ip4_config, &addr);
 
-	if (g_variant_lookup (v_dict, "Gateway", "&s", &s)) {
-		if (s && ip_string_to_network_address (s, &gateway_network)) {
-			_LOGI ("Gateway: %s", s);
-			nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
-		} else {
+	if (   g_variant_lookup (v_dict, "Gateway", "&s", &s)
+	    && s) {
+
+		if (!nm_utils_parse_inaddr_bin (AF_INET, s, &gateway_network)) {
 			_LOGW ("invalid 'Gateway': %s", s);
 			goto out;
 		}
-		nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
+
+		nm_modem_get_route_parameters (NM_MODEM (self),
+		                               &ip4_route_table,
+		                               &ip4_route_metric,
+		                               NULL,
+		                               NULL);
+		{
+			const NMPlatformIP4Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
+				.gateway = gateway_network,
+				.table_coerced = nm_platform_route_table_coerce (ip4_route_table),
+				.metric = ip4_route_metric,
+			};
+
+			_LOGI ("Gateway: %s", s);
+			nm_ip4_config_add_route (priv->ip4_config, &r, NULL);
+		}
 	} else {
 		_LOGW ("Settings 'Gateway' missing");
 		goto out;
@@ -867,7 +971,8 @@ context_property_changed (GDBusProxy *proxy,
 	if (g_variant_lookup (v_dict, "DomainNameServers", "^a&s", &array)) {
 		if (array) {
 			for (iter = array; *iter; iter++) {
-				if (ip_string_to_network_address (*iter, &address_network) && address_network > 0) {
+				if (   nm_utils_parse_inaddr_bin (AF_INET, *iter, &address_network)
+				    && address_network) {
 					_LOGI ("DNS: %s", *iter);
 					nm_ip4_config_add_nameserver (priv->ip4_config, address_network);
 				} else {
@@ -889,16 +994,25 @@ context_property_changed (GDBusProxy *proxy,
 
 	if (g_variant_lookup (v_dict, "MessageProxy", "&s", &s)) {
 		_LOGI ("MessageProxy: %s", s);
-		if (s && ip_string_to_network_address (s, &address_network)) {
-			NMPlatformIP4Route mms_route;
-
-			mms_route.network = address_network;
-			mms_route.plen = 32;
-			mms_route.gateway = gateway_network;
-
-			mms_route.metric = 1;
-
-			nm_ip4_config_add_route (priv->ip4_config, &mms_route);
+		if (   s
+		    && nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
+			nm_modem_get_route_parameters (NM_MODEM (self),
+			                               &ip4_route_table,
+			                               &ip4_route_metric,
+			                               NULL,
+			                               NULL);
+
+			{
+				const NMPlatformIP4Route mms_route = {
+					.network = address_network,
+					.plen = 32,
+					.gateway = gateway_network,
+					.table_coerced = nm_platform_route_table_coerce (ip4_route_table),
+					.metric = ip4_route_metric,
+				};
+
+				nm_ip4_config_add_route (priv->ip4_config, &mms_route, NULL);
+			}
 		} else {
 			_LOGW ("invalid MessageProxy: %s", s);
 		}
@@ -946,21 +1060,33 @@ static_stage3_ip4_config_start (NMModem *modem,
 }
 
 static void
-context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+context_proxy_new_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
-	GError *error = NULL;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	priv->context_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (error) {
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (!proxy) {
 		_LOGE ("failed to create ofono ConnectionContext DBus proxy: %s", error->message);
+		g_clear_object (&priv->context_proxy_cancellable);
 		nm_modem_emit_prepare_result (NM_MODEM (self), FALSE,
 		                              NM_DEVICE_STATE_REASON_MODEM_BUSY);
 		return;
 	}
 
+	priv->context_proxy = proxy;
+
 	if (!priv->gprs_attached) {
+		g_clear_object (&priv->context_proxy_cancellable);
 		nm_modem_emit_prepare_result (NM_MODEM (self), FALSE,
 		                              NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
 		return;
@@ -972,7 +1098,6 @@ context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 	 */
 	g_clear_object (&priv->ip4_config);
 
-	/* Watch for custom ofono PropertyChanged signals */
 	_nm_dbus_signal_connect (priv->context_proxy,
 	                         "PropertyChanged",
 	                         G_VARIANT_TYPE ("(sv)"),
@@ -986,9 +1111,9 @@ context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 	                                   g_variant_new ("b", TRUE)),
 	                   G_DBUS_CALL_FLAGS_NONE,
 	                   20000,
-	                   NULL,
-	                   (GAsyncReadyCallback) stage1_prepare_done,
-	                   g_object_ref (self));
+	                   priv->context_proxy_cancellable,
+	                   stage1_prepare_done,
+	                   self);
 }
 
 static void
@@ -998,16 +1123,20 @@ do_context_activate (NMModemOfono *self)
 
 	g_return_if_fail (NM_IS_MODEM_OFONO (self));
 
+	nm_clear_g_cancellable (&priv->context_proxy_cancellable);
 	g_clear_object (&priv->context_proxy);
+
+	priv->context_proxy_cancellable = g_cancellable_new ();
+
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 	                          NULL,
 	                          OFONO_DBUS_SERVICE,
 	                          priv->context_path,
 	                          OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT,
-	                          NULL,
-	                          (GAsyncReadyCallback) context_proxy_new_cb,
-	                          g_object_ref (self));
+	                          priv->context_proxy_cancellable,
+	                          context_proxy_new_cb,
+	                          self);
 }
 
 static GHashTable *
@@ -1018,7 +1147,7 @@ create_connect_properties (NMConnection *connection)
 	const char *str;
 
 	setting = nm_connection_get_setting_gsm (connection);
-	properties = g_hash_table_new (g_str_hash, g_str_equal);
+	properties = g_hash_table_new (nm_str_hash, g_str_equal);
 
 	str = nm_setting_gsm_get_apn (setting);
 	if (str)
@@ -1081,19 +1210,29 @@ act_stage1_prepare (NMModem *modem,
 }
 
 static void
-modem_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+modem_proxy_new_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
-	GError *error = NULL;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	priv->modem_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (error) {
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (!proxy) {
 		_LOGE ("failed to create ofono modem DBus proxy: %s", error->message);
+		g_clear_object (&priv->modem_proxy_cancellable);
 		return;
 	}
 
-	/* Watch for custom ofono PropertyChanged signals */
+	priv->modem_proxy = proxy;
+
 	_nm_dbus_signal_connect (priv->modem_proxy,
 	                         "PropertyChanged",
 	                         G_VARIANT_TYPE ("(sv)"),
@@ -1105,9 +1244,9 @@ modem_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 	                   NULL,
 	                   G_DBUS_CALL_FLAGS_NONE,
 	                   20000,
-	                   NULL,
-	                   (GAsyncReadyCallback) modem_get_properties_done,
-	                   g_object_ref (self));
+	                   priv->modem_proxy_cancellable,
+	                   modem_get_properties_done,
+	                   self);
 }
 
 /*****************************************************************************/
@@ -1121,6 +1260,9 @@ static void
 constructed (GObject *object)
 {
 	NMModemOfono *self = NM_MODEM_OFONO (object);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	priv->modem_proxy_cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
@@ -1128,9 +1270,11 @@ constructed (GObject *object)
 	                          OFONO_DBUS_SERVICE,
 	                          nm_modem_get_path (NM_MODEM (self)),
 	                          OFONO_DBUS_INTERFACE_MODEM,
-	                          NULL,
-	                          (GAsyncReadyCallback) modem_proxy_new_cb,
-	                          g_object_ref (self));
+	                          priv->modem_proxy_cancellable,
+	                          modem_proxy_new_cb,
+	                          self);
+
+	G_OBJECT_CLASS (nm_modem_ofono_parent_class)->constructed (object);
 }
 
 NMModem *
@@ -1163,6 +1307,11 @@ dispose (GObject *object)
 	NMModemOfono *self = NM_MODEM_OFONO (object);
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
 
+	nm_clear_g_cancellable (&priv->modem_proxy_cancellable);
+	nm_clear_g_cancellable (&priv->connman_proxy_cancellable);
+	nm_clear_g_cancellable (&priv->context_proxy_cancellable);
+	nm_clear_g_cancellable (&priv->sim_proxy_cancellable);
+
 	if (priv->connect_properties) {
 		g_hash_table_destroy (priv->connect_properties);
 		priv->connect_properties = NULL;
@@ -1171,15 +1320,22 @@ dispose (GObject *object)
 	g_clear_object (&priv->ip4_config);
 
 	if (priv->modem_proxy) {
-		g_signal_handlers_disconnect_by_data (priv->modem_proxy, NM_MODEM_OFONO (self));
+		g_signal_handlers_disconnect_by_data (priv->modem_proxy, self);
 		g_clear_object (&priv->modem_proxy);
 	}
 
-	g_clear_object (&priv->connman_proxy);
-	g_clear_object (&priv->context_proxy);
+	if (priv->connman_proxy) {
+		g_signal_handlers_disconnect_by_data (priv->connman_proxy, self);
+		g_clear_object (&priv->connman_proxy);
+	}
+
+	if (priv->context_proxy) {
+		g_signal_handlers_disconnect_by_data (priv->context_proxy, self);
+		g_clear_object (&priv->context_proxy);
+	}
 
 	if (priv->sim_proxy) {
-		g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
+		g_signal_handlers_disconnect_by_data (priv->sim_proxy, self);
 		g_clear_object (&priv->sim_proxy);
 	}
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 6494b849..77495b62 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -26,13 +26,13 @@
 #include <fcntl.h>
 #include <string.h>
 #include <termios.h>
+#include <linux/rtnetlink.h>
 
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "nm-setting-connection.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
-#include "nm-route-manager.h"
 #include "nm-netns.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
@@ -98,6 +98,11 @@ typedef struct _NMModemPrivate {
 
 	guint32 mm_ip_timeout;
 
+	guint32 ip4_route_table;
+	guint32 ip4_route_metric;
+	guint32 ip6_route_table;
+	guint32 ip6_route_metric;
+
 	/* PPP stats */
 	guint32 in_bytes;
 	guint32 out_bytes;
@@ -108,6 +113,46 @@ G_DEFINE_TYPE (NMModem, nm_modem, G_TYPE_OBJECT)
 #define NM_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMModem, NM_IS_MODEM)
 
 /*****************************************************************************/
+
+#define _NMLOG_PREFIX_BUFLEN              64
+#define _NMLOG_PREFIX_NAME                "modem"
+#define _NMLOG_DOMAIN                     LOGD_MB
+
+static const char *
+_nmlog_prefix (char *prefix, NMModem *self)
+{
+	const char *uuid;
+	int c;
+
+	if (!self)
+		return "";
+
+	uuid = nm_modem_get_uid (self);
+
+	if (uuid) {
+		char pp[_NMLOG_PREFIX_BUFLEN - 5];
+
+		c = g_snprintf (prefix, _NMLOG_PREFIX_BUFLEN, "[%s]",
+		                nm_strquote (pp, sizeof (pp), uuid));
+	} else
+		c = g_snprintf (prefix, _NMLOG_PREFIX_BUFLEN, "[%p]", self);
+	nm_assert (c < _NMLOG_PREFIX_BUFLEN);
+
+	return prefix;
+}
+
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        char _prefix[_NMLOG_PREFIX_BUFLEN]; \
+        \
+        nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
+                "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                _NMLOG_PREFIX_NAME, \
+                _nmlog_prefix (_prefix, (self)) \
+                _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+    } G_STMT_END
+
+/*****************************************************************************/
 /* State/enabled/connected */
 
 static const char *state_table[] = {
@@ -151,11 +196,10 @@ nm_modem_set_state (NMModem *self,
 	priv->prev_state = NM_MODEM_STATE_UNKNOWN;
 
 	if (new_state != old_state) {
-		nm_log_info (LOGD_MB, "(%s): modem state changed, '%s' --> '%s' (reason: %s)\n",
-		             nm_modem_get_uid (self),
-		             nm_modem_state_to_string (old_state),
-		             nm_modem_state_to_string (new_state),
-		             reason ? reason : "none");
+		_LOGI ("modem state changed, '%s' --> '%s' (reason: %s)",
+		       nm_modem_state_to_string (old_state),
+		       nm_modem_state_to_string (new_state),
+		       reason ? reason : "none");
 
 		priv->state = new_state;
 		_notify (self, PROP_STATE);
@@ -181,24 +225,20 @@ nm_modem_set_mm_enabled (NMModem *self,
 	NMModemState prev_state = priv->state;
 
 	if (enabled && priv->state >= NM_MODEM_STATE_ENABLING) {
-		nm_log_dbg (LOGD_MB, "(%s): cannot enable modem: already enabled",
-		            nm_modem_get_uid (self));
+		_LOGD ("cannot enable modem: already enabled");
 		return;
 	}
 	if (!enabled && priv->state <= NM_MODEM_STATE_DISABLING) {
-		nm_log_dbg (LOGD_MB, "(%s): cannot disable modem: already disabled",
-		            nm_modem_get_uid (self));
+		_LOGD ("cannot disable modem: already disabled");
 		return;
 	}
 
 	if (priv->state <= NM_MODEM_STATE_INITIALIZING) {
-		nm_log_dbg (LOGD_MB, "(%s): cannot enable/disable modem: initializing or failed",
-		            nm_modem_get_uid (self));
+		_LOGD ("cannot enable/disable modem: initializing or failed");
 		return;
 	} else if (priv->state == NM_MODEM_STATE_LOCKED) {
 		/* Don't try to enable if the modem is locked since that will fail */
-		nm_log_warn (LOGD_MB, "(%s): cannot enable/disable modem: locked",
-		             nm_modem_get_uid (self));
+		_LOGW ("cannot enable/disable modem: locked");
 
 		/* Try to unlock the modem if it's being enabled */
 		if (enabled)
@@ -468,7 +508,7 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	}
 
 	if (!num || dns_workaround) {
-		nm_log_warn (LOGD_PPP, "compensating for invalid PPP-provided nameservers");
+		_LOGW ("compensating for invalid PPP-provided nameservers");
 		nm_ip4_config_reset_nameservers (config);
 		nm_ip4_config_add_nameserver (config, good_dns1);
 		nm_ip4_config_add_nameserver (config, good_dns2);
@@ -561,9 +601,8 @@ ppp_stage3_ip_config_start (NMModem *self,
 	/* Check if ModemManager requested a specific IP timeout to be used. If 0 reported,
 	 * use the default one (30s) */
 	if (priv->mm_ip_timeout > 0) {
-		nm_log_info (LOGD_PPP, "(%s): using modem-specified IP timeout: %u seconds",
-		             nm_modem_get_uid (self),
-		             priv->mm_ip_timeout);
+		_LOGI ("using modem-specified IP timeout: %u seconds",
+		       priv->mm_ip_timeout);
 		ip_timeout = priv->mm_ip_timeout;
 	}
 
@@ -577,12 +616,18 @@ ppp_stage3_ip_config_start (NMModem *self,
 
 	priv->ppp_manager = nm_ppp_manager_create (priv->data_port, &error);
 
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     priv->ip4_route_table,
+		                                     priv->ip4_route_metric,
+		                                     priv->ip6_route_table,
+		                                     priv->ip6_route_metric);
+	}
+
 	if (   !priv->ppp_manager
 	    || !nm_ppp_manager_start (priv->ppp_manager, req, ppp_name,
 	                              ip_timeout, baud_override, &error)) {
-		nm_log_err (LOGD_PPP, "(%s): error starting PPP: %s",
-		            nm_modem_get_uid (self),
-		            error->message);
+		_LOGE ("error starting PPP: %s", error->message);
 		g_error_free (error);
 
 		g_clear_object (&priv->ppp_manager);
@@ -621,7 +666,7 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 	const char *method;
 	NMActStageReturn ret;
 
-	nm_log_dbg (LOGD_MB, "ip4_config_start");
+	_LOGD ("ip4_config_start");
 
 	g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE);
 	g_return_val_if_fail (NM_IS_DEVICE (device), NM_ACT_STAGE_RETURN_FAILURE);
@@ -640,9 +685,8 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) != 0) {
-		nm_log_warn (LOGD_MB | LOGD_IP4,
-		             "(%s): unhandled WWAN IPv4 method '%s'; will fail",
-		             nm_modem_get_uid (self), method);
+		_LOGW ("unhandled WWAN IPv4 method '%s'; will fail",
+		       method);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
@@ -653,15 +697,15 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		ret = ppp_stage3_ip_config_start (self, req, out_failure_reason);
 		break;
 	case NM_MODEM_IP_METHOD_STATIC:
-		nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_STATIC");
+		_LOGD ("MODEM_IP_METHOD_STATIC");
 		ret = NM_MODEM_GET_CLASS (self)->static_stage3_ip4_config_start (self, req, out_failure_reason);
 		break;
 	case NM_MODEM_IP_METHOD_AUTO:
-		nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_AUTO");
+		_LOGD ("MODEM_IP_METHOD_AUTO");
 		ret = device_class->act_stage3_ip4_config_start (device, NULL, out_failure_reason);
 		break;
 	default:
-		nm_log_info (LOGD_MB, "(%s): IPv4 configuration disabled", nm_modem_get_uid (self));
+		_LOGI ("IPv4 configuration disabled");
 		ret = NM_ACT_STAGE_RETURN_IP_FAIL;
 		break;
 	}
@@ -676,13 +720,15 @@ nm_modem_ip4_pre_commit (NMModem *modem,
 {
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (modem);
 
+	nm_modem_set_route_parameters_from_device (modem, device);
+
 	/* If the modem has an ethernet-type data interface (ie, not PPP and thus
 	 * not point-to-point) and IP config has a /32 prefix, then we assume that
 	 * ARP will be pointless and we turn it off.
 	 */
 	if (   priv->ip4_method == NM_MODEM_IP_METHOD_STATIC
 	    || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO) {
-		const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, 0);
+		const NMPlatformIP4Address *address = nm_ip4_config_get_first_address (config);
 
 		g_assert (address);
 		if (address->plen == 32)
@@ -698,7 +744,8 @@ nm_modem_emit_ip6_config_result (NMModem *self,
                                  GError *error)
 {
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
-	guint i, num;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Address *addr;
 	gboolean do_slaac = TRUE;
 
 	if (error) {
@@ -710,11 +757,7 @@ nm_modem_emit_ip6_config_result (NMModem *self,
 		/* If the IPv6 configuration only included a Link-Local address, then
 		 * we have to run SLAAC to get the full IPv6 configuration.
 		 */
-		num = nm_ip6_config_get_num_addresses (config);
-		g_assert (num > 0);
-		for (i = 0; i < num; i++) {
-			const NMPlatformIP6Address * addr = nm_ip6_config_get_address (config, i);
-
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, config, &addr) {
 			if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) {
 				if (!priv->iid.id)
 					priv->iid.id = ((guint64 *)(&addr->address.s6_addr))[1];
@@ -757,9 +800,8 @@ nm_modem_stage3_ip6_config_start (NMModem *self,
 		return NM_ACT_STAGE_RETURN_IP_DONE;
 
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) != 0) {
-		nm_log_warn (LOGD_MB | LOGD_IP6,
-		             "(%s): unhandled WWAN IPv6 method '%s'; will fail",
-		             nm_modem_get_uid (self), method);
+		_LOGW ("unhandled WWAN IPv6 method '%s'; will fail",
+		       method);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
@@ -778,7 +820,7 @@ nm_modem_stage3_ip6_config_start (NMModem *self,
 		ret = NM_MODEM_GET_CLASS (self)->stage3_ip6_config_request (self, out_failure_reason);
 		break;
 	default:
-		nm_log_info (LOGD_MB, "(%s): IPv6 configuration disabled", nm_modem_get_uid (self));
+		_LOGI ("IPv6 configuration disabled");
 		ret = NM_ACT_STAGE_RETURN_IP_FAIL;
 		break;
 	}
@@ -854,7 +896,7 @@ modem_secrets_cb (NMActRequest *req,
 		return;
 
 	if (error)
-		nm_log_warn (LOGD_MB, "(%s): %s", nm_modem_get_uid (self), error->message);
+		_LOGW ("modem-secrets: %s", error->message);
 
 	g_signal_emit (self, signals[AUTH_RESULT], 0, error);
 }
@@ -975,17 +1017,15 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
 		str = nm_setting_gsm_get_device_id (s_gsm);
 		if (str) {
 			if (!priv->device_id) {
-				nm_log_dbg (LOGD_MB, "(%s): %s/%s has device-id, device does not",
-				            priv->uid,
-				            nm_connection_get_uuid (connection),
-				            nm_connection_get_id (connection));
+				_LOGD ("%s/%s has device-id, device does not",
+				       nm_connection_get_uuid (connection),
+				       nm_connection_get_id (connection));
 				return FALSE;
 			}
 			if (strcmp (str, priv->device_id)) {
-				nm_log_dbg (LOGD_MB, "(%s): %s/%s device-id mismatch",
-				            priv->uid,
-				            nm_connection_get_uuid (connection),
-				            nm_connection_get_id (connection));
+				_LOGD ("%s/%s device-id mismatch",
+				       nm_connection_get_uuid (connection),
+				       nm_connection_get_id (connection));
 				return FALSE;
 			}
 		}
@@ -998,10 +1038,9 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
 		str = nm_setting_gsm_get_sim_id (s_gsm);
 		if (str && priv->sim_id) {
 			if (strcmp (str, priv->sim_id)) {
-				nm_log_dbg (LOGD_MB, "(%s): %s/%s sim-id mismatch",
-				            priv->uid,
-				            nm_connection_get_uuid (connection),
-				            nm_connection_get_id (connection));
+				_LOGD ("%s/%s sim-id mismatch",
+				       nm_connection_get_uuid (connection),
+				       nm_connection_get_id (connection));
 				return FALSE;
 			}
 		}
@@ -1009,10 +1048,9 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
 		str = nm_setting_gsm_get_sim_operator_id (s_gsm);
 		if (str && priv->sim_operator_id) {
 			if (strcmp (str, priv->sim_operator_id)) {
-				nm_log_dbg (LOGD_MB, "(%s): %s/%s sim-operator-id mismatch",
-				            priv->uid,
-				            nm_connection_get_uuid (connection),
-				            nm_connection_get_id (connection));
+				_LOGD ("%s/%s sim-operator-id mismatch",
+				       nm_connection_get_uuid (connection),
+				       nm_connection_get_id (connection));
 				return FALSE;
 			}
 		}
@@ -1069,10 +1107,11 @@ deactivate_cleanup (NMModem *self, NMDevice *device)
 		    priv->ip6_method == NM_MODEM_IP_METHOD_AUTO) {
 			ifindex = nm_device_get_ip_ifindex (device);
 			if (ifindex > 0) {
-				nm_route_manager_route_flush (nm_netns_get_route_manager (nm_device_get_netns (device)),
-				                              ifindex);
-				nm_platform_address_flush (nm_device_get_platform (device), ifindex);
-				nm_platform_link_set_down (nm_device_get_platform (device), ifindex);
+				NMPlatform *platform = nm_device_get_platform (device);
+
+				nm_platform_ip_route_flush (platform, AF_UNSPEC, ifindex);
+				nm_platform_ip_address_flush (platform, AF_UNSPEC, ifindex);
+				nm_platform_link_set_down (platform, ifindex);
 			}
 		}
 	}
@@ -1149,12 +1188,12 @@ ppp_manager_stop_ready (NMPPPManager *ppp_manager,
                         GAsyncResult *res,
                         DeactivateContext *ctx)
 {
+	NMModem *self = ctx->self;
 	GError *error = NULL;
 
 	if (!nm_ppp_manager_stop_finish (ppp_manager, res, &error)) {
-		nm_log_warn (LOGD_MB, "(%s): cannot stop PPP manager: %s",
-		             nm_modem_get_uid (ctx->self),
-		             error->message);
+		_LOGW ("cannot stop PPP manager: %s",
+		       error->message);
 		g_simple_async_result_take_error (ctx->result, error);
 		deactivate_context_complete (ctx);
 		return;
@@ -1168,7 +1207,8 @@ ppp_manager_stop_ready (NMPPPManager *ppp_manager,
 static void
 deactivate_step (DeactivateContext *ctx)
 {
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (ctx->self);
+	NMModem *self = ctx->self;
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
 	GError *error = NULL;
 
 	/* Check cancellable in each step */
@@ -1187,7 +1227,7 @@ deactivate_step (DeactivateContext *ctx)
 		if (priv->ppp_manager)
 			ctx->ppp_manager = g_object_ref (priv->ppp_manager);
 		/* Run cleanup */
-		NM_MODEM_GET_CLASS (ctx->self)->deactivate_cleanup (ctx->self, ctx->device);
+		NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, ctx->device);
 		ctx->step++;
 		/* fall through */
 	case DEACTIVATE_CONTEXT_STEP_PPP_MANAGER_STOP:
@@ -1203,16 +1243,15 @@ deactivate_step (DeactivateContext *ctx)
 		/* fall through */
 	case DEACTIVATE_CONTEXT_STEP_MM_DISCONNECT:
 		/* Disconnect asynchronously */
-		NM_MODEM_GET_CLASS (ctx->self)->disconnect (ctx->self,
-		                                            FALSE,
-		                                            ctx->cancellable,
-		                                            (GAsyncReadyCallback) disconnect_ready,
-		                                            ctx);
+		NM_MODEM_GET_CLASS (self)->disconnect (self,
+		                                       FALSE,
+		                                       ctx->cancellable,
+		                                       (GAsyncReadyCallback) disconnect_ready,
+		                                       ctx);
 		return;
 
 	case DEACTIVATE_CONTEXT_STEP_LAST:
-		nm_log_dbg (LOGD_MB, "(%s): modem deactivation finished",
-		            nm_modem_get_uid (ctx->self));
+		_LOGD ("modem deactivation finished");
 		deactivate_context_complete (ctx);
 		return;
 	}
@@ -1377,6 +1416,76 @@ nm_modem_get_iid (NMModem *self, NMUtilsIPv6IfaceId *out_iid)
 /*****************************************************************************/
 
 void
+nm_modem_get_route_parameters (NMModem *self,
+                               guint32 *out_ip4_route_table,
+                               guint32 *out_ip4_route_metric,
+                               guint32 *out_ip6_route_table,
+                               guint32 *out_ip6_route_metric)
+{
+	NMModemPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM (self));
+
+	priv = NM_MODEM_GET_PRIVATE (self);
+	NM_SET_OUT (out_ip4_route_table, priv->ip4_route_table);
+	NM_SET_OUT (out_ip4_route_metric, priv->ip4_route_metric);
+	NM_SET_OUT (out_ip6_route_table, priv->ip6_route_table);
+	NM_SET_OUT (out_ip6_route_metric, priv->ip6_route_metric);
+}
+
+void
+nm_modem_set_route_parameters (NMModem *self,
+                               guint32 ip4_route_table,
+                               guint32 ip4_route_metric,
+                               guint32 ip6_route_table,
+                               guint32 ip6_route_metric)
+{
+	NMModemPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM (self));
+
+	priv = NM_MODEM_GET_PRIVATE (self);
+	if (   priv->ip4_route_table  != ip4_route_table
+	    || priv->ip4_route_metric != ip4_route_metric
+	    || priv->ip6_route_table  != ip6_route_table
+	    || priv->ip6_route_metric != ip6_route_metric) {
+		priv->ip4_route_table = ip4_route_table;
+		priv->ip4_route_metric = ip4_route_metric;
+		priv->ip6_route_table = ip6_route_table;
+		priv->ip6_route_metric = ip6_route_metric;
+
+		_LOGT ("route-parameters: table-v4: %u, metric-v4: %u, table-v6: %u, metric-v6: %u",
+		       priv->ip4_route_table,
+		       priv->ip4_route_metric,
+		       priv->ip6_route_table,
+		       priv->ip6_route_metric);
+	}
+
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     priv->ip4_route_table,
+		                                     priv->ip4_route_metric,
+		                                     priv->ip6_route_table,
+		                                     priv->ip6_route_metric);
+	}
+}
+
+void
+nm_modem_set_route_parameters_from_device (NMModem *self,
+                                           NMDevice *device)
+{
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	nm_modem_set_route_parameters (self,
+	                               nm_device_get_route_table (device, AF_INET, TRUE),
+	                               nm_device_get_route_metric (device, AF_INET),
+	                               nm_device_get_route_table (device, AF_INET6, TRUE),
+	                               nm_device_get_route_metric (device, AF_INET6));
+}
+
+/*****************************************************************************/
+
+void
 nm_modem_get_capabilities (NMModem *self,
                            NMDeviceModemCapabilities *modem_caps,
                            NMDeviceModemCapabilities *current_caps)
@@ -1451,6 +1560,7 @@ set_property (GObject *object, guint prop_id,
 	case PROP_PATH:
 		/* construct-only */
 		priv->path = g_value_dup_string (value);
+		g_return_if_fail (priv->path);
 		break;
 	case PROP_DRIVER:
 		/* construct-only */
@@ -1509,40 +1619,27 @@ set_property (GObject *object, guint prop_id,
 static void
 nm_modem_init (NMModem *self)
 {
+	NMModemPrivate *priv;
+
 	self->_priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_MODEM, NMModemPrivate);
+	priv = self->_priv;
+
+	priv->ip4_route_table = RT_TABLE_MAIN;
+	priv->ip4_route_metric = 700;
+	priv->ip6_route_table = RT_TABLE_MAIN;
+	priv->ip6_route_metric = 700;
 }
 
-static GObject*
-constructor (GType type,
-             guint n_construct_params,
-             GObjectConstructParam *construct_params)
+static void
+constructed (GObject *object)
 {
-	GObject *object;
 	NMModemPrivate *priv;
 
-	object = G_OBJECT_CLASS (nm_modem_parent_class)->constructor (type,
-	                                                              n_construct_params,
-	                                                              construct_params);
-	if (!object)
-		return NULL;
-
-	priv = NM_MODEM_GET_PRIVATE ((NMModem *) object);
+	G_OBJECT_CLASS (nm_modem_parent_class)->constructed (object);
 
-	if (!priv->data_port && !priv->control_port) {
-		nm_log_err (LOGD_PLATFORM, "neither modem command nor data interface provided");
-		goto err;
-	}
+	priv = NM_MODEM_GET_PRIVATE (NM_MODEM (object));
 
-	if (!priv->path) {
-		nm_log_err (LOGD_PLATFORM, "D-Bus path not provided");
-		goto err;
-	}
-
-	return object;
-
-err:
-	g_object_unref (object);
-	return NULL;
+	g_return_if_fail (priv->data_port || priv->control_port);
 }
 
 /*****************************************************************************/
@@ -1552,10 +1649,7 @@ dispose (GObject *object)
 {
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE ((NMModem *) object);
 
-	if (priv->act_request) {
-		g_object_unref (priv->act_request);
-		priv->act_request = NULL;
-	}
+	g_clear_object (&priv->act_request);
 
 	G_OBJECT_CLASS (nm_modem_parent_class)->dispose (object);
 }
@@ -1584,7 +1678,7 @@ nm_modem_class_init (NMModemClass *klass)
 
 	g_type_class_add_private (object_class, sizeof (NMModemPrivate));
 
-	object_class->constructor = constructor;
+	object_class->constructed = constructed;
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index a50727a9..9546e4a1 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -105,10 +105,12 @@ typedef enum {  /*< underscore_name=nm_modem_state >*/
 
 struct _NMModemPrivate;
 
-typedef struct {
+struct _NMModem {
 	GObject parent;
 	struct _NMModemPrivate *_priv;
-} NMModem;
+};
+
+typedef struct _NMModem NMModem;
 
 typedef struct {
 	GObjectClass parent;
@@ -185,6 +187,21 @@ gboolean nm_modem_complete_connection (NMModem *self,
                                        const GSList *existing_connections,
                                        GError **error);
 
+void nm_modem_get_route_parameters (NMModem *self,
+                                    guint32 *out_ip4_route_table,
+                                    guint32 *out_ip4_route_metric,
+                                    guint32 *out_ip6_route_table,
+                                    guint32 *out_ip6_route_metric);
+
+void nm_modem_set_route_parameters (NMModem *self,
+                                    guint32 ip4_route_table,
+                                    guint32 ip4_route_metric,
+                                    guint32 ip6_route_table,
+                                    guint32 ip6_route_metric);
+
+void nm_modem_set_route_parameters_from_device (NMModem *modem,
+                                                NMDevice *device);
+
 NMActStageReturn nm_modem_act_stage1_prepare (NMModem *modem,
                                               NMActRequest *req,
                                               NMDeviceStateReason *out_failure_reason);
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index fa4c8dbb..663102de 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -127,8 +127,8 @@ start (NMDeviceFactory *factory)
 	NMWwanFactory *self = NM_WWAN_FACTORY (factory);
 	NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE (self);
 
-	priv->mm = g_object_new (NM_TYPE_MODEM_MANAGER, NULL);
-	g_assert (priv->mm);
+	priv->mm = g_object_ref (nm_modem_manager_get ());
+
 	g_signal_connect (priv->mm,
 	                  NM_MODEM_MANAGER_MODEM_ADDED,
 	                  G_CALLBACK (modem_added_cb),